The kernel¶
RetailOS runs on RTXC, a commercial real-time kernel. Chikuma's kernel is not a stub standing in for it and not a rewrite inspired by it: it is the recovered design, with the firmware's own task ids, priorities and names, covered by host tests.
The service ABI, which is not what you would guess¶
Kernel services are not reached through SWI. r0 holds a frame, and the request code sits at
frame + 4. That was established against the shipped entry and exit sequences instruction by
instruction -- 15 of 15 match.
All 49 dispatch slots have been read and classified into about twenty operations, each handler named in the Ghidra project with its evidence attached.
Three of them create a task, in order: 0x17 allocates, 0x16 defines, 0x15 starts. The frame
formula and the mode word were verified against every live 0x15 call on a boot -- 34 for 34.
The task record¶
0x34 bytes, every field attributed. The check that it was right is the kind this project prefers
to a plausible-looking field table: 34 of 34 records carry their own id, and 34 of 34 frames sit
inside their own task's stack. A wrong base makes both of those fail immediately, which is how an
earlier, confident, wrong table was caught.
The state bitfield has every bit attributed, and the mask 0xaa (the ordered waits) was confirmed
from two independent directions.
The tasks¶
34 of them, 32 named from the firmware's own strings -- and two independent naming mechanisms in the image agree on all 20 they share, which is what makes the names recovered rather than guessed.
Some that matter:
| Task | Id | Priority |
|---|---|---|
| EventManager | 5 | |
| WindowManager | 8 | 52 |
| ATAWorkLoopTask | 0x20 | 0x33 |
| ATAWorkLoopIRQTask | 0x21 | 9 |
plus TouchwheelTask, PCFPowerMgr, HoldSwitch, the five USB tasks, and the rest. The table lives
in kernel/config/tasks.h and tasks/.
That ATA pair has a consequence worth stating early: the filesystem does not call the disk. It posts to a queue and blocks. A stack scan at the ATA command write can never name the caller, because the caller is in another task by construction.
Timers¶
One list, with a kind tag driving expiry -- a unified model rather than several parallel ones. The gate is that the expiry switch matches the arming side, kind for kind.
Timer and event blocks come from a pool whose free head lives in task state at +0x14. One service
carves a task timer's block out of the task's own stack, at frame - 0x20, and so allocates nothing
at all.
Scheduling¶
rtxc_sched_after_service is recovered in all four phases, and the gate is symmetric: its exit
condition matches the service epilogue's, arrived at from the opposite side.
Preemption is real, and the scheduler sat in the tree as a labelled scaffold for weeks before the real one was read. That label is the reason it was never mistaken for the recovered article -- which is the whole argument for labelling stand-ins.
The gates¶
The kernel's milestones were made visible on purpose, because a milestone without a gate cannot be told from a milestone you think you reached:
- the kernel links into
payload.elfand starts a task → 76,800 pixels, a full frame of colour bars; - cooperative switching through the recovered entry → 497,649 pixels, 6.5 frames, three tasks interleaving.
Interrupts, and why the emulator changed¶
Interrupt timing is not a detail here. The Unicorn-based emulator delivered interrupts only at slice boundaries; QEMU raises the line the moment a device asserts it. An interrupt arriving in the middle of a kernel service faulted on every QEMU boot and passed the old gate every time. See In the emulator.