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 — Xtensa LX7 ISA port (ESP32-S3, plan.md Phases 21/24).
Implements the Group A (rivet::port::arch) symbol contract for the
xtensa-esp32s3-none-elf target: context switch, interrupt entry/exit,
critical section. Contains no board/MMIO knowledge (UART, watchdog,
SYSTIMER) — that is rivet-bsp-esp32s3's job.
Why this crate depends on xtensa-lx/xtensa-lx-rt
Unlike rivet-arch-riscv/rivet-arch-cortex-m (hand-written trap entry
assembly, verified against a QEMU machine model), this port targets
real hardware only — no Xtensa QEMU model is available in this
environment. Xtensa's exception/interrupt architecture (multiple
priority levels, hardware-assisted windowed-register spill/fill) is
substantially harder to get right blind than RISC-V's single mtvec
or Cortex-M's fixed vector table, with a real bricking risk and no fast
simulator iteration loop. xtensa-lx/xtensa-lx-rt are the direct
Xtensa analogs of the riscv/cortex-m crates the other two ports
depend on for CSR/register access — except here the boot-glue layer
(vector table, exception/interrupt entry assembly, Reset) is also
sourced from there rather than hand-written, because that is exactly
the layer where a mistake is most likely and least recoverable. Rivet's
own code starts where xtensa-lx-rt hands control to a Rust interrupt
handler with a full, already-correctly-saved Context.
Task switching: mutate the save frame, don't hand-write asm
xtensa_lx_rt::exception::Context is the CPU's full saved state
(PC, PS, A0-A15, SAR, ...). #[interrupt(3)]'s handler receives
&mut Context pointing at a frame xtensa-lx-rt's own assembly
allocated 256 bytes below whatever stack was live at interrupt time
(confirmed by reading SAVE_CONTEXT/RESTORE_CONTEXT in
xtensa-lx-rt's source: A1 is saved/restored as the genuine
pre-interrupt stack pointer). Whatever this handler leaves in that
struct is what gets resumed on return — so a context switch is: copy
the outgoing task's full state out, copy the incoming task's full
state in. No hand-written save/restore assembly needed for the switch
itself.
Because the CPU's windowed-register overflow/underflow spill handling
is defined relative to the current stack (A1), not any global
state, switching A1 to a different task's stack as part of restoring
its Context is sufficient on its own — the hardware spills/fills
each task's own window state to/from that task's own stack memory on
demand, already isolated by construction. Context does not persist
WINDOWBASE/WINDOWSTART — confirmed against xtensa-lx-rt's own
struct definition, consistent with this being re-derivable hardware
state rather than architectural task state.
A task's first-ever dispatch does not go through a real interrupt
A brand new task can be dispatched two different ways: via
[__rivet_arch_start_first_task] (the very first task the scheduler
ever runs, reached directly from boot) or, just as often, via the
ordinary tick/reschedule interrupt path (every other task spawned
before rivet::run(), since only one task can be "first"). Both cases
fabricate the same shape of Context — see [fresh_task_context] —
reached by jumping (never calling) into [rivet_ptask_trampoline_impl],
whose first instruction is entry a1, 0. This is exactly how
xtensa-lx-rt's own Reset reaches main (a plain jump into
windowed code with A1 pre-pointed at a fresh stack), and the one
thing a fabricated first dispatch does not need to get right — a
valid return address / caller frame — is never exercised, because the
trampoline never executes retw: task exit goes through an explicit
call to rivet_task_exit_core, never a return.
Distinguishing "never dispatched" from "has a saved Context"
[__rivet_arch_init_task_stack] runs before the task has an id
(rivet::preempt::spawn calls it before registering the task), so it
cannot yet index into [CONTEXTS] (which is keyed by task id). Its
returned sp therefore encodes (entry_fn, arg, stack_base, stack_len) directly — a bootstrap marker, not a Context index.
sp values of the two kinds are reliably distinguishable at every
point that needs to tell them apart: a task id is always
< MAX_PTASKS (a handful, in practice); a bootstrap marker is always
>= MAX_PTASKS by construction (see [encode_bootstrap]). The first
time a task is actually interrupted (by either path), its Tcb.sp is
permanently rewritten to its task id, and the bootstrap marker for it
is never consulted again.