The network coprocessor's mailbox (D45)¶
The interface between P32W and the network coprocessor (a Pico 2 W with a
W5500, on the I/O card, D50). Three programs implement it and must
agree on it:
- the emulators' model (emulator/p32io.h, -N), which carries it out
with the Mac's own sockets;
- the kernel's driver;
- the coprocessor's firmware.
This document is their common reference. Version 1, 2026-10-17.
1. Registers¶
Two byte registers in the I/O card's window (D52), after the first
CH376S's. Byte accesses only, as for the card's other byte devices. The
I/O card's interrupt status (FFFF_FFF4) has bit 8 for the coprocessor.
| Address | Read | Write |
|---|---|---|
0xFFFF_FF52 NDATA |
the next reply byte (reading takes it) | the next request byte |
0xFFFF_FF53 NSTAT / NCTRL |
status | control |
Status (NSTAT, read):
| Bit | Name | Meaning |
|---|---|---|
| 0 | RXF |
a reply byte is waiting in NDATA |
| 1 | TXE |
the request latch is empty: P32W may write NDATA |
| 2 | ATTN |
the coprocessor has events (section 4); with IEN, IRQ7 is asserted |
| 3 | IEN |
interrupts enabled (as written) |
| 6 | — | always 0 |
| 7 | READY |
the coprocessor is running (its firmware has started) |
Control (NCTRL, write):
| Bit | Meaning |
|---|---|
| 3 | IEN: assert IRQ7 while ATTN is set |
| 7 | RESET: restart the coprocessor (the Pico's RUN pin) and empty both latches; READY drops until it has started again |
The latches. On the card, each direction is one byte latch with a
full/empty flag, kept in a GAL: writing NDATA fills the request latch,
and the Pico taking the byte empties it again; the Pico filling the reply
latch sets RXF, and P32W reading NDATA clears it. The Pico answers
within a microsecond, which is less than one P32W instruction, so a driver
that checks TXE and RXF before each byte almost never waits. The
model answers at once, as all of the emulators' devices do: software
must depend on the bits only, never on timing.
The I/O card's side (D50): the request latch reaches the Pico through
a 3.3 V buffer it enables with -PRD; the Pico clocks the
reply latch with PWR. A GAL clocked by P32W's CLK keeps TXE and RXF
and acts on each strobe's leading edge, once. RESET is a one-clock pulse
on the Pico's RUN, stretched by an RC to about 15 µs.
Without the I/O card, the device window does not answer at these addresses
(BUSERROR). With the card but no running coprocessor, READY is 0.
The emulators give the second case without -N.
2. Messages¶
P32W sends a request and reads the reply, then sends the next
request; there is exactly one reply to each request, and nothing else
ever arrives in NDATA. The two have the same 4-byte header:
| Byte | Request | Reply |
|---|---|---|
| 0 | command | status (section 5) |
| 1 | socket number (0 if none) | socket number (the new one, for SOCKET and ACCEPT) |
| 2, 3 | payload length, low byte first | payload length, low byte first |
| 4 … | payload | payload |
Payloads are at most 1024 bytes each way. Addresses and ports in
payloads are in network order (big-endian), as in sockaddr_in:
an address is 4 bytes, a port 2.
A driver that loses its place (a P32W reset in the middle of a message,
say) sets RESET and starts again with HELLO.
3. Commands¶
| Code | Name | Request payload | Reply payload |
|---|---|---|---|
01 |
HELLO |
— | the text P32NET 1 (the protocol's version). Closes every socket |
02 |
IFINFO |
— | 24 bytes: flags (bit 0 link up, bit 1 address configured, bit 2 by DHCP), 0, MAC (6), address, netmask, gateway, DNS server (4 each) |
03 |
IFCONFIG |
mode (1 byte: 0 DHCP, 1 static), then address, netmask, gateway, DNS server | — |
04 |
EVENTS |
— | 2 bytes: a bit for each socket whose state has changed since the last EVENTS (bit n = socket n), low byte first. Clears ATTN |
10 |
SOCKET |
type (1 byte): 1 TCP, 2 UDP, 3 TLS, 4 DNS lookup, 5 ICMP (the W5500's IP-raw mode, protocol 1: SENDTO and RECVFROM carry whole ICMP messages, P32W builds them, the port is 0) |
— (the reply's socket byte is the new socket) |
11 |
CONNECT |
TCP, TLS: address, port; TLS also the host name, for SNI and the certificate check. DNS: the name to look up | — (started: section 4) |
12 |
BIND |
port (0: any) | — |
13 |
LISTEN |
backlog (1 byte) | — |
14 |
ACCEPT |
— | the peer's address and port; the reply's socket byte is the new, connected socket. AGAIN if none is waiting |
15 |
SEND |
the data | 2 bytes: how many were taken (AGAIN if none could be) |
16 |
RECV |
2 bytes: the most to return | the data. AGAIN if none is there yet; OK with none at the end of the stream. DNS: the 4-byte address |
17 |
SENDTO |
UDP: address, port, the data | 2 bytes: how many were sent |
18 |
RECVFROM |
UDP: 2 bytes, the most to return | the sender's address and port, then the data. AGAIN if none |
19 |
SHUTDOWN |
— | — (TCP, TLS: no more sending; the peer sees the end of the stream) |
1A |
CLOSE |
— | — (the socket is free again) |
1B |
STATE |
— | 14 bytes: state, error, bytes ready to read (2; a listening socket: connections waiting), room to send (2), local port (2), peer address (4), peer port (2). The four 2-byte counts and ports here are numbers, low byte first; the address is in network order |
The coprocessor has 8 sockets, numbered 0 to 7 (one for each W5500 socket). A TLS socket takes one, and a DNS lookup takes one while it runs.
Socket states (STATE's first byte):
| Value | State |
|---|---|
| 0 | free |
| 1 | new (made, not yet used) |
| 2 | connecting (TCP and TLS: the handshakes; DNS: the lookup) |
| 3 | connected (DNS: the answer is ready) |
| 4 | listening |
| 5 | closed by the peer (data may still be waiting to be read) |
| 6 | failed (STATE's second byte is the error, a status code) |
| 7 | bound (UDP: ready to send and receive) |
4. Events and waiting¶
CONNECT only starts a connection, because a handshake can take seconds
and the mailbox is shared by every socket. The coprocessor sets ATTN
when anything a process might be waiting for changes on any socket:
- a connection made or failed;
- a DNS answer;
- data arriving;
- room to send again;
- a connection waiting to be accepted;
- the peer closing.
The driver then sends EVENTS and wakes the processes waiting on those
sockets, which look again with STATE, RECV or ACCEPT. Without
interrupts, the driver can poll ATTN.
5. Status codes¶
The reply's first byte. The driver turns them into P32W's errno values.
| Code | Name | errno | Meaning |
|---|---|---|---|
| 0 | OK |
done | |
| 1 | AGAIN |
EAGAIN |
nothing yet: no data, no room, no connection waiting |
| 2 | INVAL |
EINVAL |
a bad request: no such socket, or not in that state |
| 3 | NOSOCK |
ENOBUFS |
all 8 sockets are in use |
| 4 | REFUSED |
ECONNREFUSED |
connection refused |
| 5 | TIMEDOUT |
ETIMEDOUT |
no answer |
| 6 | UNREACH |
EHOSTUNREACH |
no route to the host |
| 7 | RESET |
ECONNRESET |
the connection was reset by the peer |
| 8 | NOTCONN |
ENOTCONN |
not connected |
| 9 | INUSE |
EADDRINUSE |
the port is in use |
| 10 | NONAME |
(the resolver's "host not found") | DNS: no such name |
| 11 | TLS |
ECONNABORTED |
the TLS handshake failed, or the certificate is not trusted |
| 12 | NOLINK |
ENETDOWN |
no network: the link is down, or no address yet |
| 13 | TOOBIG |
EMSGSIZE |
a payload over 1024 bytes |
| 14 | BADCMD |
EINVAL |
an unknown command |
6. The emulators' model (-N)¶
The model carries the commands out with the Mac's sockets, so P32W in the
emulator really reaches the network:
- The interface it reports: address 10.0.2.15/24, gateway 10.0.2.2,
DNS server 10.0.2.3, MAC 02:50:33:32:00:01, link up, by DHCP. These are
QEMU's user-networking numbers.
- 10.0.2.2 is the Mac (connections to it go to 127.0.0.1), so tests
can run their own servers. Other addresses are reached as they are.
- Listening: P32W port p becomes the Mac's p + 8000 when p is
below 1024 (80 is 8080, 23 is 8023), else p; -Nport OFFSET changes
the 8000. On the Mac's loopback only.
- DNS uses the Mac's resolver (IPv4 answers).
- TLS goes through tools/p32tls.py (Python's ssl: the Mac's
certificate store, SNI, and the host-name check). -Nca FILE gives
another set of trusted certificates, so tests can use a certificate of
their own.
- No loopback: P32W cannot connect to its own address, as on the W5500.
- Waiting: while a socket is busy, an idle P32W (WAIT) waits in real
time for the Mac's sockets as well as the timer, so emulated time does
not run ahead of the network.