journal 2026-05-03

P50 - the chip takes its first trap in S-mode

p50riscvsmodedelegationautonomous-mode

Autonomous-mode rung, 6th past P45. After P49 made priv mode real, P50 makes priv mode actually shape where traps go.

What landed

enter_trap now decides between M-mode entry and S-mode entry based on the trap’s cause and the delegation registers:

delegate_to_s = (cur_priv != M)
              && (!is_interrupt && medeleg[cause_idx])
              || (cur_priv != M)
              && ( is_interrupt && mideleg[cause_idx]);

When delegating, the chip writes sepc/scause/stval instead of the M-mode equivalents, captures cur_priv into mstatus.SPP, shifts SIE → SPIE, sets cur_priv = S, and jumps to stvec.

sret (0x10200073) is the dual of mret: restore cur_priv from SPP, restore SIE from SPIE, set SPP back to U (per-spec least-priv-supported on sret), pc ← sepc.

sstatus is now a real subset view of mstatus — the P47 standalone storage went away. Reads return mstatus & SSTATUS_MASK, writes only update those bits in mstatus. Single architectural register, two projections.

The probe

trap_delegation_probe walks the chip through the full priv crossing:

  1. Set medeleg[ECALL_FROM_S]=1, install probe-local M and S handlers in mtvec/stvec.
  2. mret with MPP=S to a label.
  3. ecall from S → delegated, S handler runs, captures scause/sstatus, sret keeps us in S.
  4. Clear medeleg from S, ecall again → not delegated, M handler runs, captures mcause/mstatus, mret back to M.

Verified end to end. PASS at 5,102,703 clocks.

Wart count

Zero new. The gcc-zbb-auto-emit-sext.b hang from P46 is still outstanding; march held at rv32ima_zba_zicsr_zifencei.

What’s still missing for Linux

Interrupt delegation (sip/sie need to be subset views of mip/mie and the timer/external fast paths need to look at cur_priv). M-only CSR privilege check on S-mode access. Then the big one: Sv32 paging behind satp.