PowerPC: little-endian mode on the 604, and Bandit byte-lane reversal - #135
Open
pappadf wants to merge 4 commits into
Open
PowerPC: little-endian mode on the 604, and Bandit byte-lane reversal#135pappadf wants to merge 4 commits into
pappadf wants to merge 4 commits into
Conversation
Apple's 2.26NT Open Firmware for the Apple Network Server runs the 604 in
little-endian mode (`little-endian? true`), and the PowerPC Windows NT
loaders are little-endian PE images. Two pieces were missing to execute
that path.
1. MSR[ILE] / MSR[LE] on the 604
--------------------------------
Both bits were masked out of the MSR, so mtmsr/rfi silently dropped them
and the core always ran big-endian. They are now implemented on the 604
only; the 601 mask is unchanged.
A 60x in little-endian mode does not reorder bytes. It munges the low
three bits of every effective address (XOR 7/6/4 for byte/halfword/word,
nothing for an aligned doubleword), and instruction fetch munges like a
word load. Implemented in src/core/cpu/ppc/:
* ppc_le_ea() applies the munge; `ea` stays architected everywhere an
instruction writes it back or reports it.
* Instruction fetch XORs the pc with a cached le_xor, recomputed on
every fetch-window refill (a stale value cannot survive an MSR write,
which flushes the window).
* Any misaligned scalar access, and every lmw/stmw/lswi/lswx/stswi/
stswx, takes the alignment exception (604UM 4.5.6).
* A doubleword FP transfer becomes two munged word accesses with the
high-order word at the higher address.
* Exception entry replaces MSR[LE] with a copy of MSR[ILE]; rfi
restores LE from SRR1.
* debug.disasm applies the fetch munge so little-endian code lists in
program order.
2. Bandit byte-lane reversal
----------------------------
The CPU munge is only half of the mechanism; the host bridge carries the
other half. Mode-select $50 bit 24 is the endian bit: set at power-on for
the straight byte lanes every Macintosh OS uses, cleared by the firmware
for a little-endian client, at which point the bridge reverses its eight
byte lanes. The two cancel, so a little-endian guest reaches PCI with
plain loads and stores.
* pci.c/pci.h gain a per-bus flag (pci_bus_set_lane_reverse /
pci_bus_lane_reverse). The window dispatch applies it once, before
decode: an N-byte access at offset o becomes o ^ (8-N) with its bytes
reversed, so no device model sees anything but PCI byte addresses.
* bandit.c drives the flag from mode-select, and the bridge's own
config address and data ports honour it too. That last part is
required: the firmware's `set-caddr` composes a natural one-hot
config address and stores it with `xl!`, and `xl@`/`xl!` are the
byte-flipping variants only in big-endian mode (the ROM patches those
tokens to the flipping forms under `little? 0=`). In little-endian
mode they are plain, so the flip has to come from the bridge.
* Anything that bypasses the CPU applies the same reversal itself: the
Grand Central island's direct mapping and the DBDMA movers (tnt.c),
and the 53C825 SCRIPTS bus-master block movers (scripts53c8xx.c).
With straight config ports the firmware's own probe reads $00100300
where it compares against $31000, so its Shiner-ESB test fails and the
two 53C825 nodes and the 54M30 video are never created. With the
reversal the little-endian device tree matches the big-endian one.
Tests
-----
* tests/unit/suites/ppc: test_604_little_endian covers the munge at
every offset and width, update forms writing back the architected EA,
lwbrx/lhbrx, aligned and word-aligned lfd/stfd, the alignment faults,
ILE->LE on exception entry with rfi restoring it, and the 601 masking
both bits off.
* tests/unit/suites/pci: test_window_lane_reversal pins the offset
transform and the byte reversal per width, and that the flag reads
back and is reversible.
* tests/integration/tnt-pci-slots gains coverage that clearing the
endian bit reverses the pass-through island's lanes while the config
ports stay straight, and that restoring it restores the straight
view. Its coherency handshake now preserves bit 24, which is what the
documented read/OR-$40/write sequence does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pappadf
force-pushed
the
ppc-le-mode-and-bandit-lane-reversal
branch
from
September 5, 2026 17:20
9a7e92e to
96a7336
Compare
The row's endian-bit block still encoded an earlier design in which Bandit's own config address and data ports stayed straight while the lanes reversed. The shipped model reverses the ports too (bandit.c, port_offset / port_val32), because the firmware's `xl!`/`xl@` are the byte-flipping variants only in big-endian mode, so in little-endian mode the flip has to come from the bridge. CI failed on the first assertion of that block. Seen from the big-endian script, a reversed 32-bit access reads and writes the little-endian config dword as-is: after clearing the endian bit mode-select reads $00000040, and restoring it means writing $01000040, not $40000001 (which left the bridge reversed). The test now asserts exactly that, and checks both the pass-through island and the data port after the restore. Also brings the two prose statements that said the ports stay straight into line with the code: the bandit.c header comment and docs/machines/tnt/tnt.md. No behavioural change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An investigation note (docs/notes, force-added like the existing notes there) with the two walls between the thread's transcript and Windows NT Setup's computer-type prompt, and the recipe that passes them: /chosen bootpath set to the CD, and two one-byte patches to VENEER.EXE (image 0x5D0C0 "partition(1)" -> "", 0x5E168 ":0" -> "") so that SETUPLDR's BlGenerateDeviceNames accepts the CD-ROM ARC name and Apple's disk-label returns raw sector access. The screen, rendered from the ttya stream, goes in docs/assets. No code change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Open Firmware and AIX arrive knowing what this chip is, so a flat byte array
per register block carried the model this far. A driver that has to *find*
the part does not: Windows NT's cirrus.sys unlocks the extensions, reads the
chip ID, and sizes the framebuffer, and every one of those reads came back
as whatever had last been written.
SR06 "Unlock ALL Extensions" — $12 unlocks and reads back $12, anything
else locks and reads back $0F. The round trip is the presence test.
CR27 read-only $A0: CL-GD5430, revision 0 — the die this board carries,
and the value that agrees with its $00A0 PCI device ID. cirrus.sys
requires CR27 >> 2 to fall in [$0B, $2F] before it goes on.
SR15 "DRAM Control" bits 3:0 report the fitted memory; 2 is 1 MB. The
driver builds its mode list from this field rather than probing, so
zero meant every mode was too large and the adapter had none.
$3CC Miscellaneous Output's read address, answering with what $3C2 was
given. Bit 0 says which CRTC pair to use, and a driver that read
zero went to $3B4/$3B5 — which decoded nothing. Both pairs now
answer, so software that never writes $3C2 still works.
With these NT 4.0's cirrus.sys identifies the adapter and initialises it.
ans-console, ans-device-tree, ans-pci-slots, tnt-pci-slots, ans-aix-boot,
ans-macos-2rom and ans-aix-installed-boot all still pass — the goldens are
unchanged, because everything that already worked wrote $3C2 and SR06 the
way real VGA code does.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What this changes
Two related gaps that together prevented the machine from running any
little-endian PowerPC client.
1.
MSR[ILE]/MSR[LE]on the 604Both bits were masked out of the MSR, so
mtmsr/rfisilently dropped them andthe core always ran big-endian. They are implemented on the 604 only; the 601
mask is unchanged.
A 60x in little-endian mode does not reorder bytes. It munges the low three bits
of every effective address (XOR 7/6/4 for byte/halfword/word, nothing for an
aligned doubleword), and instruction fetch munges like a word load. In
src/core/cpu/ppc/:ppc_le_ea()applies the munge.eastays architected everywhere aninstruction writes it back or reports it.
le_xor, recomputed on everyfetch-window refill. A stale value cannot survive an MSR write, which flushes
the window.
lmw/stmw/lswi/lswx/stswi/stswx, takes the alignment exception.the higher address.
MSR[LE]with a copy ofMSR[ILE];rfirestoresLEfromSRR1.debug.disasmapplies the fetch munge so little-endian code lists in programorder.
2. Bandit byte-lane reversal
The address munge is only half the mechanism. The host bridge carries the other
half: mode-select
$50bit 24 is the endian bit, set at power-on for thestraight byte lanes every Macintosh OS uses, and cleared by the firmware for a
little-endian client. The CPU munge and the bridge reversal cancel, so a
little-endian guest reaches PCI with plain loads and stores.
pci.c/pci.hgain a per-bus flag (pci_bus_set_lane_reverse/pci_bus_lane_reverse). The window dispatch applies it once, before decode:an N-byte access at offset
obecomeso ^ (8-N)with its bytes reversed, sono device model sees anything but PCI byte addresses.
bandit.cdrives the flag from mode-select, and the bridge's own configaddress and data ports honour it too. That part is required. The firmware's
set-caddrcomposes a natural one-hot config address and stores it withxl!, andxl@/xl!are the byte-flipping variants only in big-endian mode(the ROM patches those tokens to the flipping forms under
little? 0=). Inlittle-endian mode they are plain, so the flip has to come from the bridge.
Central island's direct mapping and the DBDMA movers (
tnt.c), and the 53C825SCRIPTS bus-master block movers (
scripts53c8xx.c).Why the config ports matter
With straight config ports the firmware's own probe reads
$00100300where itcompares against
$31000. Its Shiner-ESB test therefore fails, and the hookthat installs the two 53C825 controllers and the 54M30 video never creates those
nodes. Observed device tree in little-endian mode, before and after:
After the change the little-endian tree matches the big-endian tree exactly, and
the firmware's
?esbreads-1instead of0.How far this takes the boot
Testing used the 2.26NT ANS ROM and the Windows NT 4.0 PowerPC install media.
Neither is in this repository.
1. Open Firmware boots little-endian and keeps the serial console. After
setting
little-endian? true,real-mode? false,real-baseandload-baseand restarting, the console comes back on ttya:
The fully expanded device path in that message is itself evidence the 53C825
node now exists; before the change the same line read
can't OPEN: disk2:aix.2. The NT veneer loads and lays out. Reading it off a SCSI disk with
read-blocksand relocating it produces:A full byte comparison of guest memory
0x50000..0x63000against the expectedlaid-out image (comparing after un-reversing aligned 8-byte groups, since a
little-endian guest stores it doubleword-reversed) gives:
.text0x50000-0x5C400.bss0x5C400-0x5CC00.rdata0x5CC00-0x60C00.data0x60C00-0x61C00.reldata0x61C00-0x61E003. The veneer executes. Single-stepping its entry:
The TinkerDifferent thread records a hardware failure at exactly
0x50014, withr8holding garbage (18A2D8C5) because the veneer's.datawas not loaded.That does not reproduce here:
.datais loaded correctly,r8resolves intoit, and the store succeeds.
4. The ARC interface comes up. Console output, matching the banner recorded
in that thread:
The failing call is
call-method("claim", ihandle=/memory, align=0, size=0xD0, virt=0x4000)returning-1. Mapping the page withmap-spacedoes not satisfyit, because mapping is not claiming.
5. With the thread's two documented
noppatches applied (they nop theblto the claim wrapper, so
r3keeps theli r3,0x4000immediately before it,which is not
-1, and the error branch is skipped), the veneer gets past bothclaims and reaches SETUPLDR:
That is as far as it goes. Windows NT Setup and the HAL are not reached.
The next blocker, which this PR does not introduce
Open Firmware's ISO-9660 file read returns wrong data past exactly 16 KB.
Loading a 0x27800-byte file off the CD is byte-perfect for the first
0x4000bytes and then diverges. The data it fetches comes from
LBA = expected + file_start_LBA, i.e. the extent base is added a second time(expected 115061, actual 230114, extent start 115053).
Raw
read-blocksof 16 CD blocks at the same LBA is byte-perfect, so the CDmodel and the 53C825 DMA path (including the new lane reversal) are correct.
This reproduces identically in big-endian mode — same 7919 mismatching bytes
at the same
0x4000boundary — so it is pre-existing and unrelated to thischange. It is, however, what now blocks loading SETUPLDR (400 KB), and it is the
next thing to fix.
Follow-up, 2026-09-05: SETUPLDR runs and the emulator reaches the real ANS 700's screen
Same branch, no further code change. Driven interactively from a checkpoint taken
just before
go, with the veneer's own debug trace switched on (it has aVrDebugbitmask in
.dataand ships with its COFF symbol table, so every routine has a name).Three findings, each read off the 2.26NT firmware's detokenized Forth source (joevt's
package, thread post 43745) and confirmed against the trace.
1. The 16 KB ISO-9660 read defect is the firmware's, and the veneer never hits it.
The
deblockersupport package buffers 8 × 2 KB blocks.iso-9660-filesopenseeksthe deblocker to the file's absolute extent while
open-ok?is still false, thensets
open-ok?, after which itsread-blocksaddsfileextentto every block numberthe deblocker asks for. The first 16 KB come out of the buffer already filled, and the
next request is for block
fileextent + 8, which becomes LBA2·fileextent + 8—exactly the numbers above. The firmware's own
$loadcalls the package'sloadwithout a
seek, so a bareloadof any ISO file over 16 KB is wrong on real hardwaretoo. The NT veneer issues
seekbefore everyread, which re-positions the deblockerrelative to the file, so this never affects the NT path.
2.
VrOpen returned d(ARCENODEV) was an empty/chosenbootpath. The thread'stranscript loads the veneer by hand with
read-blocks, so nothing setsbootpath—only the firmware's
load/bootdo. The veneer'sfind_boot_devreads that property,finddevice("")yields the root node, whose ARC name is the Open Firmware root'sname,device-tree, andVrOpenthen cannot find adevice-tree(0)componentunder the ARC root. Setting the property at the prompt before
gomakes the veneer derive
multi(0)scsi(0)cdrom(0)fdisk(0)partition(1)\PPC\SETUPLDR,open it, relocate SETUPLDR's six sections, build the memory descriptor list and jump
to it. That is the path named in the real machine's error message (post 49785), so the
real ANS 700 had
bootpathset as well.3. The real hardware's wall,
The file multi(0)scsi(0)cdrom(0)fdisk(0)partition(1) is corrupted, is reproduced verbatim, and its cause is the firmware'sdisk-label.SETUPLDR opens the raw partition (
sd@0,0:1) and probes it for a filesystem: reads of0x62 bytes at 0, 512 at 0x2000, 528 at 0 and 2048 at 0x8000 (the ISO volume
descriptor). Apple's
disk-labelopen, given the argument1with no file name,still detects
CD001and interposesiso-9660-files, whoseopenwith no path leavesthe instance on the ISO root directory as a pseudo-file (428 bytes on this CD).
seekdoes not update itsfileposnandreadclamps tofilesize, so the fourreads returned 98, 330, 0 and 0 bytes — 428 − 98 = 330 — and the descriptor read gets
nothing. SETUPLDR's recognizers all fail and it reports the partition corrupted. The
console then shows
which is the furthest screen anyone has reached on real hardware (post 49785).
Where that leaves the emulator: level with the real ANS 700, with the little-endian
CPU mode and Bandit lane reversal from this PR carrying the veneer, the firmware's
client interface and SETUPLDR through several hundred million instructions without a
CPU- or bus-side fault. Nothing in the two walls points at the emulator. Going further
means giving SETUPLDR raw sector access to the CD (a firmware method override, or a
veneer patch), after which text-mode Setup is expected to ask for the computer type —
the missing ANS HAL. That is deliberately not part of this PR.
Follow-up 2, 2026-09-05: past the real machine's wall — SETUPLDR stops at the HAL menu
Still no code change on this branch; the emulator was used to find out why SETUPLDR
says the partition is corrupted, and the answer is two bytes in the veneer.
The "corrupted" message is
BlGenerateDeviceNamesrejecting the boot name. Withraw partition reads made to work (a
disk-labelopenoverride typed at the firmwareprompt), SETUPLDR's own CDFS read the volume descriptor, the root and
PPCdirectoriesand the first 1 KB of its own image — and still printed the message. Breakpoints on its
error reporters caught
SlFriendlyError(EINVAL, "multi(0)scsi(0)cdrom(0)fdisk(0)partition(1)", setup.c:416)right after
BlGenerateDeviceNames, which parses ARC names lexically and requirescdrom(N)fdisk(N)to be the end of the name; the trailingpartition(1)is EINVAL,and
SlFriendlyErrorrenders EINVAL as "The file %s is corrupted". The veneer'sfind_boot_devappendspartition(1)to every boot path, correct for a hard disk andwrong for a CD-ROM. Same veneer, same SETUPLDR on the real ANS 700, so this is the
hardware thread's wall too, independent of the firmware.
Two one-byte patches to
VENEER.EXE, no firmware change:.rdatastring0x5D0C00xD2C0"partition(1)"""multi(0)scsi(0)cdrom(0)fdisk(0)\PPC\SETUPLDR0x5E1680xE368":0"""VrOpenof the bare device passes an empty Open Firmware argument, the one case in which Apple'sdisk-labelreturns raw sector access instead of interposingiso-9660-fileson the ISO root directoryWith those and
bootpathset, SETUPLDR readsTXTSETUP.SIF, prints Setup is loadingfiles (Windows NT Executive)… and stops at
which is the missing Apple Network Server HAL — the point this whole exercise was
aiming for. Everything on the way ran on the 604 little-endian mode and Bandit lane
reversal from this PR: the veneer, the firmware's client interface in little-endian
mode, SETUPLDR's CDFS over ARC reads, and its INF parser over the 126 KB
TXTSETUP.SIF,several billion instructions with no CPU- or bus-side fault.
Follow-up 3, 2026-09-05: the screen, the recipe, and what is loaded
Rendered from the ttya byte stream: SETUPLDR drives the ARC console with 8-bit CSI
cursor addressing, SGR colours (
0m 37m 44mfor the client area,0m 30m 47mfor thebars) and CP437 box characters. The two
OFClose(...) failedlines at the top are theveneer's, printed before SETUPLDR painted the screen.
Where NT stands at this screen. The SCSI command log shows SETUPLDR read the whole
of
\PPC\NTKRNLMP.EXE(84 READ commands over its 666 blocks) afterTXTSETUP.SIF, so thekernel image is in memory — but nothing of NT executes. The kernel imports all of its
hardware access from
HAL.DLL, the loader binds those imports only after a HAL is chosen,and none of the seven HALs on the CD is for an Apple machine. Control is in SETUPLDR's
menu loop, polling the ARC console for a key.
How to get here — the full recipe is in
docs/notes/2026-09-05-ans-windows-nt-setupldr.mdon this branch, with the mechanism of each wall. In short, on top of the thread's
transcript (post 49785: LE reboot,
VENEER.EXEviaread-blocks+init-program, thefour pokes), type at the
0 >prompt beforego:The first line is only needed when the veneer is hand-loaded (a
bootfrom the CD setsbootpathitself); the two bytes are0xD2C0and0xE368in the file for a staticVENEER.EXEpatch. On the emulator it ismake headless, the ANS 500 profile with64 MB, the 2.26NT ROM, the NT 4.0 OEM CD on
/bandit/53c825@11/sd@0,0and the veneeron a disk at
/bandit/53c825@12/sd@0,0; the whole sequence from a cold boot to the menuruns in about ten minutes at
--speed=turbo.The branch gained only that note and the image (commit
8ebed27); no code changed.Testing
Added:
tests/unit/suites/ppc:test_604_little_endiancovers the munge at everyoffset and width, update forms writing back the architected EA,
lwbrx/lhbrx, aligned and word-alignedlfd/stfd, the alignment faults,ILE->LEon exception entry withrfirestoring it, and the 601 maskingboth bits off.
tests/unit/suites/pci:test_window_lane_reversalpins the offset transformand byte reversal per width, and that the flag reads back and is reversible.
tests/integration/tnt-pci-slots: clearing the endian bit reverses thepass-through island's lanes and the bridge's own config ports — a
reversed 32-bit access then reads and writes the little-endian config
dword as-is (mode-select reads
$00000040; restoring the bit is a writeof
$01000040) — and restoring it restores the straight view.Changed: that row's coherency handshake now writes
0x40000001instead of0x40000000, preserving bit 24. That is what the documented read / OR$40/write sequence does; the previous literal cleared the endian bit as a side
effect.
Run locally before pushing:
make headlessclean, andclang-format-18 --dry-run --Werrorover the changed sources. The unit and integration suitesare left to CI.
CI follow-up (39fa333): the first CI run failed on exactly one row,
tnt-pci-slots, whose endian-bit block still asserted the earlier"config ports stay straight" design. The test, the bandit.c header comment
and docs/machines/tnt/tnt.md now match the implemented behaviour described
above. No code change.
🤖 Generated with Claude Code