diff --git a/harness/mame/NETWORK_REPORT.md b/harness/mame/NETWORK_REPORT.md index 2dd7333..41830db 100644 --- a/harness/mame/NETWORK_REPORT.md +++ b/harness/mame/NETWORK_REPORT.md @@ -3649,3 +3649,76 @@ only explanation that fits all the evidence: the descriptor length fields must b the measured state (`TDMD` never cleared, no `TINT`, no `RINT`, `ERR` clear). `lance_fixlen.coff` remains the payload to run on the machine. + +## 53. Mapping the report's functions to 4.4BSD source + +Every function reversed above has a counterpart in +`/home/arti/code/original-bsd` (CSRG, 4.4BSD-Lite2). Mapping ROM address → +source function, with the construct that identifies each pair. + +### 53.1 Protocol stack + +| ROM | BSD source | evidence | +|---|---|---| +| `0x3F624980` IP task | `ipintr()` `sys/netinet/ip_input.c:115` | takes the packet from a queue, then dispatches (below) | +| `0x3F6270F0` protocol dispatch | `ip_input.c:366` `(*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen);` | **both tables found in RAM**: `0x1E0F70` = `ip_protox[256]` (protocol→index), `0xD6BE0` = `inetsw[]` | +| — struct layout | `struct protosw` `sys/sys/protosw.h:32` | `pr_protocol` at byte 6 and `pr_input` at byte 10 — exactly the offsets in our `0xD6BE0` dump (section 23.2) | +| `0x3F61FFA0` ICMP input | `icmp_input()` `sys/netinet/ip_icmp.c:148` | checksum verify over the ICMP header, then the type switch | +| `0x3F6224E0` type dispatch | the `switch (icm->icmp_type)` in `icmp_input()` | same case set (section 46, 51.2) | +| `0x3F622A40` reflect | `icmp_reflect()` `ip_icmp.c:386` | swaps source/destination, adjusts the total length, recomputes the checksum | +| `0x3F623350` ICMP send | `icmp_send()` `ip_icmp.c:510` | final transmit of the reply | +| `0x3F62EA00` IP transmit | `ip_output()` `sys/netinet/ip_output.c:43` | htons of total length and fragment word, header checksum at byte 10, then the interface's output | +| `0x3F63A340` TCP input | `tcp_input()` `sys/netinet/tcp_input.c:189` | states 3..10 tables (section 24) are `tcp_input`'s `switch (tp->t_state)` | +| TCP options | `tcp_dooptions()` `tcp_input.c:1309` | — | +| MSS handling | `tcp_mss()` `tcp_input.c:1501` | — | +| `0x3F6540C0` UDP input | `udp_input()` `sys/netinet/udp_usrreq.c:55` | header length checks, then the PCB lookup below | +| `0x3F656200` UDP header builder | `udp_output()` `udp_usrreq.c:351` | writes protocol 17 into the header (section 20) | +| `0x3F61E490` binding lookup | **`in_pcblookup()` `sys/netinet/in_pcb.c:428`** | exact match of the algorithm — see 53.2 | +| `0x3F6148E0`, `0x3F614200` ARP cache | `arplookup()` `if_ether.c:500`, `arpresolve()` `:261` | 19-bucket hash by IP, 9 entries (section 26) | +| `0x3F6119A0` ARP responder | **`in_arpinput()` `sys/netinet/if_ether.c:375`** | the "ether address is broadcast" diagnostic lives in it (section 51.1) | +| `0x3F60F260` ARP request | `arprequest()` `if_ether.c:216`, `arpwhohas()` `:202` | builds a 28-byte ARP payload | + +### 53.2 `in_pcblookup` — line-for-line + +``` +in_pcb.c:434 int matchwild = 3, wildcard; <-- our `movk 3,a11` +in_pcb.c:437 if (inp->inp_lport != lport) continue; <-- entry+0xB0 vs arg1 +in_pcb.c:439 if (inp->inp_laddr.s_addr != INADDR_ANY) <-- entry+0x90 vs arg2 +in_pcb.c:452 if (inp->inp_faddr.s_addr != INADDR_ANY || + inp->inp_fport != fport) continue; <-- entry+0x60 / +0x80 +in_pcb.c:462 if (wildcard < matchwild) { match = inp; matchwild = wildcard; ... } +``` + +which is exactly the loop at `0x3F61E570`..`0x3F61EA20`, including the +`wildcard=3` start and the "best match wins" selection. The PCB field offsets in +section 31.1 are therefore `inp_lport`(22), `inp_laddr`(18), `inp_faddr`(12), +`inp_fport`(16). + +### 53.3 LANCE driver + +| ROM | BSD source (`sys/pmax/dev/if_le.c`, DECstation) | evidence | +|---|---|---| +| `0x3F9D4BC0` driver command entry (init + send) | `leinit()` `:429`, `lereset()` `:359` | CSR1/CSR2 = init block, CSR3 = 0, CSR0 = INIT, poll IDON, STRT | +| `0x3F9D36C0` ring arming | `ledrinit()` `:337`, `leinit()` | descriptor address/length/OWN setup | +| `0x3F9D57F0` TX kick | **`lestart()` `:458`** | `LER2_tmd1(tmd, LE_OWN|LE_STP|LE_ENP);` then `LERDWR(ler0, LE_TDMD|LE_INEA, ...)` | +| `0x3F9D5B40` interrupt dispatch | **`leintr()` `:501`** | reads CSR0, tests `LE_INTR`, `LE_SERR`, RXON/TXON, then RINT/TINT | +| `0x3F9D62B0` RX poll | **`lerint()` `:606`** | walks the receive ring, hands completed buffers up | +| `0x3F9D8AF0` TX completion | the TINT half of `leintr()` | releases the transmitted descriptors | +| `0x3F9DCF50`→`0x3F9D4BC0` submission | `if_start` → `lestart()` | the queue-to-ring hand-off | + +### 53.4 What the mapping settles about the descriptor length + +`lestart()` writes the transmit length as **plain two's complement**: + +```c +LER2_tmd2(tmd, -len); /* if_le.c:485 */ +``` + +For a 24-byte frame that is `0xFFE8`. For the receive side the same arithmetic +gives `0xFA0A` for 1526 bytes. Our payload wrote `0x0FE8` and `0x0A0A` — the +**positive** values, missing the top nibble that two's complement produces. + +This confirms section 50 from a third independent direction and simplifies the +rule: the field is simply `-len`, and for any length under 4096 that always has +the top nibble set. There is no separate "0xF000 convention" to remember — a +positive value is simply wrong.