NETWORK_REPORT: map every reversed function to 4.4BSD source

Adds the ROM -> BSD correspondence with the construct that identifies each
pair: ipintr/ip_input's inetsw[ip_protox[]] dispatch (both tables found in RAM
at 0x1E0F70 and 0xD6BE0, struct protosw offsets 6/10 match the dump),
icmp_input/icmp_reflect/icmp_send, tcp_input/tcp_dooptions/tcp_mss,
udp_input/udp_output, in_pcblookup (line-for-line: matchwild=3, lport, laddr,
faddr/fport, best-match), arpresolve/arplookup/in_arpinput/arprequest, and the
pmax LANCE driver's leinit/lereset/ledrinit/lestart/leintr/lerint.

lestart() writes the descriptor length as plain -len (if_le.c:485), which for
24 bytes is 0xFFE8 -- confirming section 50 from a third direction and showing
our 0x0FE8/0x0A0A were simply positive values.
This commit is contained in:
arti committed 2026-10-30 01:39:09 +02:00
1 parent b5fe6935e1
commit 3ad5a691b8
1 file changed
+73
+73
View File
@@ -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.