ND Ethernet II Controller (PCB 3094) - Complete 68000 Firmware Reference¶
Image: encos-ser-all-banks-68k.bin (raw, MC68000 big-endian, base 0x000000, 512 KB)
Board: Norsk Data Ethernet II Controller, PCB 3094 (ndwiki 3094)
Firmware: ENCOS production Ethernet/XMSG server (PLANC-compiled, 1986)
Cross-reference: RetroCore C# host emulator NDBusEthernetII.cs; behavioral model ../protocode/
This document is intended to be complete enough that a developer could re-implement the controller firmware from it. Every claim is tagged:
- [C] CONFIRMED - proven in this image's disassembly (address cited) or in the authoritative RetroCore host emulator.
- [H] HYPOTHESIS - strongly indicated by the code but not fully traced.
- [U] UNCONFIRMED - from the task brief / external docs, or runtime-only (zero in the static image).
Nothing here is guessed; where a value is only known at runtime it is marked [U] and the reason stated.
1. Executive summary¶
The ND Ethernet II controller is an intelligent DMA Ethernet card built around an MC68000 with an AMD Am7990 LANCE and an MC68901 MFP. It has no EPROM: the ND-100 host loads all 512 KB of 68000 code and data into the card's DRAM, then releases the 68000 from reset. The card and the ND-100 share that DRAM (the ND-100 sees it through a bank window), and communicate through it using two doorbells:
- 68000 -> ND-100: write the SCIP register (
0xEF0080, mirror0xEF0180) -> the ND-100 takes interrupt level 12. [C] - ND-100 -> 68000: set a channel flag in
nd_channel_flags(0x0B56) and raise MFP GPIP6 -> the 68000 takes MFP vector 0x4E and dispatches the channel. [C]
Ethernet traffic flows through Am7990 descriptor rings in that shared DRAM. This document covers the boot flow, the MFP/RTC/LANCE hardware, the full receive and transmit paths (descriptor formats, DMA, two-stage MAC filtering), the XMSG message built for received frames, the ND-100 doorbell channels, the monitor/OPCOM debug channel, and a complete function and data index.
2. Image and load facts [C]¶
| Item | Value |
|---|---|
| Ghidra language | 68000:BE:32:default |
| Base / range | 0x000000 - 0x07FFFF (512 KB) |
| Reset SSP (vector 0) | 0x000005C8 |
| Reset PC (vector 1) | 0x00001CFE (reset_entry) |
| Named functions | 116 |
| PLANC module headers | * NCOM *, HDLC-DR, ,ASYN-DR, LOC-XMSG, * MAIN *, M-MANAG, PHLS-GEN, RT-CLOCK, SHORTLIB (dated Apr-Aug 1986) |
The card has no EPROM; the ND-100 loads the image and releases reset (host emulator
NDBusEthernetII.cs, and confirmed by the all-zero runtime structures in the static
image). The brief's bank-0 diagnostic anchors (0x25F0/0x2598/0x3338/0x4610/0x57F2 and
the 0x400/0x440/0x880 command mailbox) are from a different firmware and do not
apply here.
3. 68000 memory map [C, host emulator]¶
flowchart TB
subgraph A["68000 24-bit address space"]
DRAM["0x000000-0x07FFFF<br/>Local/shared DRAM 512KB<br/>vectors, code, data, LANCE rings+buffers"]
SPARE["0x080000-0xEEFFFF<br/>unmapped (EPROM option, never fitted)<br/>access -> bus error"]
IO["0xEF0000-0xEF01FF<br/>I/O space (EF00xx mirrored at EF01xx)"]
PROT["0xF00000-0xF7FFFF<br/>protection table (per-page write protect)"]
MIRROR["0xF80000-0xFFFFFF<br/>DRAM mirror = ND-100 shared window"]
end
| Range | Region | Notes |
|---|---|---|
| 0x000000-0x07FFFF | Local/shared DRAM | Vectors, code, data, LANCE rings and buffers |
| 0x080000-0xEEFFFF | Unmapped | EPROM option never fitted; access -> bus error |
| 0xEF0000-0xEF01FF | I/O space | EF00xx decoded again at EF01xx (PIOC compatibility) |
| 0xF00000-0xF7FFFF | Protection table | Per-page RAM write protection |
| 0xF80000-0xFFFFFF | DRAM mirror | Mirror of 0x000000-0x07FFFF; the ND-100 window |
4. I/O register map [C]¶
Named per the C# model constants (IoAddresses, FirmwareConstants.cs). Confirmed by
observed absolute-long accesses (addresses cited) and the host emulator.
| Address | Name | Dir | Purpose | Evidence |
|---|---|---|---|---|
| 0xEF0010 | Proff |
W | Protection-table bypass | host emu |
| 0xEF0020 | Modcr |
R/W | Mode control (EPROMMODE/PARITYDIS/BREAKMODE) | 0x1B0A writes 0 |
| 0xEF0040 | MerrStat |
R | Parity/memory error status | 0x1D84 clears it at boot |
| 0xEF0060 | Earen |
R | Memory-error address latch | host emu |
| 0xEF0080 | Scip |
W | Write -> INT12 to ND-100 | 0x1A5C, 0x224C, 0x249A |
| 0xEF0180 | ScipMirror |
W | Alternate SCIP doorbell | 0xECF2 (XMSG send) |
| 0xEF00A0 | LanceRdp |
R/W | LANCE register data port | 0x4AC8, 0x616E |
| 0xEF00A2 | LanceRap |
R/W | LANCE register address port | 0x4ABE, 0x4ADC |
| 0xEF00A8 | Xcvpw |
W | Transceiver 12V power | 0x47BA (#3 on / #0 off) |
| 0xEF00B0 | LanReset |
W | LANCE hardware reset | region present |
| 0xEF00B8 | EthStat |
R | HW status (bit2=power, bit0=LAN int) | host emu |
| 0xEF00C0 | MfpBase |
R/W | MC68901 MFP (odd displacements) | 0x396A programs it |
MFP register writes at boot (init_mfp_registers, 0x396A) [C] - base 0xEF00C0, odd displacements:
| MFP offset | Value | Meaning (MC68901) |
|---|---|---|
| 0x03 | 0x00 | GPIP data direction / AER area |
| 0x05 | 0x00 | interrupt enable A (cleared then set later) |
| 0x07 | 0xC0 | interrupt enable/mask |
| 0x09 | 0xA0 | interrupt mask |
| 0x13 | 0xC0 | timer control |
| 0x15 | 0x80 | timer data |
| 0x17 | 0x40 | Vector Register (VR) = 0x40 -> MFP vectors 0x40-0x4F |
| 0x1D | 0x50 | USART / timer |
| 0x23 | 0xF4 | USART control |
So VR = 0x40 is CONFIRMED (resolves the brief's assumption). MFP interrupt sources therefore vector to 0x40-0x4F (addresses 0x100-0x13F).
5. Named data globals [C unless noted]¶
Shared-DRAM globals renamed in Ghidra (hex kept for reference):
| Address | Name | Meaning | Conf |
|---|---|---|---|
| 0x0406 | monitor_req_status |
monitor request status word | C |
| 0x040A | monitor_counter |
monitor postbox event counter | C |
| 0x040C | monitor_code |
monitor sub-code | C |
| 0x040E | monitor_param |
monitor parameter | C |
| 0x0410 | monitor_counter2 |
monitor postbox second counter | C |
| 0x0412 | monitor_request_flag |
monitor request flag (set by nd_monitor_set_flag) | C |
| 0x0454 | cpu_register_dump_frame |
D0-D7/A0-A6/PC/USP/SR trap frame | C |
| 0x04BA | warm_boot_magic |
0x55555555 sentinel after a caught trap | C |
| 0x04BE | warm_boot_restart_count |
restart counter | C |
| 0x0500 | saved_stack_pointer |
scratch SP save | C |
| 0x0504 | monitor_delay_counter |
monitor wait/delay counter | C |
| 0x0534 | mfp_init_table_src |
8-byte source table copied to 0x5E8 | C |
| 0x05E8 | mfp_shadow_regs |
MFP register shadow / timer area | C |
| 0x0660/0x0662 | trap_hook_lock / _b |
TAS locks for the trap-frame hook | C |
| 0x0B56 | nd_channel_flags |
8 ND-100 doorbell channel flags | C |
| 0x0B66 | nd_channel_struct_table |
per-channel struct ptrs (runtime) | C |
| 0x0BE8 | nd_channel_context_table |
per-channel context ptrs (runtime) | C |
| 0x0FBE | rtc_isr_lock |
TAS lock in the RTC ISR | C |
| 0x0FC2 | rtc_tick_counter |
RTC tick counter | C |
| 0x0FCA | rtc_tick_counter_b |
second RTC tick counter | C |
| 0x0FD6 | timer_queue_head |
timer queue head pointer | C |
| 0x1292 | mfp_init_values |
14-word table for MFP/vector init | C |
| 0x18000 | rcv_ring_free_count |
RX ring free-buffer count | C |
| 0x18002 | rcv_ring_producer_index |
RX ring producer index (append) | C |
| 0x18004 | rcv_ring_consumer_index |
RX ring consumer index (complete) | C |
| 0x18008 | (RX descriptors) | 128 x 8-byte RMD (ends at 0x18408) | C |
| 0x18408 | xmt_ring_free_count |
TX ring free count | C |
| 0x1840A | xmt_ring_producer_index |
TX ring producer index | C |
| 0x18410 | (TX descriptors) | 128 x 8-byte TMD | C |
| 0x18810 | lance_init_block |
Am7990 init block (see 8) | C |
| 0x1885E | lance_mac_address |
6-byte MAC (PADR source) | C |
| 0x18886 | lan_function_code |
LAN function selector (==4 gates padding) | C |
| 0x18888 | lan_mode_flag |
LAN mode flag (gates INITLANCE MODE bit) | C |
| 0x1888A | lan_header_present_flag |
header-present flag (TX/RX framing) | C |
| 0x1888C | lan_stats_block |
LAN statistics/counters block | C |
| 0x188C6 | conn_state_block |
connection state block | H |
| 0x188DA | rx_buffer_pool |
pool the RX buffers are cut from | C |
| 0x1A2D2 | conn_list_head |
active connection list head | C |
| 0x1A2D8 | conn_id_counter |
connection id allocator counter | C |
| 0x1AA9E | active_conn_list |
active connection list | C |
| 0x1D0EA | conn_node_freelist |
connection-node free list | C |
| 0x1D16E | XROUT_dispatch_maxindex |
0x07 (8 XROUT handlers) | C |
| 0x1D170 | XROUT_dispatch_table |
8 x 32-bit XROUT handler pointers | C |
| 0x1D1D6 | conn_dispatch_maxindex |
0x09 (second dispatch table) | C |
| 0x1D1D8 | conn_dispatch_table |
connection dispatch pointer table | C |
| 0x1E21A | xmsg_node_id |
XMSG node id / magic (into messages) | C |
| 0x1E232 | conn_lookup_list |
connection lookup list | C |
| 0x2AB5E | route_log_table |
routing log / statistics table | H |
| 0x36368 | rx_buffer_count |
number of RX buffers to allocate | C |
6. Vector table and interrupt architecture [C]¶
Vectors 0-7 (the standard 68000 exceptions plus the card's autovector assignment) and the MFP vector block at 0x100-0x13F (VR=0x40).
| 68000 addr | Vector | Handler | Meaning |
|---|---|---|---|
| 0x000 | 0 SSP | 0x000005C8 | initial supervisor stack |
| 0x004 | 1 PC | 0x00001CFE | reset_entry |
| 0x008 | 2 | 0x0000211C | LANCE level-2 (PLANC trampoline -> 0x1E9A) |
| 0x00C | 3 | 0x00002136 | MFP level-3 (vectored via VR=0x40) |
| 0x078 | 30 (0x1E) | via opcom_handler_ptr (0x52A -> 0x1B00) |
ND-100 OPCOM level 6 |
| 0x138 | 0x4E | 0x0000250E nd_host_interrupt_handler |
ND-100 request (MFP GPIP6) |
| 0x13C | 0x4F | 0x00002142 | write violation (MFP GPIP7) |
68000 IPL autovector assignment (host emulator + ND-12.055.1):
| Level | Source | Vector type |
|---|---|---|
| 7 (NMI) | ND-100 power low | autovector |
| 6 | ND-100 OPCOM | autovector (0x1E) |
| 5 | Memory parity error | autovector |
| 4 | PTC test console | autovector |
| 3 | MFP (MC68901) | vectored (VR=0x40) |
| 2 | LANCE | autovector |
MFP vectored sources (level 3), VR=0x40 -> vector VR|source:
| MFP source | Vector | GPIP/function | Meaning |
|---|---|---|---|
| 15 | 0x4F | GPIP7 | write violation by 68000 |
| 14 | 0x4E | GPIP6 | ND-100 requesting interrupt |
| 12 | 0x4C | USART RX | receive buffer full |
| 11 | 0x4B | USART RX | receive error |
| 10 | 0x4A | USART TX | transmit buffer empty |
| 9 | 0x49 | USART TX | transmit error |
| 7 | 0x47 | GPIP5 | LANCE memory access error |
| 5 | 0x45 | Timer C | real-time clock |
flowchart TD
ND[ND-100 host]
ND -->|Control Word bit2| G6[MFP GPIP6 -> vec 0x4E]
ND -->|Control Word bit3| L6[68000 level 6 OPCOM vec 0x1E]
ND -->|Control Word bit6| L7[68000 level 7 power-low NMI]
LANERR[LANCE mem error] --> G5[MFP GPIP5 -> vec 0x47]
WV[write violation] --> G7[MFP GPIP7 -> vec 0x4F]
RTC[Timer C] --> TC[MFP vec 0x45 -> rtc_timer_isr]
G6 --> MFP[MFP level 3 vectored]
G5 --> MFP
G7 --> MFP
TC --> MFP
MFP --> CPU[68000]
LANCE[LANCE INTR] --> L2[68000 level 2 -> RINT/TINT]
L2 --> CPU
L6 --> CPU
L7 --> CPU
CPU -->|write SCIP EF0080/EF0180| SCIP[INT12 to ND-100]
SCIP --> ND
7. Boot / initialization flow [C]¶
reset_entry (0x1CFE) step by step:
flowchart TD
A["reset_entry 0x1CFE<br/>SSP=0x5C8"] --> B["stash A0 -> saved_stack_pointer (0x500)"]
B --> C["monitor postbox init:<br/>monitor_param=1, monitor_code=0, clear monitor_req_status"]
C --> D{"warm_boot_magic == 0x55555555?"}
D -->|yes| E["clear magic; warm_boot_restart_count++;<br/>report restart via nd_monitor_set_flag + post_and_signal_nd100_scip"]
D -->|no| F
E --> F["clear MerrStat (0xEF0040)"]
F --> G["install OPCOM handler ptr at vector 0x1E (addr 0x78)"]
G --> H["jsr maybe_init_ctrl_table_5e8 (0x1AD4)"]
H --> I["jsr init_mfp_registers (0x396A)<br/>program MFP base 0xEF00C0, VR=0x40"]
I --> J["jsr maybe_startup_check_406 (0x1C6A)"]
J --> K["arm warm_boot_magic=0x55555555; boot_stage_flag=1"]
K --> L["STOP #0x2500 (supervisor, IPL5)<br/>hand off to ND-100 / wait for interrupts"]
After boot the firmware is event/interrupt-driven (the PLANC POSI postbox scheduler:
POSIINITIALIZE 0x11732, POSISTART 0x1179C, POSIAPPEND 0x11DC4). Hardware bring-up
(transceiver power, LANCE ring setup, LAN init) runs through LNMAINIT_main (0x6ECE),
which chains its steps through the error-check trampoline check_status_or_hwerror
(0x6EBA) and ends by calling STARTMA (0x5850).
8. LANCE (Am7990) - initialization [C]¶
8.1 Init block (lance_init_block, 0x18810)¶
Built at runtime by INITLANCE (0x48EA); zero in the static image. Standard Am7990
layout:
| Offset | Field | Built by | Meaning |
|---|---|---|---|
| +0 | MODE | INITLANCE bit-by-bit | bit15 from lan_mode_flag; sets bit2; clears bits 6/5/4/3/1/0 |
| +2 | PADR (6 bytes) | copied from lance_mac_address (0x1885E) via PLANC_IMUL |
station MAC address |
| +8 | LADRF (8 bytes) | (runtime) | logical (multicast) address filter |
| +0x12 | RDRA | (runtime) | RX ring pointer + RLEN |
| +0x16 | TDRA | (runtime) | TX ring pointer + TLEN |
8.2 CSR programming sequence [C] (block at 0x4ABE inside/around INITLANCE)¶
Xcvpw (0xEF00A8) = 0x03 ; transceiver 12V power on (0x47BA)
RAP(0xEF00A2)=3 ; RDP(0xEF00A0)=0x0004 ; CSR3 = BSWP (byte swap for 68000 big-endian)
init block ptr = 0x18810
RAP=1 ; RDP = iadr low ; CSR1 = init block addr low
RAP=2 ; RDP = iadr high ; CSR2 = init block addr high
RAP=0 ; RDP = 0x0001 ; CSR0 = INIT (start initialization)
jsr LANCE_WAIT (0x4E42) ; wait for IDON
CSR0 bit reference (Am7990): ERR(15) BABL(14) CERR(13) MISS(12) MERR(11) RINT(10)
TINT(9) IDON(8) INTR(7) INEA(6) RXON(5) TXON(4) TDMD(3) STOP(2) STRT(1) INIT(0).
TX kick observed: CSR0 = 0x0048 = INEA|TDMD at 0x616E.
9. Receive path [C]¶
9.1 RX descriptor ring (RMD)¶
Ring header rcv_ring_free_count/_producer_index/_consumer_index at 0x18000; 128
descriptors of 8 bytes at 0x18008 (128*8 = 0x400, ending exactly at the TX ring
0x18408). Indices wrap mod 128. RMD layout (standard Am7990):
| Offset | Field | Meaning |
|---|---|---|
| +0 | RMD0 (word) | buffer address low 16 bits (LADR) |
| +2 | RMD1 (word) | high byte = flags (OWN15/ERR14/FRAM13/OFLO12/CRC11/BUFF10/STP9/ENP8), low byte = HADR |
| +3 | (byte) | HADR = buffer address bits 16-23 |
| +4 | RMD2 (word) | buffer length as two's complement (-BCNT) |
| +6 | RMD3 (word) | receive: MCNT (message length, 12 bits) + errors |
RX buffer size = 0x5F0 = 1520 bytes (max Ethernet frame), confirmed at
append_rx_buffers_to_ring (0x5BCA), cut from rx_buffer_pool (0x188DA/0x3636C),
count from rx_buffer_count (0x36368).
9.2 RCVRINGAPPEND (0x5B60) [C] - hand a buffer to the chip¶
idx = rcv_ring_producer_index ; (0x18002)
desc = 0x18008 + idx*8
RMD0 = buffer_low ; RMD1.HADR(+3) = buffer_high
RMD2 = -length
producer_index = (idx+1) mod 128 ; free_count--
RMD1.flags |= OWN(0x8000) ; hand buffer to LANCE
9.3 Frame arrival and RCVCOMPLETE (0x5C42) [C]¶
The chip DMAs a received frame into the current descriptor's buffer, sets MCNT in RMD3, clears OWN, sets STP/ENP (or error bits). Then RCVCOMPLETE runs (via the level-2 RINT event):
idx = rcv_ring_consumer_index ; (0x18004)
desc = 0x18008 + idx*8
if RMD1.OWN still set -> nothing to do
length = (RMD3 & 0xFFF) - 4 ; strip 4-byte FCS
buffer = HADR:RMD0
dest_mac = first 6 bytes of buffer
if dest group bit == 0 (unicast):
compare 6 bytes vs lance_mac_address (0x1885E) ; SOFTWARE address check (0x5D5E)
match -> "for us" flag (0x38) = 1
else:
maybe_handle_group_address (0x542C) ; multicast/broadcast
if for-us: deliver frame up to XMSG (XMRECEIVER)
advance consumer_index ; re-arm buffer (RCVRINGAPPEND)
sequenceDiagram
participant NET as Ethernet
participant LAN as LANCE Am7990
participant MEM as Shared DRAM
participant FW as 68000 firmware
participant ND as ND-100
NET->>LAN: frame
LAN->>LAN: hardware address filter (PADR / broadcast / LADRF)
LAN->>MEM: DMA frame into RMD buffer, set MCNT, clear OWN
LAN->>FW: level-2 RINT
FW->>MEM: RCVCOMPLETE read RMD (consumer idx)
FW->>FW: length=(MCNT&0xFFF)-4; read dest MAC
FW->>FW: software MAC recheck vs 0x1885E (0x5D5E)
FW->>FW: XMRECEIVER build XMSG msg
FW->>MEM: re-arm buffer (RCVRINGAPPEND)
FW->>ND: SCIP write -> INT12
Two-stage MAC filtering [C]:
flowchart TD
F[frame dest MAC] --> HW{LANCE hardware filter}
HW -->|PROM mode bit15| ACC1[accept]
HW -->|broadcast FF:FF:FF:FF:FF:FF| ACC1
HW -->|== PADR| ACC1
HW -->|multicast: CRC32 top6 bits -> LADRF bit set| ACC1
HW -->|else| DROP1[drop before DMA]
ACC1 --> DMA[DMA into ring]
DMA --> SW{firmware software recheck 0x5D5E}
SW -->|unicast == 0x1885E| DEL[deliver up + SCIP]
SW -->|group bit set| GRP[maybe_handle_group_address]
SW -->|else| DROP2[drop]
Multicast LADRF hash: CRC-32 (poly 0xEDB88320) of the 6 destination bytes, top 6 bits
(crc >> 26) index the 64-bit LADRF in the init block; accept if that bit is set.
(Standard Am7990; the firmware's multicast-add routine is not wired to a caller in the
static image, so hash orientation is standard-chip [H].)
10. Transmit path [C]¶
TX ring header xmt_ring_free_count/_producer_index at 0x18408; 8-byte TMDs at
0x18410. Same descriptor format as RX.
10.1 XMTRINGAPPEND (0x6054) [C]¶
compute TX buffer address + length from the outbound message
buffer[0..5] = dest MAC (from the message, msg+0x22)
buffer[6..11] = SOURCE MAC = copy of lance_mac_address (0x1885E) ; our address
buffer[12..] = ethertype + payload (already staged)
if lan_function_code==4 and length<0x3C: pad to 60 bytes
idx = xmt_ring_producer_index
desc = 0x18410 + idx*8
TMD0 = buffer_low ; TMD1.HADR = buffer_high ; TMD2 = -length
TMD1.flags |= STP(0x200)|ENP(0x100)
producer_index=(idx+1) mod 128 ; free_count--
TMD1.flags |= OWN(0x8000)
CSR0 (0xEF00A0) = 0x0048 ; INEA|TDMD -> chip transmits
10.2 XMTCOMPLETE (0x61D2) [C]¶
On TINT, walk the TX consumer index (0x1840C), for each descriptor the chip released (OWN=0), reclaim the buffer and advance the index.
sequenceDiagram
participant ND as ND-100
participant FW as 68000 firmware
participant MEM as Shared DRAM
participant LAN as LANCE
participant NET as Ethernet
ND->>FW: outbound message (doorbell channel)
FW->>MEM: XMTRINGAPPEND build header (src MAC=0x1885E) + TMD, set OWN
FW->>LAN: CSR0=0x0048 (INEA|TDMD)
LAN->>MEM: DMA read TMD buffer
LAN->>NET: transmit frame
LAN->>FW: level-2 TINT
FW->>MEM: XMTCOMPLETE reclaim descriptor
11. XMSG message for a received frame [C]¶
XMRECEIVER (0xBED8) packages a received frame into an XMSG message:
| Message offset | Value | Meaning |
|---|---|---|
| +0x14 | 0x00004000 (bit 14 set) | flags |
| +0x18 | *xmsg_node_id (0x1E21A) |
node id / magic |
| +0x1C | frame descriptor (12 bytes) | payload reference |
| +0x20 | 0 | reserved |
| +0x24 | 4 | subtype / count |
Then jsr XMPFRRE (0x10C4C), which posts the message through the postbox ring to the
ND-100 and rings SCIP. The on-wire XMSG framing the ND-100 decodes is the repo's
existing XMSG protocol (see xmsg-decode material); the header fields above are what
the firmware assembles.
12. ND-100 <-> 68000 doorbells [C]¶
12.1 68000 -> ND-100 (SCIP)¶
post_and_signal_nd100_scip (0x1A48): bump monitor_counter (0x40A) and
monitor_counter2 (0x410), then move.b #1,(0xEF0080) (SCIP) -> the card latches
"interrupt set for ND-100 on level 12". The XMSG postbox producer
maybe_xmsg_postbox_send_ring (0xEACC, inside PORTSEND) uses the mirror 0xEF0180.
Delivery honors the ND-100's interrupt-enable bit (latched if disabled; the ENNS0
startup-race latch documented in the host emulator).
12.2 ND-100 -> 68000 (channel doorbell)¶
nd_host_interrupt_handler (0x250E), wired to MFP GPIP6 (vector 0x4E):
after maybe_mfp_interrupt_ack (0x225C)
for ch in 7..0:
if nd_channel_flags[ch] (0x0B56 + ch*2) != 0:
clear it
schedule the channel's handler via PLANC scheduler (trap #2, D0=9),
using nd_channel_context_table (0xBE8) and nd_channel_struct_table (0xB66)
Eight channels; the ND-100 sets a channel flag then raises GPIP6. Handler tables are populated at runtime (zero in the static image). One channel carries "transmit this frame" and ends at XMTRINGAPPEND.
stateDiagram-v2
[*] --> Idle
Idle --> ChannelSet: ND-100 sets nd_channel_flags[ch]=1
ChannelSet --> Gpip6: ND-100 raises MFP GPIP6
Gpip6 --> Dispatch: 68000 nd_host_interrupt_handler scans flags
Dispatch --> Handler: clear flag, schedule channel handler (trap #2)
Handler --> Idle: handler runs (e.g. XMTRINGAPPEND)
13. Monitor / OPCOM / warm boot [C]¶
- Monitor postbox at 0x40A (
monitor_*): the reset/trap monitor path. On every trap,save_cpu_context_to_0x454writes a full register frame tocpu_register_dump_frame(0x454);nd_monitor_set_flag(0x1A30) setsmonitor_request_flag(0x412);post_and_signal_nd100_sciprings the ND-100. The counterpartrestore_cpu_context_and_rte(0x1A12) restores the frame and RTEs. - OPCOM level-6 autovector (vector 0x1E / addr 0x78), handler pointer
opcom_handler_ptr(0x52A -> 0x1B00) installed by reset entry. - Warm boot:
warm_boot_magic(0x4BA) = 0x55555555 marks a restart after a caught trap; reset entry detects it, bumpswarm_boot_restart_count(0x4BE), and reports it rather than looping. - PLANC runtime panics: strings
- STACK OVERFLOW AT,- ASSERT VIOLATION AT,- INDEX RANGE ERROR AT(0x44DE-0x451C).FATALERROR(0x4C26) andmaybe_plancruntime_panic(0x44B4) are the fatal paths.
14. RTC / timer [C]¶
rtc_timer_isr (0x3A68), wired to MFP Timer C (vector 0x45). Sets mfp_shadow_regs
(0x5E8)=1, increments rtc_tick_counter (0xFC2) and rtc_tick_counter_b (0xFCA),
walks timer_queue_head (0xFD6) firing entries whose expiry matches the tick, uses
rtc_isr_lock (0xFBE) as a TAS lock, then trap #2 + maybe_monitor_wait_ack and RTE.
15. XROUT / connection routing (message layer above Ethernet) [C name / H detail]¶
maybe_xrout_msg_dispatch(0x9924): extracts a 4-bit type from the message byte at offset 0x2C, validates againstXROUT_dispatch_maxindex(0x1D16E=7), jumps throughXROUT_dispatch_table(0x1D170), 8 handlers 0x99E2..0x9D8E.- handler 0 (0x99E2): set type 0x3000, allocate connection id (
maybe_alloc_connection_id). - handler 1 (0x9A56): find/remove connection by type.
- handlers 2-7: message-type transforms routing to
maybe_build_tx_descriptor(0x8C78),maybe_register_conn_range(0x96A6),maybe_free_conn_sublists(0x917C).
- handler 0 (0x99E2): set type 0x3000, allocate connection id (
- Second dispatch table
conn_dispatch_table(0x1D1D8, max 9). - Connection management: id allocator (
conn_id_counter0x1A2D8), list heads (conn_list_head0x1A2D2,active_conn_list0x1AA9E,conn_lookup_list0x1E232), free list (conn_node_freelist0x1D0EA). Routines:maybe_alloc_conn_node(0x8ED8),maybe_find_remove_conn(0x8F86),maybe_register_conn_range(0x96A6),maybe_sorted_list_insert(0xC5BC),maybe_free_conn_node_1d0ea(0x83BA).
16. Complete function index¶
Confidence: [C]=confirmed name (symbol table or direct evidence), [H]=hypothesis (behavioral name from analysis).
| Addr | Name | Role | Conf |
|---|---|---|---|
| 0x1A12 | restore_cpu_context_and_rte | restore trap frame + RTE | C |
| 0x1A30 | nd_monitor_set_flag | set monitor_request_flag | C |
| 0x1A48 | post_and_signal_nd100_scip | bump counters + SCIP (INT12) | C |
| 0x1A66 | save_cpu_context_to_0x454 | save trap register frame | C |
| 0x1ACA | maybe_monitor_wait_ack | monitor wait/ack loop | H |
| 0x1AD4 | maybe_init_ctrl_table_5e8 | early ctrl table init | H |
| 0x1C6A | maybe_startup_check_406 | startup check on monitor_req_status | H |
| 0x1CFE | reset_entry | reset entry / boot | C |
| 0x2192 | maybe_tas_lock_patch_return | TAS lock + trap-frame hook | H |
| 0x225C | maybe_mfp_interrupt_ack | MFP interrupt acknowledge | H |
| 0x250E | nd_host_interrupt_handler | ND-100 channel doorbell dispatch | C |
| 0x396A | init_mfp_registers | MFP init (VR=0x40) | C |
| 0x3A58 | maybe_set_timer_reload_fca | timer reload setter | H |
| 0x3A68 | rtc_timer_isr | RTC/timer ISR | C |
| 0x44B4 | maybe_plancruntime_panic | PLANC panic path | H |
| 0x4660 | calc_crc32 | CRC-32 (0x6DB88320) | C |
| 0x4754 | calc_crc32_setup | CRC-32 wrapper | C |
| 0x48B2 | maybe_lance_rdp_clear | LANCE RDP clear helper | H |
| 0x48EA | INITLANCE | LANCE init | C |
| 0x4BA0 | maybe_set_lance_active | set LANCE active flag | H |
| 0x4BD6 | report_via_pomnreport | generic event/error report | C |
| 0x4C26 | FATALERROR | fatal error handler | C |
| 0x4CC6 | maybe_report_event_IA | report event "IA" | H |
| 0x4D66 | ASRCONNECT | connect async-serial handler | C |
| 0x4E42 | LANCE_WAIT | wait for LANCE IDON | C |
| 0x4F52 | maybe_connect_evhandler_18828 | connect LAN event handler | H |
| 0x4FAA | posi_init_18834 | postbox init wrapper | C |
| 0x503A | posi_start_18834 | postbox start wrapper | C |
| 0x514A | posi_getall_wrapper | POSIGETALL wrapper | C |
| 0x518E | posi_return_wrapper | POSIRETURN wrapper | C |
| 0x5322 | maybe_init_buffer_pool_188da | init buffer pool/freelist | H |
| 0x542C | maybe_handle_group_address | multicast/group RX handling | H |
| 0x548E | clear_struct_1888c | clear stats block | C |
| 0x5512 | clear_struct_188c6 | clear conn state block | C |
| 0x553C | maybe_format_lance_descriptor | format an RMD/TMD | H |
| 0x561E | init_rcvring_descriptors | build RX ring descriptors | C |
| 0x5700 | init_rcvring_wrapper | RX ring init wrapper | C |
| 0x5850 | STARTMA | start LANCE/MAC | C |
| 0x5880 | STOPMA | stop LANCE/MAC | C |
| 0x58E0 | HARDWAREERROR | hardware error report | C |
| 0x5B60 | RCVRINGAPPEND | append RX buffer to ring | C |
| 0x5BCA | append_rx_buffers_to_ring | append N 1520-byte RX buffers | C |
| 0x5C42 | RCVCOMPLETE | receive-complete consumer | C |
| 0x6054 | XMTRINGAPPEND | build TX frame + descriptor + kick | C |
| 0x61D2 | XMTCOMPLETE | transmit-complete reclaim | C |
| 0x6DA8 | LNMAEVENTS | LAN management events | C |
| 0x6EBA | check_status_or_hwerror | error-check trampoline | C |
| 0x6ECE | LNMAINIT_main | master LAN init sequence | C |
| 0x704A | POMNERRHANDLER | postbox error handler | C |
| 0x803E | maybe_report_event_IE | report event "IE" | H |
| 0x80AA | maybe_report_event_ID | report event "ID" | H |
| 0x8314 | maybe_queue_append_1b22a | queue append | H |
| 0x83BA | maybe_free_conn_node_1d0ea | free conn node to freelist | H |
| 0x8AC8 | maybe_enqueue_by_channel | enqueue to postbox by channel | H |
| 0x8C78 | maybe_build_tx_descriptor_1a2b4 | build TX descriptor entry | H |
| 0x8CCA | maybe_finalize_tx_chain_1a2b8 | finalize TX chain | H |
| 0x8D90 | maybe_alloc_connection_id | allocate connection id | H |
| 0x8ED8 | maybe_alloc_conn_node | alloc + init conn node | H |
| 0x8F86 | maybe_find_remove_conn | find/remove conn by addr | H |
| 0x917C | maybe_free_conn_sublists | free conn sublists | H |
| 0x91A8 | maybe_free_descriptor_chain | free descriptor chain | H |
| 0x91D6 | maybe_remove_conn_wrapper | remove conn wrapper | H |
| 0x9282 | maybe_conn_range_contains | address-range membership test | H |
| 0x9526 | maybe_find_remove_conn_by_type | find/remove conn by type | H |
| 0x95D6 | maybe_lookup_conn_check_range | lookup conn + range check | H |
| 0x96A6 | maybe_register_conn_range | register conn address range | H |
| 0x9924 | maybe_xrout_msg_dispatch | XROUT message-type dispatcher | C |
| 0xB692 | maybe_report_event_ID_b692 | report event "ID" (2) | H |
| 0xBA56 | maybe_log_route_entry_2ab5e | log route/stats entry | H |
| 0xBCDE | COPY | block copy | C |
| 0xBED8 | XMRECEIVER | build XMSG msg for RX frame | C |
| 0xBFF8 | maybe_build_xrout_message | build XROUT message | H |
| 0xC47E | maybe_queue_append_28f32 | queue append | H |
| 0xC5BC | maybe_sorted_list_insert | sorted linked-list insert | H |
| 0xC822 | maybe_find_node_by_id_1e232 | linked-list lookup by id | H |
| 0xE6B0 | POWAITFORLAN | wait for LAN | C |
| 0xE73C | PORTCREATE | create XMSG port | C |
| 0xEAA6 | PORTSEND | send message to port (-> ND-100) | C |
| 0xEAB6 | maybe_status_trampoline_eab6 | status trampoline | H |
| 0xEACC | maybe_xmsg_postbox_send_ring | XMSG postbox producer + SCIP mirror | H |
| 0xED10 | PONAREGISTER | postbox name register | C |
| 0xF05A | LNNDTOMAAPPEND | ND-to-MA append | C |
| 0xF3E8 | maybe_print_statistics | format/print statistics | H |
| 0x106F0 | XMPSEND | XMSG send | C |
| 0x107CA | XMPFCLS | XMSG postbox file close | C |
| 0x10880 | XMPFREL | XMSG postbox file release | C |
| 0x10936 | XMPFREA | XMSG postbox file free | C |
| 0x10C4C | XMPFRRE | XMSG postbox file receive | C |
| 0x11502 | XMPBAST | XMSG buffer allocate/stack | C |
| 0x1164E | XMPXETS | XMSG X-ETS | C |
| 0x11732 | POSIINITIALIZE | postbox scheduler init | C |
| 0x1179C | POSISTART | postbox scheduler start | C |
| 0x1192A | POSPGETALL | postbox get-all (P) | C |
| 0x1199E | POSIGETALL | postbox get-all (I) | C |
| 0x11C66 | POSIRETURN | postbox return | C |
| 0x11DC4 | POSIAPPEND | postbox append | C |
| 0x11F78 | POMNREPORT | postbox management report | C |
| 0x12168 | POLKLOCK | postbox lock | C |
| 0x12212 | POLKUNLOCK | postbox unlock | C |
| 0x1222E | PIOCOS | connect handler / event OS | C |
| 0x12258 | PO32TOSTRING | 32-bit to string | C |
| 0x12644 | PLANC_UTBY | PLANC unpack byte | C |
| 0x12ED8 | SPASI_stackalloc | stack alloc | C |
| 0x12FA6 | PLANC_ERROR | PLANC error | C |
| 0x1302A | PLANC_GETNO | PLANC get number | C |
| 0x1309E | PLANC_OUTBYTE | PLANC output byte | C |
| 0x1310C | PLANC_IMOD | PLANC integer modulo | C |
| 0x13286 | PLANC_MOVE | PLANC block move | C |
| 0x133E6 | PLANC_IMUL | PLANC integer multiply | C |
| 0x1342C | PLANC_IDIV | PLANC integer divide | C |
| 0x134E6 | PLANC_APPD | PLANC list append | C |
| 0x13500 | PLANC_REMV | PLANC list remove | C |
| 0x135A8 | PLANC_XRET | PLANC routine return | C |
| 0x13748 | MON2_syscall | monitor syscall 2 | C |
| 0x1A268 | LNCNSPCOMMAND | LAN connection SP command | C |
| 0x2D350 | POCONFIGURE | postbox configure | C |
17. Data structure index¶
| Structure | Address / type | Producer | Consumer | Ownership |
|---|---|---|---|---|
| Monitor postbox | 0x40A | 68000 | ND-100 | request flag + counters |
| CPU register dump | 0x454 (60+ bytes) | 68000 trap | ND-100 (OPCOM) | - |
| ND channel flags | 0x0B56 (8 words) | ND-100 | 68000 | per-channel flag |
| RX ring | header 0x18000, 128x8B RMD at 0x18008 | firmware appends / LANCE fills | LANCE / firmware | RMD OWN bit |
| TX ring | header 0x18408, 128x8B TMD at 0x18410 | firmware | LANCE | TMD OWN bit |
| LANCE init block | 0x18810 (24 bytes) | INITLANCE (runtime) | LANCE | - |
| RX buffers | 1520 bytes, pool at 0x188DA/0x3636C | firmware | LANCE DMA | RMD OWN |
| XMSG postbox slot | 8 bytes (owner + 3 payload words) | 68000 | ND-100 | owner word |
| XMSG message | built by XMRECEIVER | 68000 | ND-100 | via postbox |
| XROUT dispatch table | 0x1D170 (8 ptrs) | static | maybe_xrout_msg_dispatch | - |
| Connection lists | 0x1A2D2 / 0x1AA9E / 0x1E232 / freelist 0x1D0EA | firmware | firmware | linked list |
18. Mapping to the C# behavioral model (../protocode/)¶
Every subsystem above is implemented and verified in the C# model:
| Firmware element | C# location |
|---|---|
| reset flow, MFP init, RTC | NDEthernetIIFirmware.cs (ResetEntry, InitializeMfp, OnRtcTick) |
| CRC-32 | NDEthernetIIFirmware.Crc32 |
| LANCE CSR/init, RX DMA, MAC filter, LADRF, TX | LanceControllerModel.cs |
| RX descriptor consume, software MAC recheck | NDEthernetIIFirmware.ProcessRxComplete |
| TX reclaim | NDEthernetIIFirmware.ProcessTxComplete |
| XMSG message | NDEthernetIIFirmware.PostReceivedFrameToHost, XmsgMessage |
| ND-100 doorbell channels | NDEthernetIIFirmware.OnNdHostInterrupt, NDEthernetIIController.SignalNdChannel |
| SCIP doorbell | InterruptController.WriteScip |
| descriptor/ring geometry, addresses | FirmwareConstants.cs (LanceRing, FirmwareDataAddresses, IoAddresses) |
The model compiles clean (net8.0, no LINQ, no external packages) and each path is exercised end-to-end.
19. Open questions / remaining [U]/[H]¶
- Who writes
lance_mac_address(0x1885E) - the ND-100/SINTRAN host during bring-up (block is zero statically). [H] - Exact runtime MODE value and ring lengths (RLEN/TLEN) in the init block. [U]
- The multicast-add routine that sets LADRF bits (not wired to a caller statically). [H]
- Full XMSG on-wire framing the ND-100 decodes (documented in the repo's XMSG notes).
- XROUT handlers 2-7 exact message-type semantics (routing state machine). [H]
- Which ND-100 doorbell channel index carries which function (tables runtime-populated). [H]
- The startup memory-probe loop (behaviour confirmed host-side; loop not traced). [U]
20. Explicit assumptions¶
- The PLANC symbol-table record layout is
[code-addr:32][zero:32][name], verified by multiple exact matches to auto-analyzed functions (0x4C26, 0x5B60, 0x11732, 0x1179C, 0x11DC4, 0x704A, 0xBCDE, 0x133E6, 0x13748). - I/O semantics (SCIP=INT12, MFP GPIP6=ND request, LANCE RAP/RDP, VR=0x40) are confirmed from the disassembly and/or the authoritative RetroCore host emulator.
- Multicast LADRF uses the standard Am7990 hash (documented chip behaviour), since the firmware's own multicast-add path is not statically wired.