Restores all three FireWire ports on a Power Mac G4 MDD FW800 (PowerMac3,6) running Mac OS 9.2.2:
the two FW400 ports at S400, and the FW800 port at S800, at the same time. One patched extension,
FireWire Enabler. Nothing else is modified.
| FW400 ports | FW800 port | |
|---|---|---|
| stock | do not enumerate a 1394b device at all | S800 |
| the earlier one-byte global clamp released here | work | S400 |
| this patch | work | S800 |
So the gain over stock is that the two FW400 ports become usable. The gain over the
previously released fix is keeping S800 on the FW800 port instead of trading it away for the
FW400 ports. Measurements in docs/QUICKBENCH-S800-vs-S400.md.
Mac OS 9's FireWire family builds the IEEE 1394 speed map from the sp field of each node's
self-ID packet.
sp is two bits, and the value 3 is reserved in 1394a. 1394b uses it to mean "S400 or better,
ask the PHY". It does not mean S800.
Apple's own IOFireWireController::buildTopology says exactly that:
speedCode = (id0 & kFWSelfID0SP) >> kFWSelfID0SPPhase; if( speedCode == kFWSpeedReserved ) speedCode = kFWSpeed800MBit | kFWSpeedUnknownMask; // we don't know how fast it is
and then verifies by transfer: on a failed config-ROM read it calls setNodeSpeed(node, local, current - 1) and retries, down to S100. TI's TSB81BA3 errata recommends the same try-and-see
approach.
Mac OS 9's family has no step-down. It stores sp verbatim and treats 3 as S800. So two 1394b
nodes joined by a legacy (1394a data/strobe) cable both claim S800 on a hop that physically
carries S400, the transmit is never received, and the device simply never appears. That is the
dead-FW400-ports bug.
OS 9 cannot try-and-see without a rewrite, but the FWIM owns the PHY, and the PHY knows. So the
FWIM (FireWire Enabler, the OHCIFWIM container) is hooked immediately before it hands the
self-IDs up to FWProcessSelfIDs. For each of its own ports it reads page 0 registers 8, 9 and 11
(Connected, Negotiated_speed, Beta_mode), maps each node to the port it arrives on, and clamps
that node's self-ID sp to what its own hop can actually carry. The family's existing
min-propagation then produces a correct map, with nothing else in the system altered.
The local node's own sp is deliberately left at S800. That is what allows one speed map to give
S800 to a beta neighbour and S400 to a legacy one simultaneously.
Fail-safe throughout. PHY register 2 is a liveness oracle, and an unreadable PHY, an unexpected port count, or any topology that is not a plain star centred on this Mac all fall back to a conservative S400 rather than risking the original symptom.
Measured on the target machine, Mac OS 9.2.2, one LaCie FW800 drive and a clamshell iBook (FireWire) in Target Disk Mode.
| run | configuration | result |
|---|---|---|
| 1 | nothing attached | extension loads and binds, hook runs |
| 2 | drive on the FW800 port, 9-to-9 | beta S800, ceiling S800, not clamped, mounts |
| 3 | drive on a FW400 port, 6-to-6 | legacy(DS) S400, clamped, mounts |
| 4 | drive on the FW800 port, 9-to-6 | legacy(DS) S400, clamped, mounts |
| 5 | drive on FW800 + iBook on FW400 | S800 and S400 in one map, both work |
| 6 | drive on FW800 + Power Mac G5 in target disk mode on FW400 | two 1394b devices, both claiming S800: the beta one keeps S800, the legacy one is clamped to S400, both mount |
| 6b | the same, with the stock extension | the G5 does not appear at all, while the LaCie mounts. The PHY still reports the legacy segment, so the link is up and only the speed map is wrong |
Run 6 is the one that matters most. Both devices are 1394b and both report sp = 3, so it is the
configuration stock cannot handle at all, and the only one where a wrong port-to-node mapping
fails visibly rather than silently. Log in logs/FWFixCheck_G5_TDM_v006.log.
Throughput, measured on the same drive and controller with only one variable changed at a time.
Patched against stock on the beta port is +1.5% read, which is to say nothing: stock was
already S800 there. S800 against S400 as hop speeds is +10.6% read and +7.5% write, which is
what the earlier global clamp gave away and this patch keeps. A legacy device merely being present
on the bus costs the beta device a further 8.9%. Details and the full tables in
docs/QUICKBENCH-S800-vs-S400.md.
FireWire Enabler in the System Folder's Extensions folder is the only file that changes. Keep
the original.
Expand artifacts/FireWire S800 Enabler.bin. It produces a file already named FireWire Enabler, so it drops
straight in. It reads 2.8.8 in Extensions Manager against a stock 2.8.7, which is how you tell
at a glance which one is installed.
FWFixCheck is a read-only diagnostic. It scans the System heap for the patch's counter block and
reports what it decided: per-port state, beta flag and negotiated speed, and each node's own sp
against the ceiling applied to it.
- Only a plain star centred on this Mac gets per-connection speeds. Behind a hub or a daisy
chain, everything is capped at S400. This is correct rather than conservative: the PHY can only
measure the hops it terminates, a legacy hop deeper in the tree is invisible in the self-ID
stream, and assuming S800 there would reproduce the original defect. See the warning in
tools/patch-firewire-enabler.py. - Sleep and wake are broken on this machine independently of FireWire, confirmed with no FireWire device ever attached. Not addressed here.
python3 tools/patch-firewire-enabler.py "FireWire Enabler" out/ python3 tools/wrap-macbinary.py "out/ascend/FireWire Enabler" "FireWire S800 Enabler.bin"
Give it your machine's own FireWire Enabler, transferred with its resource fork intact. The
patcher refuses to touch a file whose vers is not 2.8.7, so it cannot silently patch a build it
was not derived from.
What it does: grows the OHCIFWIM container's code section, appends the hook, redirects the two
bl instructions that call FWProcessSelfIDs, updates the container length in the cfrg
resource, and stamps vers (1) to 2.8.8. Two bytes change inside the original code, everything
else is appended, and the data and loader sections come through byte-identical.
| path | purpose |
|---|---|
artifacts/FireWire S800 Enabler.bin |
the patched extension, MacBinary. Expands to a file already named FireWire Enabler. |
artifacts/FWFixCheck.bin |
the diagnostic. |
tools/patch-firewire-enabler.py |
the patcher. All the reasoning is in its docstring and comments. |
tools/ppcasm.py |
a small PowerPC assembler whose encoders are each checked against a real instruction from the Apple binaries. It refuses addi with RA=0, a load or store based on r0, and any volatile register held across a bl. Each of those caught a real bug here. |
tools/versstamp.py |
the vers (1) rewrite that stamps the patched build 2.8.8. |
tools/fw-fixcheck/ |
diagnostic source, for the Retro68 PowerPC toolchain. |
docs/S800-RUNBOOK.md |
every hardware run, what it asked, what would have falsified it, and the three bugs found on the way. |
docs/QUICKBENCH-S800-vs-S400.md |
the throughput comparison and why it is only about ten percent. |
logs/ |
raw diagnostic output from every run quoted above. |
Reverse engineering, the patch, the diagnostics and the documentation were produced with substantial help from Claude (Anthropic), working from Apple's published IOFireWireFamily source, the TI TSB81BA3 datasheet, and disassembly of the shipping OS 9 binaries. All hardware testing was run on a real Power Mac G4 MDD FW800.