Everything here is specific to the esp32-c3, which I have.
It might work for other chips, but that is not guaranteed
#Building and running
#Building
riscv32-none-elf-objdump -d target/riscv32imc-unknown-none-elf/release/bare-bones
#RUnning
source /opt/esp-idf/export.sh
qemu-system-riscv32 -nographic \
-M esp32c3 \
-drive file=zig-out/bin/yanos.bin,if=mtd,format=raw \
-serial mon:stdio -s
#Knowledge
- when the esp is first turned on, it boots into the ROM bootloader
- this bootloader is written once, in the factory, and can't be overwritten
- the ROM bootloader does barely anything
- then the ROM bootloader tries to boot the program located at
0x0
- but execution starts there, it checks for headers
- there is direct boot, which we are using here
- and there is some other method, were the code is loaded into RAM, as specified by the header residing at
0x0
#Direct Boot
- shoudln't really be used, I just like to torture myself and I have no idea what I am doing
- should be used to bootstrap "new languages, frameworks and execution environments" (according to direct boot example
- direct boot only happens, when
- Secure boot is disabled
- direct boot not disabled by
EFUSE_DIS_LEGACY_SPI_BOOT
- ROM bootloader does not detect valid binary image
- first 8 bytes in flash are
1d 04 db ae 1d 04 db ae
- this is handled by the linker script
- prereqs 1-2 can be handled by doing some
menuconfig
shit in a normal esp project, but I think they are default, because I did nothing specific
- the ROM bootloader then jumps to
Flash start + 8
- after booting here, the code needs to:
- [x] set up global pointer register
- the global pointer is used to optimize/enable at all (?) global variable access
- in riscv this is a special register
gp
/x3
- [x] set up stack pointer register
- enable stack access
- in riscv this is a special register
sp
/x2
- [x] zero-initialize the .bss section
- the
.bss
section stores static variables, which are not yet initialized
- is expected to be
0
- [ ] initialize the .data section, copying it from ROM
- contains local and global static variables
- [ ] write the vector table address to the MTVEC register (optional)
- [ ] call C library initialization (optional)
- [x] call the main function
#Watchdogs
- after the above steps, the system will still continue to bootloop
- this is because the watchdogs are not being fed (reset) or disabled
- there are 3 watchdogs, one in each timer module
- TIMG0 - the timer module
- RTC - the rtc module
- SWD - super watchdog timer
- disable TIMG0:
- disable write protection by writing
0x50D83AA1
to TIMG_WDT_WKEY
- clear
TIMG_WDT_FLASHBOOT_MOD_EN
- then disable to timer by setting
TIMG_WDT_EN
- reenable write protection by zeroing
TIMG_WDT_WKEY
- disable RTC:
- disable write protection by writing
0x50D83AA1
to RTC_CNTL_WDT_WKEY
- clear
RTC_CNTL_WDT_FLASHBOOT_MOD_EN
- then disable to timer by setting
RTC_CNTL_WDT_EN
- reenable write protection by zeroing
RTC_CNTL_WDT_WKEY
- disable SWD
- disable write protection for SWD write
0x8F1D312A
to RTC_CNTL_SWD_WKEY
- disable timer by writing 1 to
RTC_CNTL_SWD_DISABLE
- you might have noticed the write protection
- those timers help to reset the board, in case it gets into a deadlock or some other precarious situation, so it isn't necessarily recommended to disable those timers
- we will do it here, just to get something working
#UART
- the esp32c3 offers a rom function for printing a single byte to the UART output:
uart_tx_one_char(b: u8)
- we can use that to split a message in single bytes and transmit them