Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Rivet RTOS board support: ESP32-C6 real hardware (plan.md Phase 26).
Status
Confirmed working on real hardware: boot (through espflash's
ESP-IDF app image format — see link-esp32c6.ld's module docs),
watchdog disable, the monotonic clock (SYSTIMER), the polling
console, preemptive task spawning and dispatch, and — the hard part —
genuine tick-driven preemption between two same-priority tasks
(preempt_test.rs: reliable "ABABBABAB..." interleaving, both tasks
reaching completion, repeatable across independent flash/boot
cycles).
Four real, hardware-confirmed bugs found getting there, all
root-caused (not guessed) via bisecting print statements or, once
prints stopped showing up at all, via bisecting hardware behavior
itself against esp-hal's reference source:
- This core's PMP doesn't behave like the privileged spec's reference
shape
rivet-arch-riscv::pmpwas written against (proven correct on QEMU virt and MPS2-AN385) — probing its grain via the standard technique returned an implausible ~256 MiB minimum NAPOT region, not a real hardware property. Worked around, not fixed: hardware PMP guards are off entirely for this board (pmp-guarddisabled onrivet-arch-riscv), falling back to the kernel's own software stack-watermark check alone — already proven sufficient by itself on the ESP32-S3, which has no PMP/MPU-equivalent hardware guard at all. mcycle(RV32's cycle-count CSR) hangs forever the instant it's read on this core — not the standard rollover-safemcyclehdouble-read loop failing to converge (tried a 32-bit-only read too, still hung), the bare single-instructioncsrritself never completes, with no trap/panic ever printed.rivet-arch-riscv'sno-mcyclefeature skips defining__rivet_arch_cycle_countentirely; this crate provides it instead, derived from SYSTIMER (already used fornow_us()).- This chip's interrupt matrix gates CPU-level delivery through
PLIC_MX(0x2000_1000), a genuinely different peripheral fromINTPRI(0x600c_5000) despite similar-looking field names in theesp32c6PAC — writing enable/priority/threshold toINTPRIwas silently a no-op for delivery. See the "interrupt matrix" section below. - Enabling one of this chip's custom per-line
miebits (12-31) whilemtvecis in direct mode hard-locks the core immediately — no trap, no exception, nothing printable, every time. This chip's "plic" controller flavour requires vectoredmtvec(confirmed againstesp-hal's own_setup_interrupts, which always installs one before touching these lines) —rivet-arch-riscv's newvectored-trapfeature. Getting the resulting image to actually flash uncovered a fifth, tooling-level issue: seerivet-arch-riscv's own vector-table doc comment for theespflashalignment-padding quirk that cost the most time of anything in this list.
Full S3-equivalent test suite now passing on real hardware:
demo, preempt_test, sleep_test, mutex_test, stress_spawn,
join_test, respawn_test, stress_max_ptasks, soak_smoke,
report_test, deadline_test, watchdog_test (genuine LP_WDT
reset — rst:0x10 LP_WDT_SYS — after the expected marker),
fault_overflow/fault_isolate (via the software watermark check;
PMP guards are off, see bug #1), irq_test (a real peripheral
interrupt — UART0 tx_done — routed through rivet::irq, not a
software-only stand-in; see the "generic peripheral IRQ" section
below), embedded_hal_test (rivet-bsp-support's RivetDelay/
Serial are entirely board-agnostic, so this needed no new code at
all). smp_test doesn't apply — single-core chip.
The interrupt matrix + PLIC_MX (plan.md Phase 26 follow-up)
Same two-part design as the ESP32-S3's own interrupt matrix
(rivet-arch-xtensa's periph_irq/ipi modules), just RISC-V-side
register names: INTERRUPT_CORE0.core_0_intr_map(source) routes a
peripheral interrupt source (e.g. SYSTIMER_TARGET0 = 57,
FROM_CPU_INTR0 = 22 — confirmed against this PAC version's own
Interrupt enum) to one of 32 CPU interrupt lines; PLIC_MX
(0x2000_1000 — a different peripheral from INTPRI, despite
esp32c6's SVD using similar-looking names for both; confirmed by
reading esp-hal's own interrupt/riscv/plic.rs driver, which
esp-metadata's soc.toml says is the controller flavour this chip
actually uses) enables/prioritizes each line via its
mxint_enable/_pri/_thresh/_clear registers. INTPRI still
has one real job: its cpu_intr_from_cpu register is the software
self-trigger ("write 1 to raise IPI to self") used for the
reschedule line — a genuinely separate mechanism from the delivery
gate above. rivet-arch-riscv's trap handler doesn't know any of
this — dispatch reaches this crate via its board-irq-hook feature,
an escape hatch exactly for controllers that are real RISC-V
ISA-adjacent hardware but not portable across vendors the way
CLINT/PLIC are. Both PLIC_MX-enabled lines also need their mie bit
set directly at the core (raw CSR access — bits 12-31 aren't in the
riscv crate's typed API) — see bug #4 above for why that requires
rivet-arch-riscv/vectored-trap.
CPU lines 16 (tick) and 17 (reschedule) were picked arbitrarily from the "platform-defined" range (avoiding 3/7/11, which carry standard RISC-V meaning even though this core has no CLINT wired to them) — not verified against any documented reservation, just chosen to be unlikely to collide with anything.
Generic peripheral IRQ (rivet::irq)
Lines 18-23 are a small pool reserved for whatever peripherals an app
registers via rivet::irq::register/enable — a board-owned
[AtomicU32; 6] (IRQ_LINE_SOURCE) hands out one on first use and
remembers the mapping so a later enable for the same id reuses it.
rivet::irq::register/dispatch both index a fixed
[T; RIVET_MAX_IRQS] table in rivet itself (default 32) directly
by the u32 the caller passes — this chip's own peripheral source
numbers run well past that (UART0 = 43), so irq::UART0 etc. are
small logical ids instead of raw source numbers, translated to the
real source only where INTERRUPT_CORE0 routing actually needs it
(LOGICAL_TO_SOURCE). rivet-arch-riscv's board-irq-hook feature
grew three more extern hooks for this (__rivet_board_irq_{enable, disable,set_priority}), the same split as the tick/resched dispatch
hook: the mechanism (rivet::irq's table, __rivet_arch_irq_*'s
call-through) is arch-generic, which peripheral is which id and how
it's wired to a CPU line is this board's own business.
Why no esp-hal dependency
Same reasoning as rivet-bsp-esp32s3: esp-hal's own interrupt/timer
runtime would fight Rivet for ownership of the same hardware. Watchdog
disable and the boot-time cache/clock state are hand-rolled directly
against the esp32c6 PAC, cross-checked against esp-hal's own source
for correctness (register names, unlock keys, bit meanings) without
linking esp-hal itself.