ports/risc-v32/gnu: Add initial ESP32-C6 bare-metal ThreadX bring-up … - #576
ports/risc-v32/gnu: Add initial ESP32-C6 bare-metal ThreadX bring-up …#576alieissa-commits wants to merge 3 commits into
Conversation
The watchdog disable never took effect, which is what resets the board on a standalone power-on. With DR_REG_LP_WDT_BASE at 0x600B1C00, LP_WDT_WPROTECT_REG is at offset 0x18, so 0x600B1C18. The unlock key was going to 0x600B1C1C, which is LP_WDT_SWD_CONFIG_REG. Write protection was therefore never lifted and the hardware dropped the CONFIG0 write that was meant to stop the timer, while the key itself landed in the super watchdog's configuration register as an arbitrary value. Send the key to WPROTECT, clear CONFIG0, then re-lock. Name the registers rather than open coding the addresses, since that is how the mistake hid. Also disable the super watchdog, which was not being touched at all. It is a separate timer in the same block, enabled out of reset, and it resets the chip on its own period whatever LP_WDT is doing, so stopping only LP_WDT cannot fix the reset loop. Note that the ESP32-C6 uses the same key for both write-protect registers; the C3 and S3 use 0x8F1D312A for the super watchdog, so a snippet copied from those targets silently fails to unlock it here. Register offsets and the key value are from ESP-IDF's soc/lp_wdt_reg.h and esp_hal_wdt/esp32c6 lpwdt_ll.h. Verified by running the sequence against a stubbed register block: CONFIG0 ends at 0, both write-protect registers are re-locked, SWD_CONFIG has the disable and auto-feed bits set, and the key is no longer written to SWD_CONFIG. Not yet verified on hardware. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
|
Thank you for this — the hard parts are in good shape, and the two problems you documented in the README both have identifiable causes. Notes below so you can pick this up when you are back. First, what I checked and found correct, because these are the easy things to get wrong in a RISC-V port:
Issue 1, the standalone reset: fixed on the branchI have pushed one commit for this, because it was small and self-contained. The watchdog disable was never taking effect. With The super watchdog was also not being touched at all. It is a separate timer in the same block, enabled out of reset, and it resets the chip on its own period whatever LP_WDT is doing — so stopping only LP_WDT could not have fixed the loop. One ESP32-C6 specific trap worth remembering: this chip uses the same key for both write-protect registers, while the C3 and S3 use Worth noting why JTAG hid this: OpenOCD's C6 target feeds or disables the RTC watchdog when it attaches, and the core is halted for much of a debug session. "Works under GDB, resets standalone" is a useful signature for a watchdog problem rather than a coincidence. The commit is verified against a stubbed register block, not on hardware, so please confirm it on the DevKitC when you are back. Issue 2, the tick never advancingI have left this one for you, because it touches three files and needs the board to confirm. There are three independent causes, and the first is on its own enough to explain the symptom. The CPU has no direct mode
and A second, quieter problem sits alongside it: So .section .iram0.vectors, "ax"
.balign 0x100 /* 256 bytes, not .align 4 */
.option push
.option norvc /* each entry must be exactly 4 bytes */
.global _esp32c6_vector_table
_esp32c6_vector_table:
j _esp32c6_panic_handler /* 0: exceptions */
j _esp32c6_trap_entry /* 1 */
j _esp32c6_trap_entry /* 2 */
/* ... 3 through 31, all the same shim ... */
.option popwith la t0, _esp32c6_vector_table
ori t0, t0, 1 /* vectored mode */
csrw mtvec, t0Please also add The interrupt number is in mcause, not in a claim register
The in-tree call _tx_thread_context_save
csrr a0, mcause
call esp32c6_interrupt_dispatchvoid esp32c6_interrupt_dispatch(uint32_t mcause_val)
{
if (!(mcause_val & RISCV_MCAUSE_INTERRUPT_FLAG)) return;
uint32_t line = mcause_val & 0x1F; /* the CPU interrupt line */
if (line && s_isr_table[line]) s_isr_table[line](s_isr_arg[line]);
REG_WRITE(PLIC_MXINT_CLEAR_REG, 1UL << line); /* edge acknowledge */
}The systimer bus clock is never enabled
Two smaller things in the same file. Suggested orderEach step is independently observable, which should make picking this up cold easier:
One suggestion on scopeI would not follow the README's recommendation to replace the systimer with Separately, |
Summary
This PR adds an initial bare-metal Eclipse ThreadX bring-up example for the Espressif ESP32-C6 microcontroller (
ESP32-C6-DevKitC-1) underports/risc-v32/gnu/example_build/esp32c6/.It provides the basic hardware adaptation layer, memory map, build scripts, third-party licensing compliance, and a validation application demonstrating ThreadX thread creation and mutex synchronization on RISC-V32.
Key Changes & Implementations
1. Build System & Toolchain
CMakeLists.txt&cmake/esp32c6-toolchain.cmake: Cross-compilation support usingriscv32-esp-elf-gcc(-march=rv32imac_zicsr_zifencei -mabi=ilp32). Includes post-buildesptool.py elf2imagegeneration (esp32c6_demo.bin).2. Assembly Boot & Trap Infrastructure
entry.S: Initial CPU setup clearing Machine Interrupts (csrc mstatus, 8), setting up Global Pointer (gp), zeroing.bss, enforcing Direct Modemtvec(Mode 0), and jumping toc6_startup.vectors.S: Trap vector table pre-allocating 128-byte stack frames required by ThreadX RISC-V context save (_tx_thread_context_save), dispatching interrupts to C, and restoring thread context (_tx_thread_context_restore).tx_initialize_low_level.S: Assembly hook allocating stack space to preserveraacross platform C driver initialization (esp32c6_platform_init).3. Memory Map & Bootloader Validation
link.ld: Configured for ESP32-C6 512 KB SRAM (0x40800000). PlacedKEEP(*(.rodata_desc .rodata_desc.*))at byte offset 0x20 of segment 0 so Espressif's 2nd-stage bootloader validatesesp_app_desc_t(0xABCD5432magic word). Defines_tx_initialize_unused_memoryat heap boundary.4. Platform Hardware Drivers (
platform/)console.c: UART driver using Espressif ROM API (esp_rom_output_tx_one_char) with\n\r\nline normalization.interrupt.c: Configures PCR INTMTX bus clock (0x60096090), PLIC priority levels, CPU interrupt thresholds, andmie.MEIE/mie.MTIECSR bits. Implements RISC-V PLIC Claim/Complete transaction dispatching viaPLIC_MXINT_CLAIM_REG(0x20001094).systimer.c: Links pinned ESP-IDFsystimer_hal.c, connects Alarm 0 to 16 MHz Counter 0, takes hardware snapshots, and reloads periodic 10ms target values.startup.c: Definesesp_app_desc_t, unlocks/disables hardware LP Watchdog Timer (LP_WDT), and hands off execution totx_kernel_enter().5. Application Demo & Compliance
demo_threadx.c: ThreadX kernel validation demo creatingdemo_mutex,thread_0, andthread_1with 16-byte aligned static RISC-V stack buffers.NOTICE.md: Formal third-party IP attribution file following Eclipse Foundation and Apache 2.0 standards for Espressif Systems, 10xEngineers, and Zephyr RTOS.README.md: Build, flash, serial monitor, and OpenOCD/GDB debugging instructions.Known Technical Issues & Status (For Future Development)
Warning
This bring-up example is an initial foundation and has active hardware tick issues documented in
README.md:thread_0_entry. However, after callingtx_thread_sleep(), the ThreadX scheduler remains in_tx_thread_schedule_loopbecause PLIC external interrupts are not advancing_tx_timer_system_clock.Recommended Next Steps
As outlined in [
threadx_ESP32C6_native_WIFI_plan.md]:platform/systimer.cimplementation with a pinned ESP-IDFesp_timerframework component import (esp_timer_systimer.c/systimer_hal.c) or use the native CPU RISC-V Machine Timer (mtimecmp/MTIE).Verification Performed
riscv32-esp-elf-gcc15.2.0.esptool.py elf2image.esp32c6_demo.elf.