PIOC-OS - the ENCOS Ethernet controller RTOS¶
Image: encos-ser-all-banks-68k.bin (MC68000, PLANC-MC compiled, ND 1986)
Date: 2026-07-26
PIOC-OS is the small multitasking kernel running on ND's ENCOS Ethernet II controller card. It is priority-preemptive, built from nine linked modules, and talks to the ND-100 host through a shared DRAM comm block and two doorbells.
The documents¶
| # | Document | Covers |
|---|---|---|
| 01 | Module inventory | the nine modules, build dates, symbol-to-module map |
| 02 | Boot and init | reset entry, warm-restart path, board config, PRKEY gate |
| 03 | Process model | object table, descriptor map, flags, the three processes |
| 04 | Scheduler | preemption mechanism, ready queues, context switching |
| 05 | Kernel API | all 27 trap #2 services |
| 06 | IPC and messaging | events, ports, sub-process queues, XMSG gateway |
| 07 | Time and timers | MFP tick, timer elements, arm and cancel |
| 08 | Memory | heap, PLANC frame arena, fixed pools |
| 09 | Interrupts and errors | vector table, doorbells, error subsystem, dispatch defects |
Related, outside this folder:
..\ENCOS-FIRMWARE-SYMBOL-TABLE-2026-07-26.md- ND's own 241 linker symbolsE:\Dev\Repos\Ronny\RetroCore\DOCS\ND_EthernetII_MASTER_REFERENCE_2026-07-23.md- the host-side view: comm block, registers, boot/download flowE:\Dev\Repos\Ronny\RetroCore\DOCS\ND_EthernetII_HLE_SEAM_CONTRACT_2026-07-26.md- what an HLE must reproduce- The
ghidra-plancskill - PLANC-MC conventions, without which this firmware is easy to mis-read
The layers¶
graph TD
HOST["ND-100 host"] -->|"vector 78 doorbell"| KERNEL
KERNEL -->|"SCIP 0xEF0080"| HOST
HOST -->|"SUBFN 0x408"| MON["Monitor request dispatch<br/>table 0x0512"]
subgraph CARD["ENCOS card"]
KERNEL["PIOC-OS kernel<br/>trap #2 @0x3498<br/>scheduler @0x2C90"]
PROC["3 processes<br/>FREE p5 · PRO1 p1 · RTC p14"]
SUBP["Sub-process queues<br/>LNMASPCOMM · LNMASPDATA<br/>LNCNSPCOMM · POCSSPCOMM"]
SVC["Services<br/>LNMA · LNCN · POCS · XMSG"]
HW["LANCE · MFP timer"]
end
KERNEL --- PROC
PROC --- SUBP
SUBP --- SVC
SVC --- HW
HW -->|"interrupts"| KERNEL
style HOST fill:#2196F3,color:#fff
style KERNEL fill:#9C27B0,color:#fff
style MON fill:#9C27B0,color:#fff
style PROC fill:#3F51B5,color:#fff
style SUBP fill:#009688,color:#fff
style SVC fill:#4CAF50,color:#fff
style HW fill:#FFA726,color:#000
Process states¶
stateDiagram-v2
[*] --> Created: trap 0x03 create<br/>flags bit 0
Created --> Ready: trap 0x04 start<br/>bits 2+4, queued at 0x0B06
Ready --> Running: scheduler 0x2C90<br/>highest priority wins
Running --> Ready: preempted<br/>higher priority became runnable
Running --> Blocked: trap 0x0A/0x0B wait<br/>trap 0x19 host request<br/>bit 1 set
Blocked --> Ready: event post 0x2562<br/>clears bit 1
Running --> [*]: trap 0x1A terminate
Created --> [*]: trap 0x05 delete<br/>frees the 0x0A8A slot
The one mechanism to understand first¶
Deferred preemption by return-address hijack. When an event post makes a higher-priority
process runnable, 0x2192 does not switch. It saves the interrupted PC and SR into the current
descriptor, then overwrites the exception frame's return PC with the scheduler address so the
rte lands in 0x2C90 instead of resuming. A double latch (0x0660 and 0x0662) stops a
second event post rewriting the frame again and destroying the saved PC.
Everything else - the priority scan, the descriptor context area, the wait/post pair - hangs off that.
Address cross-reference¶
| Address | Meaning | Doc |
|---|---|---|
| 0x0000-0x03FF | 68000 vector table | 09 |
| 0x0406 / 0x0408 / 0x040C | host REQUEST / SUBFN / reply | 02, 04, 09 |
| 0x0454-0x049D | whole-machine context save frame | 02 |
| 0x04BA / 0x04BE | warm-restart signature / restart counter | 02 |
| 0x04C2 / 0x04C6 | host-request queues (trap 0x19) | 05 |
| 0x04CA | board configuration record | 02 |
| 0x0500 | saved A0 from reset entry | 02 |
| 0x0512 | monitor SUBFN dispatch (misnamed "scheduler action") | 04 |
| 0x05C8 | initial SSP and module directory head | 01, 02 |
| 0x0650 | current process descriptor | 04 |
| 0x0658 | interrupted exception frame pointer | 04 |
| 0x0660 / 0x0662 | reschedule pending / rewrite latch | 04 |
| 0x0A8A | object table, 30 slots | 03 |
| 0x0B06 | ready-queue heads, 16 priorities | 04 |
| 0x0B66 / 0x0B96 / 0x0BE8 | slot ownership tables | 05 |
| 0x0C6A | trap #2 dispatch, 27 entries | 05 |
| 0x0D00-0x0D0A | heap control words | 08 |
| 0x0FC0 / 0x0FC2 / 0x0FCA / 0x0FD6 | timer count / ticks / list head | 07 |
| 0x0FCE / 0x0FD2 | the two timer lists | 07 |
| 0x1300-0x1A00 | heap | 08 |
| 0x2192 / 0x2C90 | preemption trigger / scheduler | 04 |
| 0x2562 | event poster | 03, 06 |
| 0x3498 | trap #2 kernel entry | 05 |
| 0x18882-0x188C4 | mode words and statistics block | seam contract |
| 0x18982 | host command jump table | seam contract |
| 0x2D354 / 0x2D472 / 0x2D4F4 | name registry / port pools | 06 |
| 0x00EF0080 | SCIP doorbell to the host | 09 |
| 0x00EF00D5 | MFP timer enable | 07 |
STATICALLY UNRESOLVABLE - needs a live card or an emulator run¶
The ROM contains the code that builds these, not the structures themselves. Do not infer their contents from the image.
| What | Why |
|---|---|
Descriptor size (DAT_00000664) |
zero in the image, computed at boot. Fields reach +0x98, so >= 0x9C |
| Object table contents (0x0A8A) | all zeros; populated at run time |
| Ready-queue heads (0x0B06) | all zeros |
| Heap free list | built at boot from the board config DRAM size |
| Port pools and name registry | the 0xAAAA signature is written at init, not in the image |
| Actual restart count (0x04BE) | runtime state |
OPEN QUESTIONS, ranked¶
- ~~Which routine writes PRKEY?~~ ANSWERED 2026-07-27.
PublishDatafieldTableAndPrkey@0x1C6A, instruction at 0x1CF4, called from 0x1DA6 on the cold path - and a full emulator trace confirms it runs and writes PRKEY, STARTED and reply 3 correctly. Finding F10 does not hold for this image under emulation. See 02 section 6. - The init call order from
reset_entryonward (02 section 7). - Message buffer format and
PORTSEND/PORTRECEIVbodies (06 section 5). 0x3E00, the trap 0x18 timer arm path; and what timer modes 1 and 3 select (07 section 6).- The scheduler dispatch tail past the priority scan, and the queue link offset (04 section 7).
- What distinguishes flags bit 3 from bit 5 - the event poster ignores both (03 section 4).
- The TRAP #1 fault reporter body (09 section 6).
- The
DATASERVICdispatch at 0x6AC6, still unrecovered.
Corrections this documentation makes to earlier analysis¶
| Was | Is |
|---|---|
| trap 0x19 "unimplemented, raises #PRERR 0x16" | suspend/block on a host request - 0x16 was an inline frame descriptor |
tbl_piocOsSchedulerActionDispatch @0x0512 |
the monitor SUBFN dispatch, nothing to do with scheduling |
trap_hook_ctx_ptr / trap_hook_lock |
current process descriptor / reschedule-pending flag |
| frame +0x04 "unused" (ND-820026.1) or "frameLimit" | the outgoing-argument frame pointer |
| first parameter at +0x14 | +0x12; ERRCODE is 16-bit |
| trap 0x0D "wait / await event" | timer work - unresolved, do not treat either label as settled |
| bank 3 "never disassembled" | fully carved, Ethernet diagnostics, and unreachable in this build |
| F10 "the firmware never posts PRKEY" | does not hold - the writer at 0x1CF4 runs correctly under emulation; the ENNS0 symptom was command ordering |
Provenance¶
Every document states what was read from the image versus what is carried forward or inferred. Where the ND manuals disagree with the image - and they do, in at least two places - the image wins, because it is what runs.