PIOC-OS - Process and object model¶
Image: encos-ser-all-banks-68k.bin
Date: 2026-07-26
Status: descriptor map complete for every field the kernel touches. Total descriptor size is
NOT statically recoverable - see section 6.
1. Two distinct layers - do not conflate them¶
| Layer | Created by | Appears in the object table? |
|---|---|---|
| Processes (schedulable, own context and priority) | PosCreateObject trap 0x03 |
Yes, at 0x0A8A |
Sub-processes (LNMASPCOMM, LNMASPDATA, LNCNSPCOMM, POCSSPCOMM) |
POSIINITIALIZE / POSISTART |
No |
There are exactly three processes. Everything else that looks like a task is a sub-process: a
message-queue endpoint serviced by one of those three, not a scheduled entity with its own stack
and priority. CMDSERVICE pulling work off LNMASPCOMM is a loop inside a process, not a context
switch.
2. The object table - POS_OBJECT_TABLE @ 0x0A8A¶
- 30 longword slots, 1-based: handle 1 is slot[0], so the address of a slot is
0x0A8A + (handle-1)*4... but the kernel indexes it directly as0x0A8A + handle*4(see trap 0x05 and 0x2562, both of whichasl.w #2the raw handle). Treat the table as 31 entries with slot 0 unused; handle bounds are checked as0 <= h <= 0x1E. - Each non-zero slot points at a heap-allocated descriptor record, not a function.
- A free slot is zero. Trap 0x05 (delete) clears the slot.
Enumerating every process the firmware created, from a live dump:
for (h = 0; h <= 30; h++) {
desc = *(uint32 *)(0x0A8A + h*4);
if (!desc) continue;
name = (char *)(desc + 0x18); /* 4 chars, e.g. "RTC " */
handle = *(uint16 *)(desc + 0x0E);
flags = *(uint16 *)(desc + 0x16);
priority = *(uint8 *)(desc + 0x1C); /* 1..15 */
entry = *(uint32 *)(desc + 0x1E);
}
3. The descriptor field map¶
Every offset below is used by code that has been read.
| Offset | Size | Field | Established from |
|---|---|---|---|
| +0x0E | 2 | handle (== its table index) | trap 0x11 |
| +0x16 | 2 | flags - see section 4 | traps 0x0A/0x0B/0x19, poster 0x2562 |
| +0x18 | 4 | name, 4 chars | trap 0x08, PosCreateObjectBody |
| +0x1C | 1 | priority 1..15 | creation, scheduler |
| +0x1E | 4 | entry point | RtcInit parameter block, creation |
| +0x30 | 32 | saved D0-D7 | scheduler 0x2C90, trap #2 entry |
| +0x50 | 28 | saved A0-A6 | scheduler (the A0 patch proves the split) |
| +0x6C | 4 | saved USP | scheduler |
| +0x70 | 4 | saved PC (seeded from +0x1E at start) | scheduler, 0x2192 |
| +0x74 | 2 | saved SR | 0x2192 |
| +0x76 | 4 | pending events (bit set) | traps 0x0A/0x0B/0x0C, poster 0x2562 |
| +0x7A | 4 | wait mask | traps 0x0A/0x0B |
| +0x00 | 4 | link - used as a queue link by trap 0x19's 0x04C2 list | 0x21D6 |
| +0x10 | 4 | second link - the 0x04C6 list | 0x21D6 |
Fields are written up to at least +0x98, so the record is at least 0x9C bytes.
4. The flags word (+0x16)¶
| Bit | Meaning | Set by | Cleared by |
|---|---|---|---|
| 0 | created | PosCreateObjectBody |
|
| 1 | blocked | waits (0x2BD8/0x2C34), trap 0x19 | the event poster 0x2562 - the only bit it touches |
| 2 | runnable / queued | PosStartProcess |
|
| 3 | waiting, trap 0x0A flavour | 0x2BD8 | |
| 4 | started | PosStartProcess |
|
| 5 | waiting, trap 0x0B flavour | 0x2C34 |
Bits 3 and 5 are markers, not semantics. 0x2BD8 and 0x2C34 are byte-identical apart from
which of the two they set, and the poster ignores both. Whatever reads them is elsewhere; do not
assume traps 0x0A and 0x0B wake differently.
5. The three processes¶
PosCreateObject (0x2E98) has exactly three call sites, all at boot:
| Handle | Name | Name at | Priority | Attr bit 15 | Entry | Role |
|---|---|---|---|---|---|---|
| 1 | FREE |
0x0C62 | 5 | no | 0x1A96 | idle - what runs when no ready queue is populated |
| 2 | PRO1 |
0x0C66 | 1 | no | 0x7E0E | PIUSERMAIN / AUTO_START - the application |
| 3 | RTC |
0x12B0 | 14 | yes | 0x3BFE | real-time clock service, highest priority |
FREE and PRO1 share one 8-byte literal "FREEPRO1" at 0x0C62, sliced into two 4-char names.
"RTC " sits immediately before the RT-CLOCK module directory record at 0x12B4.
The priorities are the whole scheduling story: RTC (14) preempts everything, the application (1) runs whenever nothing else needs the CPU, and FREE (5) sits between them purely so the priority scan never falls off the bottom.
6. What is NOT statically recoverable¶
The ROM image contains the code that builds these structures, not the structures themselves:
DAT_00000664, the descriptor size, is zero in the image. It is computed at boot, so the real record length cannot be read statically. We know only that fields extend to +0x98.- The object table at 0x0A8A is all zeros in the image - it is populated at run time.
- The ready-queue heads at 0x0B06 are zero, likewise.
Anything in this document about contents (as opposed to layout) therefore comes from reading the
code that writes them. To confirm actual values, dump a booted card or an emulator run and apply
/PIOCOS/PiocOsObject to a descriptor.
Provenance¶
The descriptor field map, the flags bits, and the two-layer distinction were read from the image. The three-process table was established earlier and re-confirmed against the creation call sites. Section 6 lists exactly what static analysis cannot answer, rather than guessing at it.