Skip to content

PIOC-OS - Interrupts, faults and error handling

Image: encos-ser-all-banks-68k.bin Date: 2026-07-26 Status: interrupt map essentially complete. Error subsystem PARTIAL.


1. The 68000 vector table (0x000-0x3FF)

Fully populated in the image - it is a real static table, not built at run time. (PIOC-OS trap 0x0F can also patch it at run time, so a live card may differ.)

Reading it first is the fastest orientation available on an unfamiliar ND firmware image: it gives you the entry point and every interrupt handler before you disassemble anything. The M68kVectorTable.java script types all 256 entries as pointers, which creates the references that make otherwise-unreferenced handlers visible.

The handlers that matter

Vector Offset Value What
0 0x000 0x05C8 initial SSP (a value, not a handler)
1 0x004 0x1CFE RESET PC - the firmware entry point
25-31 0x064-0x07C autovectors; IRQ6 = 0x1B00, IRQ7 = 0x1DD8 are real handlers
32 0x080 0x3498 TRAP #0 - same handler as TRAP #2
34 0x088 0x3498 TRAP #2 - the PIOC-OS kernel entry
69 0x114 0x3A68 RTC / MFP timer ISR
78 0x138 0x250E ND-100 host doorbell

Vector 78 is re-hooked at run time

POMNPROCES saves the static handler to 0x199E8, reads a new pointer from 0x199EC and writes it to 0x138. The installed handler at 0x7726 is hand-written, not PLANC:

movem.l {D0-D5/A0-A4},-(SP)
... walks an 8-slot ring anchored at (0x18A38).l, issuing trap 0x09 per armed slot ...
movem.l (SP)+,{...}
move.l (0x199E8).l,-(SP)
rts                              <- chains to the PREVIOUS handler

The push saved vector ; rts idiom is a tail-chain, not a return. An emulator must preserve the chaining or the original 0x250E handler never runs.

All processor faults funnel into TRAP #1

The 2-byte stubs at 0x1F24+ that vectors 4-11 point at are each 4E 41 = trap #1. So the TRAP #1 handler (vector 33) is the fault reporter, and the stub ADDRESS is what identifies which fault occurred - the stubs are not distinct routines and should not be named individually.

Most other vectors point into dense stub runs at 0x1F24-0x1F7A, 0x1F9C-0x211A and 0x4074-0x409A. The ones at 0x4074+ are rte - do-nothing handlers.


2. Installing a vector at run time - trap 0x0F

PosTrap0FInstallExceptionVector @0x3396:

vec = arg[0]                                 word
if (vec < 8 || vec > 255) return error       validated 8..255
*(long *)(0 + vec*4) = arg.handler
return 1

Vectors 0-7 cannot be installed this way. This is the mechanism by which vector 78 gets hooked.


3. The host doorbell, both directions

Direction Mechanism
Card -> ND-100 write 1 to 0x00EF0080 (SCIP). post_and_signal_nd100_scip @0x1A5C does this after bumping the postbox counters at 0x040A and 0x0410. Trap 0x19 also uses it
ND-100 -> card vector 78, handler chain above

The monitor request path (04-SCHEDULER.md section 8) dispatches on SUBFN at 0x0408, bounds 0..5, replying at 0x040C - 1 on success, -2 for an out-of-range SUBFN, 4 after a warm restart.


4. Error handling

Routine Address
POMNERHAND 0x704A
SENDERROR 0x70CC
GETALLERRO 0x72CA
ANALYZEEVE 0x73A0
CONNECTERR 0x7412
INITERRORS 0x7528
FATALERROR 0x4C26
LnmaReportPosiErrorAndFatal 0x4D1A
HARDWAREERROR 0x58E0
WATCHDOGDE 0x5AC6

SENDERROR, GETALLERRO and INITERRORS all reference the bank-3 message tables via lea (0x6xxxx).l - the error TEXT lives in bank 3's data segment even though bank 3's code is unreachable (01-MODULE-INVENTORY.md section 4).

Hardware error codes

LNMAHWERRO carries a negative code; three are distinguished by dedicated counters:

Code Counter Behaviour
-1 0x188BA
-9 0x188C0 escalates to FATALERROR
-10 0x188C2 does not restart the MA
-5 (watchdog) 0x188C4

The watchdog is a two-variable no-progress detector - ring position and TX wrap count, snapshotted at 0x18948/0x1894A. So LNMAHWERRO = -5 means specifically "the transmitter made no progress", not a generic hardware fault.

The PLANC error path

Distinct from all of the above, and easy to confuse with it: jmp (A5) is the PLANC error unwind, where A5 holds #XRET (0x135A8) or #ERET (0x13596). There are 851 of them in the image and none is a dispatch. #PRERR raises a runtime error with a message table. See the ghidra-planc skill.


5. Three dispatch defects worth knowing

Where Defect
tbl_xroutSubfunctionDispatch @0x1D190 4-bit index (0..15), only 9 entries. Indices 9-15 read the next table's maxindex word and jump to 0x07000000. Peer-supplied, unchecked
tbl_pocsInboundStateDispatch @0x2D31C 3-bit index (0..7), only 5 entries. Indices 5-7 read the ASCII "POCS" that follows and jump to 0x504F4353. Peer-supplied, unchecked
tbl_lnPrintMasNetTypeDispatch @0x65FE0 8 entries, index correctly masked - but entries 5-7 are NULL and reachable, because the guard constrains bits 5..12 while the index uses bits 13..15. Result: jmp to 0x00000000

The third is a different failure mode from the first two: a bounds mask does not make a dispatch safe if the table itself has holes.

The host command dispatch (g_hostCommandJumpTable @0x18982) and the monitor SUBFN dispatch (0x0512) are both properly checked and are not in this list.


6. Open

  • The TRAP #1 fault reporter body - what it does with the stub address
  • The POMNERHAND / ANALYZEEVE logic and the full error code list
  • The 8-slot ring at (0x18A38).l walked by the vector-78 handler
  • What IRQ6 (0x1B00) and IRQ7 (0x1DD8) actually service

Provenance

The vector map, the trap #1 funnel and the three dispatch defects were established by reading the image. The error-routine inventory is from the vendor symbol table; their behaviour beyond the hardware error codes is not yet traced.