MacTCP driver implementation - #66
Open
mishan wants to merge 8 commits into
Open
Conversation
Both loops walk LM(UTableBase)[0..UnitNtryCnt) and dereference each entry without checking it. The unit table is sparse -- native drivers install themselves at fixed unit numbers, leaving the slots between them null -- so any empty slot below the loop bound is a null deref. This is latent rather than live: LM(UnitNtryCnt) is set to 0 in init.cpp and never updated by anything, so neither loop body has ever executed and no driver has ever been sent accRun. How UnitNtryCnt ought to be maintained is a separate Device Manager question; guard the dereference first so that raising it is safe.
callcomp re-enters emulated code to run a driver call's completion routine (A0 = param block, A1 = the routine, D0 = result). It was defined in serial.cpp at global scope, where no other translation unit could reach it, even though it implements a Device Manager convention rather than anything serial-specific. Move the definition to device.cpp and declare it in rsys/device.h. No behaviour change; serial.cpp picks it up via using namespace Executor.
MacTCP is reached by applications through PBOpen("\p.IPP") plus
PBControl with a csCode, so it needs no trap work at all: the whole
feature is a device driver plus the type definitions that just landed
in multiversal.
Phase 1, modelled on serial.cpp: driver registration at .IPP / refnum
-48, csCode dispatch, an opaque stream-cookie table, and synchronous
TCP client operations over non-blocking POSIX sockets -- ipctlGetAddr,
TCPCreate, TCPActiveOpen, TCPSend (writev over the WDS gather list),
TCPRcv, TCPClose (half-close), TCPAbort (RST via SO_LINGER 0),
TCPStatus and TCPRelease. Guest memory is big-endian, so htonl/htons
at the sockaddr boundary is both necessary and sufficient.
Operations execute synchronously and then complete, the same idiom
serial.cpp uses. Phase 2 replaces the bodies with a pending-op queue
drained from an accRun pump -- which will first require sorting out
LM(UnitNtryCnt), since accRun is currently never delivered to anyone.
Passive open, NoCopyRcv, UDP, the ASR and the DNR are all stubbed with
notes rather than half-implemented.
The documented parameter block offsets are asserted at build time, so
a disagreement between the generated headers and the MacTCP ABI fails
the build instead of corrupting guest memory at run time.
Also bumps the multiversal submodule, which carries the MacTCP
definitions along with the two generator fixes on that branch.
Until now the driver had only been verified statically -- it compiled, linked and registered, but no part of it had ever executed. main_executor.cpp already stands up memory, the 68k emulator, traps and low memory in a native gtest binary, which is enough to drive the driver through the real OpenDriver/PBControl path with no 68k application involved. These tests use that: name lookup and the unit slot, ipctlGetAddr, rejection of undersized receive buffers, unknown csCodes and unknown streams, and a full TCPCreate / TCPActiveOpen / TCPSend / TCPRcv / TCPStatus / TCPClose / TCPRelease round trip. The round trip is hermetic: the test process itself listens on a loopback socket and the driver connects back to it, so nothing depends on the host having a network, a route or a resolver. The send goes through a multi-entry WDS so the gather path is covered rather than just the single-buffer case. ipctlGetAddr skips rather than fails on a host with no non-loopback IPv4 interface, which is the normal situation in a container. Native-only for now. The dual-mode offsetof/sizeof comparison against Apple's Universal Interfaces belongs in TEST_SOURCES once the Retro68 side can generate MacTCP.h, and is what will settle the VERIFY markers still in MacTCP.yaml.
Checks the size and every byte offset of the parameter blocks an application shares with the .IPP driver, both at compile time and at runtime, plus the result codes, csCodes and connection states. The two build modes answer two different questions. Natively it checks that multiversal's generated header really lays out the way defs/MacTCP.yaml says: the YAML's size: assertions only pin totals, so a mac68k alignment mistake in the middle of a struct that preserves the total would otherwise go unnoticed. Under Retro68, compiled against Apple's Universal Interfaces, it checks the numbers taken from the MacTCP Programmer's Guide against Apple's own -- which is the ground truth that settles the VERIFY markers still in the YAML. The expected values are written out here rather than derived from the YAML, so a transcription error in the YAML surfaces as a failure instead of being mirrored. DumpLayout prints the whole layout in a diffable form for comparing the two sides directly, since an assertion tells you something is wrong but not what the other side thinks it should be. The file compiles to nothing where there is no MacTCP.h, so it does not break Retro68 builds without Apple's interfaces installed.
Picks up the multiversal correction to TCPReceivePB and TCPStatusPB, and tightens the assertions on both sides to match. The driver's guard rails now cover the fields that were wrong rather than only the ones that happened to be right: TCPReceivePB's rcvBuff, markFlag and urgentFlag, plus the sizes of TCPStatusPB and TCPiopb, which changed from 66 and 98 to 70 and 102. Worth noting for anyone reading the earlier test results: the loopback round trip passed both before and after this fix. It writes the parameter block through the same header the driver reads it through, so a wrong offset is invisible to it -- both sides are wrong in the same place. Only a comparison against an independent definition of the ABI could catch this, which is what the dual-mode test is for.
A real HTTP/1.0 request goes out through TCPSend and a real response comes back: status line, headers, and a body larger than the application's receive buffer, so it takes a dozen TCPRcv calls to drain and the server's hangup is what ends the entity. That last part matters -- connectionClosing is the normal end of an HTTP/1.0 response, not an error, and an application has to treat it that way. The server is inside the test process rather than out on the internet. MacTCP has no resolver yet, so a real host would mean a hardcoded IP address that rots; a test needing egress cannot run in CI or offline; and the driver cannot tell the difference, since routing and DNS are not parts of it. Setting EXECUTOR_MACTCP_TEST_ADDR points the same exchange at a real server for anyone who wants to watch it work.
mishan
force-pushed
the
mactcp
branch
3 times, most recently
from
August 6, 2026 22:27
fceaac8 to
159a2e3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(Has commits from PR #64 so it would build for me)