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 4 commits into
Conversation
|
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, |
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>
The example assembled its own ThreadX library from two globs, and one of them
was a directory level short:
${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/src/*.c
Four levels up from example_build/esp32c6 is ports/, so that pattern named
ports/common/src, which does not exist. file(GLOB) on a missing directory is
not an error -- it returns nothing -- so the library was built from the eight
port .S files and no kernel C source at all. Every ThreadX entry point the
example calls was therefore undefined at link time:
_tx_initialize_kernel_enter from startup.c, and tx_mutex_create, tx_mutex_get,
tx_mutex_put, tx_thread_create and tx_thread_sleep from demo_threadx.c. The
port root is five levels up, which is what core_v_mcu/CMakeLists.txt already
says.
The same hand-rolled library also left include/tx_user.h parsed by nothing.
TX_INCLUDE_USER_DEFINE_FILE is the #ifdef guarding the #include "tx_user.h" in
ports/risc-v32/common/tx_port_riscv32_common.h, and it appeared nowhere in the
example, so all four settings in that file silently fell back to the port
default -- TX_ENABLE_STACK_CHECKING was off and TX_MINIMUM_STACK was 1024
rather than the 512 the file asks for. The demo's 2048-byte stacks clear both
figures, so nothing failed loudly; the configuration was just not applied.
Both defects have one cause, so take the fix the other RISC-V examples already
use: set THREADX_ARCH, THREADX_TOOLCHAIN and TX_USER_FILE, then
add_subdirectory() the top-level CMakeLists and link the threadx target. The
top level copies TX_USER_FILE to custom_inc/tx_user.h and defines
TX_INCLUDE_USER_DEFINE_FILE as a PUBLIC property of that target, so linking it
is what makes the kernel and the example agree on one configuration. The BSP
object library links it too, for the headers and the macro rather than for any
archive.
Also drop ESP32C6_LINK_FLAGS, which was computed over two blocks and then
never referenced -- target_link_options() below it repeats the same three flags
directly.
Verified with cmake and riscv32 GCC: the kernel builds 194/194 and the six
symbols above are defined; TX_INCLUDE_USER_DEFINE_FILE and custom_inc reach the
kernel, the BSP, the demo and the assembly sources; and a _Static_assert on
TX_MINIMUM_STACK == 512 and TX_TIMER_TICKS_PER_SECOND == 100 plus an #ifdef on
TX_ENABLE_STACK_CHECKING all hold now and all fail without the change. The BSP
and the final image still need a real ESP-IDF and riscv32-esp-elf-gcc, neither
of which was available, so they are unbuilt here.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
|
Thanks for this, Ali. I've rebased the branch onto current RebaseThe branch was cut from Worth knowing about two of them, since they now gate this PR for the first time:
One commit added: the example couldn't linkWhile checking the rebase I found the CMakeLists was building the kernel from a glob that was one directory level short: file(GLOB THREADX_COMMON_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/src/*.c")Four levels up from The same hand-rolled library also left Both have one cause, so I took the fix the other RISC-V examples already use: set What I verified, and what I couldn'tWith
I could not build the BSP or the image — no ESP-IDF and no Two things for you
No objection to the overall shape of the contribution — a bare-metal C6 bring-up is a welcome addition, and the |
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.