Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/ans-nt-setup-hal-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 28 additions & 4 deletions docs/core/cpu/ppc.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,34 @@ deltas, each keyed on `cpu_model`:
same `rtcu/rtcl` slots at their rebase instant. DEC decrements once
per timebase tick. `ppc_bind_time(p, s, freq_hz, tick_hz)` takes the
tick rate explicitly: 7,833,600 on the 601 (PDM), bus/4 on the 604.
- **MSR**: adds POW (accepted as a no-op idle hint), BE, PM, RI.
Exception entry keeps ME/EP/PM and clears the rest; `rfi` restores
MSR[16-31] only (POW survives). ILE/LE stay unimplemented on both
models (big-endian Macs).
- **MSR**: adds POW (accepted as a no-op idle hint), BE, PM, RI, and the
little-endian pair ILE/LE. Exception entry keeps ME/EP/PM/ILE, clears
the rest, and REPLACES LE with a copy of ILE; `rfi` restores MSR[16-31]
only (POW and ILE survive). The 601 masks ILE/LE off (its Macs never
leave big-endian).
- **Little-endian mode** (PEM §3.2.2; 604UM §4.5.6): with MSR[LE] set the
core does what the silicon does — no byte reordering, only address
munging. Every data EA has its low three bits XORed with 7/6/4 for a
byte/halfword/word (`ppc_le_ea`), a doubleword FP transfer becomes two
munged word accesses with the high word at the higher LE address
(`ppc_le_ld64/st64`), and instruction fetch munges like a word load
(`g_ppc_fetch.le_xor`, refreshed on every refill — every MSR write
flushes the window). Memory therefore holds the LE program's image with
each doubleword byte-reversed (code: the two words of each doubleword
swapped). Any access that is not naturally aligned, and every
lmw/stmw/lswi/lswx/stswi/stswx, takes the alignment exception with DAR
= the architected EA; update forms write back the unmunged EA. Device
registers see the munged address like RAM does — the guest compensates
itself (Apple's Open Firmware `ar-rl@` is `little? if 4 xor then xl@`).
First user: the Apple Network Server's NT-era Open Firmware
(`little-endian? true` after the pe-loader's forced reboot), which
pair-swaps and byte-reverses its own dictionary in place before flipping
MSR[LE], then loads the PowerPC-LE `VENEER.EXE` (docs/machines/tnt/tnt.md).
On that machine the *bridge* carries the other half of the trick: Bandit
reverses its byte lanes for everything downstream (its `$50` mode-select
endian bit), so the CPU's address munge and the bridge's lane reversal
cancel and a little-endian client reaches PCI with plain loads and stores
— see "Bridge byte-lane reversal" in docs/core/peripherals/pci.md.
- **MMU**: the ARCHITECTED BAT format (BEPI/BL/Vs/Vp upper,
BRPN/WIMG/PP lower; blocks 128 KB–256 MB; PP-only protection) with
four SPLIT pairs each way — fetch consults the IBATs (`batu/batl`),
Expand Down
20 changes: 20 additions & 0 deletions docs/core/peripherals/pci.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,26 @@ bits whose transform order *reverses* between directions, and the texture
path's own pair in `tLOD`. The model honours the bits rather than
assuming a big-endian host, because Mac Glide drives them from the
application (`grLfbWriteColorSwizzle`).
### Bridge byte-lane reversal for a little-endian client

A host bridge can be told to reverse its eight byte lanes so that a CPU
running in little-endian mode sees PCI as byte-address-invariant. Turn it
on with `pci_bus_set_lane_reverse(bus, true)`; a bus master consults
`pci_bus_lane_reverse(bus)`. When it is on, the window dispatch applies the
reversal once, before decode: an N-byte access at window offset `o` reaches
PCI offset `o ^ (8 - N)` with its bytes reversed, so no device model ever
sees anything but plain PCI byte addresses and values. This is the
hardware counterpart to the classic PowerPC little-endian *address munge*
(the CPU XORs an access's low address bits by `8 - size` rather than
reordering bytes); the two cancel, so a little-endian guest reaches every
device behind the bridge with plain loads and stores. Anything that
bypasses the CPU — bus-master DMA, and a family's direct mapping of a
pass-through region — must apply the same `^7` byte reversal itself while
the flag is set, because it never went through the window. The Apple
Network Server's Bandit is the first user: its `$50` mode-select bit 24
drives the flag (`machines/tnt/bandit.c`), and clearing it is how the
2.26NT firmware brings the bridge into agreement with a little-endian
Windows NT client.

## Slot kinds

Expand Down
40 changes: 38 additions & 2 deletions docs/machines/tnt/tnt.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,25 @@ DMA architecture:
one-hot IDSEL, and the bridge's own device-11 header (vendor `$106B`,
device `$0001`, revision 3) registered as an ordinary device with
`$48` address-select and `$50` mode-select (latching coherency bit) as
its two quirk registers. Devices 0-10 and empty IDSELs read all-ones
because nothing is registered there — not because of a literal. Chaos
its two quirk registers. Mode-select byte 0 bit 0 is the **endian
bit**: set at power-on for the straight byte lanes every Macintosh OS
runs on, cleared by the firmware (`... cdata xb@ 0fe and cdata xb!`)
when it enters a little-endian client, at which point the bridge
reverses its eight byte lanes for everything it forwards — the
pass-through Grand Central island, the PCI memory and I/O windows, and
bus-master DMA on its way to host memory — and its own config address
and data ports with them (the firmware's `set-caddr` stores a natural
one-hot address with `xl!`, which is the byte-flipping variant only in
big-endian mode, so in little-endian mode the flip has to come from the
bridge; a reversed 32-bit access simply reads and writes the
little-endian config dword as-is). That
hardware reversal is the counterpart to the 604's little-endian address
munge: the two cancel, so a little-endian client reaches every PCI-side
device with plain loads and stores. It is what lets Apple's 2.26NT
Open Firmware keep its serial console across the mandatory
little-endian configuration reboot when booting Windows NT. Devices
0-10 and empty IDSELs read all-ones because nothing is registered there
— not because of a literal. Chaos
config space is read-restricted (`$00-$0F`, `$14`, `$18`) and ignores
writes outside its two BAR offsets; both quirks are applied in the
adapter, around the generic dispatch, because they are facts about
Expand Down Expand Up @@ -567,6 +584,25 @@ order does not matter, so the existing `PIXEL_8BPP` path is *correct* rather
than merely convenient. Deeper colour needs a little-endian framebuffer
window in `display_t`, which this repository does not have.

### The registers that are not memory

Open Firmware and AIX both arrive knowing what the part is, so for a long
time a flat byte array per register block was enough. A driver that has to
*find* the chip needs four registers that do not behave like storage, and
Windows NT's `cirrus.sys` reads all four before it will claim the adapter:

| | behaviour | why a driver cares |
|---|---|---|
| `SR06` | writing `$12` unlocks the extensions and reads back `$12`; anything else locks them and reads back `$0F` | the round trip *is* the presence test |
| `CR27` | read-only `$A0`: bits 7:2 the device (CL-GD5430, agreeing with the `$00A0` PCI ID), 1:0 the revision | the chip ID, and which mode tables apply |
| `SR15` | bits 3:0 report the fitted DRAM — `2` is 1 MB | the driver sizes its mode list from this instead of probing, and rejects every mode that will not fit |
| `$3CC` | reads back what `$3C2` (Miscellaneous Output) was given | bit 0 says whether the CRTC pair is at `$3D4/$3D5` or `$3B4/$3B5` |

The monochrome CRTC pair `$3B4/$3B5` is folded onto the colour one whatever
`$3C2` bit 0 says. Software that never writes Miscellaneous Output reads
zero from `$3CC`, goes to the monochrome addresses, and would otherwise find
nothing there.

One VGA register is deliberately not store-and-readback: **Input Status
Register 1** (`$3BA`/`$3DA`). Software does not read it for a value, it
reads it for an *edge* — every VGA console waits on the vertical-retrace or
Expand Down
153 changes: 153 additions & 0 deletions docs/notes/2026-09-05-ans-windows-nt-setupldr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Windows NT 4.0 Setup on the emulated Apple Network Server: through the veneer to the HAL question

*Investigation log, 2026-09-05. Branch `ppc-le-mode-and-bandit-lane-reversal` (PR #135).*

The TinkerDifferent thread [Apple Network Server MacOS-based ROMs found](https://tinkerdifferent.com/threads/apple-network-server-macos-based-roms-found.4756/)
has been trying to boot Windows NT 4.0 for PowerPC on the Apple Network Server 500/700
with the 2.26NT Open Firmware ROM. On real hardware the furthest point reached (post 49785)
is the *Windows NT Setup* banner followed by

```
The file multi(0)scsi(0)cdrom(0)fdisk(0)partition(1) is corrupted.
Press any key to continue.
```

Granny Smith, with the 604 little-endian mode and Bandit byte-lane reversal from PR #135,
reproduces that screen and gets past it. Windows NT Setup now stops at its computer-type
menu — the point where the missing Apple Network Server HAL is what stands in the way:

![Windows NT Setup asking for the computer type, rendered from the ttya stream](../assets/ans-nt-setup-hal-menu.png)

This note records what each wall was and the exact recipe, so that it can be repeated
on the emulator or on a real machine. Everything here was worked out with the veneer's
and SETUPLDR's own symbol tables (both ship as bare COFF images with their COFF symbol
tables intact) and the veneer's built-in debug trace; nothing needed emulator changes
beyond PR #135.

## 1. Starting point: the thread's transcript

Post 49785 is a complete Open Firmware transcript: `setenv little-endian? true`, reboot,
read `VENEER.EXE` (the FirmWorks/Microsoft Open Firmware→ARC veneer from the NT 4.0 CD's
`\PPC\`) off a SCSI disk with `read-blocks`, lay it out with the ROM's `pe-loader`
`init-program`, poke four patches (`51E3C`/`514E0`: `nop` two `claim` calls that fail;
`5CD30`: replace the default `\os\winnt\osloader.exe` with `\PPC\SETUPLDR`; `53DB0`), then
`go`. Replayed in the emulator it ends, as PR #135 recorded, with

```
Booting from 'device-tree(0)partition(1)\PPC\SETUPLDR'
VrOpen returned d
```

## 2. Wall 1: `VrOpen returned d` — an empty `bootpath`

`d` is ARC `ENODEV`. The veneer's `find_boot_dev` reads `/chosen` `bootpath`. Loading the
veneer by hand with `read-blocks` never sets it (only the firmware's `load`/`boot` do, in
`$load`), so `finddevice("")` returns the root node, whose ARC name is the Open Firmware
root's `name`, `device-tree`; `VrOpen` then cannot find a `device-tree(0)` component under
its own ARC root. Fix, at the `0 >` prompt before `go`:

```
" /bandit/53c825@11/sd@0,0" encode-string " bootpath" _chosen (property)
```

(`dev /chosen … property` did **not** take; the internal `(property)` with the `_chosen`
phandle did. Check with `dev /chosen .properties`.) The veneer now derives
`multi(0)scsi(0)cdrom(0)fdisk(0)partition(1)\PPC\SETUPLDR`, opens it, relocates
SETUPLDR's six sections and jumps to it — and SETUPLDR prints the real machine's message.
On a real machine booted with `boot` from the CD, `bootpath` is set by the firmware itself.

## 3. Wall 2: "partition(1) is corrupted" — two causes, both in the veneer's path

**The message comes from SETUPLDR's `BlGenerateDeviceNames`**, caught with breakpoints on
`SlFriendlyError`: `SlFriendlyError(7 = EINVAL, "multi(0)scsi(0)cdrom(0)fdisk(0)partition(1)", setup.c line 416)`.
That routine parses the ARC name lexically — adapters (`multi`, `scsi`), then a controller:
`disk` must be followed by `rdisk`/`fdisk` and then `partition`; `cdrom` must be followed by
`fdisk` and then **the end of the name**. One more token is EINVAL, which `SlFriendlyError`
renders as "The file %s is corrupted". The veneer's `find_boot_dev` appends `partition(1)`
to every boot path — right for a hard disk, wrong for a CD-ROM. Same veneer and SETUPLDR
on the real ANS 700, so this is the hardware wall too, independent of the firmware.

**Behind it, Apple's `disk-label` does not give raw sector access for a partition
argument.** SETUPLDR opens the boot device itself (`VrOpen` appends `:0` when a path has
no partition and no file) and probes it with its own FAT/NTFS/CDFS recognizers: reads of
0x62 bytes at 0, 512 at 0x2000, 528 at 0, 2048 at 0x8000 (the ISO volume descriptor).
Apple's `disk-label` `open`, given any non-empty argument (`0`, `1`, …) with no file name,
detects `CD001` and interposes `iso-9660-files`, whose `open` with no path leaves the
instance on the ISO **root directory** as a pseudo-file (428 bytes on this CD; its `seek`
does not touch `fileposn`, its `read` clamps to `filesize`). The four reads return 98,
330, 0 and 0 bytes and the volume descriptor is never seen. Only an **empty** argument
makes `disk-label` return the raw device, whose `read`/`seek` go through the deblocker
and work at any byte offset.

**Both are fixed by two bytes in `VENEER.EXE`** (image base `0x50000`; file offset =
image − `0x50000` + `0x200`):

| image | file | `.rdata` string | patch | effect |
|---|---|---|---|---|
| `0x5D0C0` | `0xD2C0` | `"partition(1)"` | first byte → `00` | boot name becomes `multi(0)scsi(0)cdrom(0)fdisk(0)\PPC\SETUPLDR` |
| `0x5E168` | `0xE368` | `":0"` | first byte → `00` | the bare-device open passes an empty Open Firmware argument → raw sector access |

At the prompt, with the image laid out (the firmware itself runs little-endian here, so
`c!` takes the program's address):

```
00 5D0C0 c!
00 5E168 c!
```

## 4. Result

```
Booting from 'multi(0)scsi(0)cdrom(0)fdisk(0)\PPC\SETUPLDR'
Windows NT Setup
Setup is loading files (Windows NT Executive)...
Setup could not determine the type of computer you have, or you have
chosen to manually specify the computer type.
IBM Power Series 6015 / 6020,40,42 / 6050,6070 and RS/6000 Model 7248
IBM RS/6000 Model E20/E30/F30 / 45M/H45
MOTOROLA PowerStack / PowerStack2 / Big Bend
Powerized ES, MX, LX, TX (Uniprocessor) / (Multiprocessor)
Other
```

SETUPLDR mounted the CD with its own CDFS over raw ARC reads, parsed the 126 KB
`TXTSETUP.SIF`, and read `\PPC\NTKRNLMP.EXE` in full (84 SCSI READ commands covering
its 666 blocks) before asking. The kernel is in memory but nothing of NT executes: the
kernel imports everything hardware-related from `HAL.DLL`, the loader binds those imports
only after a HAL is chosen, and no HAL on the CD is for an Apple machine. Control is in
SETUPLDR's menu loop, polling the ARC console for a key. That is the wall this exercise
was aiming for.

Full recipe, in order, on the emulator (`ans500`, 64 MB, ROM 2.26NT, NT 4.0 OEM CD on
`/bandit/53c825@11/sd@0,0`, the veneer on a disk at `/bandit/53c825@12/sd@0,0`):

1. `setenv little-endian? true`, `setenv real-mode? false`, `setenv real-base 3F00000`,
`setenv load-base 3E00000`, `reset-all`.
2. The transcript's `read-blocks` / `init-program` / four pokes from post 49785.
3. `bootpath` as in §2.
4. The two `c!` from §3.
5. `go`.

## 5. Things that were *not* the problem

- **The ISO-9660 "reads past 16 KB" defect** noted in PR #135 is real but is the
firmware's own `deblocker`/`iso-9660-files` interplay (the deblocker is left at the
absolute extent and `read-blocks` adds the extent again once `open-ok?` is set); the
firmware's `$load` never seeks, so a bare `load` of a large ISO file is wrong on real
hardware too. The veneer seeks before every read and never hits it, and SETUPLDR
reads raw sectors.
- **The emulator.** Every wall was veneer or firmware behaviour; the little-endian CPU
mode and Bandit lane reversal carried the veneer, the firmware's client interface,
SETUPLDR's CDFS and INF parser and a full kernel image load without a fault.

## 6. What next

- Pick a shipped PReP HAL (e.g. PowerStack) and watch where its first hardware access
faults — the first concrete requirement list for an Apple Network Server HAL.
- The nearest existing code is MCJack123's [maciNTosh-bandit](https://github.com/MCJack123/maciNTosh-bandit)
(NT on Old World Bandit/Hammerhead/Grand Central Power Macs), which lacks a PCI SCSI
driver — the Network Server's only disk path.

Sources: thread posts 43745 (joevt's detokenized 2.26NT Open Firmware source), 49404,
49460, 49785, 49786; the veneer's and SETUPLDR's embedded COFF symbol tables
(`D:\nt\private\ntos\boot\veneer\vr*.c`, `D:\nt\private\ntos\boot\setup\setup.c`).
10 changes: 9 additions & 1 deletion src/core/cpu/ppc/ppc.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ void ppc_exception(ppc_t *p, uint32_t vector, uint32_t srr1_hi, uint32_t resume_
ppc_context_sync(p); // taking an exception is context-synchronizing
p->srr0 = resume_pc;
p->srr1 = (srr1_hi & 0xFFFF0000u) | (p->msr & 0x0000FFFFu);
p->msr &= ppc_msr_exception_keep(p);
// LE is replaced by a copy of ILE (PEM Table 6-x "MSR settings on
// exception"): the handler runs in the endianness the OS asked for.
// ILE is masked to zero on the 601, so this is a no-op there.
uint32_t le = (p->msr & PPC_MSR_ILE) ? PPC_MSR_LE : 0u;
p->msr = (p->msr & ppc_msr_exception_keep(p)) | le;
ppc_update_active_maps(p);
p->pc = ((p->msr & PPC_MSR_EP) ? 0xFFF00000u : 0u) + vector;
// Record in the shared exception trace ring (§3.9c field mapping:
Expand Down Expand Up @@ -868,6 +872,10 @@ static int ppc_dbgif_disasm(void *ctx, uint32_t pc, char *buf) {
ppc_t *p = (ppc_t *)ctx;
bool ok;
uint32_t pa = ppc_mmu_translate_debug(p, pc, false, &ok);
// LE mode: show the word the CPU fetches (the fetch munge, pc ^ 4), so a
// listing of little-endian code reads in program order.
if (p->msr & PPC_MSR_LE)
pa ^= 4u;
ppc_insn ins;
ppc_disassemble_model(ok ? memory_debug_read_uint32(pa) : 0, pc, p->cpu_model, &ins);
// debug.c splits on '\t'; ppc_disasm emits "mnemonic\toperands" already.
Expand Down
Loading
Loading