[GH-ISSUE #61] ppc: NVRAM address wrap and hardcoded MPIC table break New World / QEMU mac99 boots #52

Closed
opened 2026-11-03 22:03:16 -07:00 by gitea-mirror · 4 comments
Owner

Originally created by @raynorpat on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/61

Booting the Mac OS X Server 1.2v3 ppc disk (kernel 5.6) on QEMU's mac99 machine (OpenBIOS, reported as a "Sawtooth" G4) hits two problems. Both are in code we carry in our tree.

1. /dev/nvram address wraps past 4 GB

src/kernel-7/bsd/dev/ppc/nvram.c (nvprobe, lines 86–95) computes the NVRAM address as PCI_IO_BASE_PHYS + regs[0].address. That works on old-world Macs, where the nvram node sits under mac-io and reg is relative. On New World firmware (and QEMU/OpenBIOS) the node is /nvram@fff04000, with an absolute reg of fff04000 4000. The sum wraps to 0x7ff04000.

What happens: /etc/startup/0200_Tune runs nvram boot-args, and the kernel takes Unresolved kernel trap: 0x0300 Data access DSISR=0x40000000 DAR=0x7ff04000 in the device read (read → spec_read → nvram read).

The PE's own ReadNVRAM path (called during DeviceTreeProbe) uses reg as an absolute address. Rewriting the OF property to 7ff04000 to satisfy nvprobe therefore makes ReadNVRAM fault on the same address instead. The two consumers disagree, so this has to be fixed in the kernel, not with a device-tree tweak.

Fix: treat reg as absolute when the node is not under mac-io (or is compatible "nvram,flash"). New World flash NVRAM probably needs the PE's NVRAM access path rather than the byte-stride nvram_data[offset << 4] accessor.

2. PCI interrupts outside the hardcoded Sawtooth MPIC table

src/drivers-ppc/bus/drvPExpert/powermac/families/sawtooth.c hardcodes PCI slots at MPIC sources 21–24 and marks 25–31 reserved. Nothing in kernel-7 or drivers-ppc reads interrupt-map. QEMU's UniNorth gives PCI children interrupts = 1 (the pin) plus an interrupt-map routing it to MPIC 0x1b–0x1e. Results:

  • -device sungem, and the default USB OHCI: endless {MPIC INT 30} from mpic_interrupt, with no handler for the source.
  • -device tulip (DEC 21143): panic: mpic_register_int: Interrupt 136 not found during probeNativeDevices. This happens even after rewriting the node's interrupts to 1d 1 at the OF prompt.
  • QEMU's default ATAPI CD-ROM on the second ata-3 bus: {MPIC INT 14} while hc1 says it registered irq 22, and the probe stalls.

Fix: build the MPIC device table from the device tree (resolving interrupt-map for PCI children) instead of trusting a per-family hardcoded table, or at least map unknown sources to the device that owns them.

Repro

vm/ppc-guest.py (coming with the ppc QEMU guest setup) boots this image on mac99,usb=off -nodefaults. It works around problem 1 by replacing nvram with false in 0200_Tune on the guest's overlay disk. Problem 2 currently leaves the guest with no working NIC.

Originally created by @raynorpat on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/61 Booting the Mac OS X Server 1.2v3 ppc disk (kernel 5.6) on QEMU's `mac99` machine (OpenBIOS, reported as a "Sawtooth" G4) hits two problems. Both are in code we carry in our tree. ## 1. `/dev/nvram` address wraps past 4 GB `src/kernel-7/bsd/dev/ppc/nvram.c` (`nvprobe`, lines 86–95) computes the NVRAM address as `PCI_IO_BASE_PHYS + regs[0].address`. That works on old-world Macs, where the nvram node sits under mac-io and `reg` is relative. On New World firmware (and QEMU/OpenBIOS) the node is `/nvram@fff04000`, with an absolute `reg` of `fff04000 4000`. The sum wraps to `0x7ff04000`. What happens: `/etc/startup/0200_Tune` runs `nvram boot-args`, and the kernel takes `Unresolved kernel trap: 0x0300 Data access DSISR=0x40000000 DAR=0x7ff04000` in the device read (`read` → `spec_read` → nvram read). The PE's own `ReadNVRAM` path (called during `DeviceTreeProbe`) uses `reg` as an absolute address. Rewriting the OF property to `7ff04000` to satisfy `nvprobe` therefore makes `ReadNVRAM` fault on the same address instead. The two consumers disagree, so this has to be fixed in the kernel, not with a device-tree tweak. Fix: treat `reg` as absolute when the node is not under mac-io (or is `compatible "nvram,flash"`). New World flash NVRAM probably needs the PE's NVRAM access path rather than the byte-stride `nvram_data[offset << 4]` accessor. ## 2. PCI interrupts outside the hardcoded Sawtooth MPIC table `src/drivers-ppc/bus/drvPExpert/powermac/families/sawtooth.c` hardcodes PCI slots at MPIC sources 21–24 and marks 25–31 reserved. Nothing in kernel-7 or drivers-ppc reads `interrupt-map`. QEMU's UniNorth gives PCI children `interrupts = 1` (the pin) plus an `interrupt-map` routing it to MPIC `0x1b–0x1e`. Results: - `-device sungem`, and the default USB OHCI: endless `{MPIC INT 30}` from `mpic_interrupt`, with no handler for the source. - `-device tulip` (DEC 21143): `panic: mpic_register_int: Interrupt 136 not found` during `probeNativeDevices`. This happens even after rewriting the node's `interrupts` to `1d 1` at the OF prompt. - QEMU's default ATAPI CD-ROM on the second `ata-3` bus: `{MPIC INT 14}` while hc1 says it registered irq 22, and the probe stalls. Fix: build the MPIC device table from the device tree (resolving `interrupt-map` for PCI children) instead of trusting a per-family hardcoded table, or at least map unknown sources to the device that owns them. ## Repro `vm/ppc-guest.py` (coming with the ppc QEMU guest setup) boots this image on `mac99,usb=off -nodefaults`. It works around problem 1 by replacing `nvram` with `false` in `0200_Tune` on the guest's overlay disk. Problem 2 currently leaves the guest with no working NIC.
Author
Owner

@raynorpat commented on GitHub (Oct 17, 2026):

Update: branch ppc-qemu fixes both problems for our kernel. A RhapsodiOS ppc kernel built from it boots the Mac OS X Server 1.2v3 disk on QEMU mac99 through multi-user to the Setup Assistant. Cuda/ADB, the display, ATA root and driverLoader all work.

A correction first: the issue body says nothing in our tree reads interrupt-map. That was wrong. driverkit-3/libDriver/ppc/IODeviceTreeBus.m does, but IOTreeDevice only used it when IsYosemite(), and it assumed one-cell interrupt specifiers.

What it took, in commit order:

  • nvram (kernel) 1222e3954: nvprobe sends Sawtooth through ReadNVRAM/WriteNVRAM (NVRAM_PMU), which already handle Core99 flash NVRAM.
  • DTFindChildEntry (kernel) ade4b9620: an allocation-free subtree search next to DTFindEntry.
  • interrupt-map (driverkit) b25b3c196: New World means IsYosemite() || IsSawtooth(). The mapper reads the controller's real #interrupt-cells (2 on an MPIC), stops at the controller, and keeps only the source cell. The tulip now gets MPIC source 0x1d.
  • KeyLargo (drvPExpert) e7cf0f798: keylargo_find_i2c ran from configure_machine, before kalloc_init(). It used a kalloc'd DT iterator, and the free blocked in kmem_free.
  • Addresses (drvPExpert) eb4b43dd9:
    • identify_machine2() added the mac-io base to the absolute /nvram@fff04000 reg; it wrapped to 0x7ff04000 in InitCore99NVRAM.
    • get_int_cntlr_base_addr() returned the open-pic reg (0x40000, mac-io relative) as-is. Every MPIC access hit low RAM, and FM_MPIC_CTRL (memory controller base 0 on Sawtooth) scribbled at 0x160 in the vector page. 5.6 adds the mac-io base on Sawtooth and only touches FM on Power Express.
  • MPIC (drvPExpert) 86f2501c5: mpic_interrupt_initialize now follows the 5.6 kernel: reset, mask all, spurious 0xff, cascade, then load the table. sawtooth_int_mapping_tbl now matches the 5.6 binary: priority 8, level/active-low for 16–18 and 22–63 (VIA cascade and PCI included). The old table had the cascade edge-triggered.

Still open:

  • /etc/startup/0700_Devices loads the Floppy driver, whose PMAC_DEV_FLOPPY isn't in the Sawtooth MPIC table (in 5.6 either), so mpic_register_int panics "Interrupt 136 not found". vm/ppc-guest.py comments it out on the guest disk.
  • Our sawtooth_interrupts device numbering still differs from 5.6's table. It doesn't block anything, because DT-routed drivers resolve through the same table.
  • Neither QEMU NIC passes traffic with the stock 1.2v3 drivers yet. The tulip (DEC21x4, with pci1011,19 added to its match) reads zeroed TX descriptors. drvPPCGem (with gmac added) spins on the GEM status register. Being looked at next.
  • The CPU/bus calibration is #62.
<!-- gh-comment-id:6242460478 --> @raynorpat commented on GitHub (Oct 17, 2026): Update: branch `ppc-qemu` fixes both problems for our kernel. A RhapsodiOS ppc kernel built from it boots the Mac OS X Server 1.2v3 disk on QEMU `mac99` through multi-user to the Setup Assistant. Cuda/ADB, the display, ATA root and driverLoader all work. A correction first: the issue body says nothing in our tree reads `interrupt-map`. That was wrong. `driverkit-3/libDriver/ppc/IODeviceTreeBus.m` does, but `IOTreeDevice` only used it when `IsYosemite()`, and it assumed one-cell interrupt specifiers. What it took, in commit order: - **nvram (kernel)** `1222e3954`: `nvprobe` sends Sawtooth through `ReadNVRAM`/`WriteNVRAM` (`NVRAM_PMU`), which already handle Core99 flash NVRAM. - **DTFindChildEntry (kernel)** `ade4b9620`: an allocation-free subtree search next to `DTFindEntry`. - **interrupt-map (driverkit)** `b25b3c196`: New World means `IsYosemite() || IsSawtooth()`. The mapper reads the controller's real `#interrupt-cells` (2 on an MPIC), stops at the controller, and keeps only the source cell. The tulip now gets MPIC source 0x1d. - **KeyLargo (drvPExpert)** `e7cf0f798`: `keylargo_find_i2c` ran from `configure_machine`, before `kalloc_init()`. It used a kalloc'd DT iterator, and the free blocked in `kmem_free`. - **Addresses (drvPExpert)** `eb4b43dd9`: - `identify_machine2()` added the mac-io base to the absolute `/nvram@fff04000` reg; it wrapped to 0x7ff04000 in `InitCore99NVRAM`. - `get_int_cntlr_base_addr()` returned the open-pic `reg` (0x40000, mac-io relative) as-is. Every MPIC access hit low RAM, and `FM_MPIC_CTRL` (memory controller base 0 on Sawtooth) scribbled at 0x160 in the vector page. 5.6 adds the mac-io base on Sawtooth and only touches FM on Power Express. - **MPIC (drvPExpert)** `86f2501c5`: `mpic_interrupt_initialize` now follows the 5.6 kernel: reset, mask all, spurious 0xff, cascade, then load the table. `sawtooth_int_mapping_tbl` now matches the 5.6 binary: priority 8, level/active-low for 16–18 and 22–63 (VIA cascade and PCI included). The old table had the cascade edge-triggered. Still open: - `/etc/startup/0700_Devices` loads the Floppy driver, whose `PMAC_DEV_FLOPPY` isn't in the Sawtooth MPIC table (in 5.6 either), so `mpic_register_int` panics "Interrupt 136 not found". `vm/ppc-guest.py` comments it out on the guest disk. - Our `sawtooth_interrupts` device numbering still differs from 5.6's table. It doesn't block anything, because DT-routed drivers resolve through the same table. - Neither QEMU NIC passes traffic with the stock 1.2v3 drivers yet. The tulip (DEC21x4, with `pci1011,19` added to its match) reads zeroed TX descriptors. drvPPCGem (with `gmac` added) spins on the GEM status register. Being looked at next. - The CPU/bus calibration is #62.
Author
Owner

@raynorpat commented on GitHub (Oct 18, 2026):

Networking now works on branch ppc-qemu. vm/ppc-guest.py run telnets into the guest on our kernel about six minutes after a fresh boot: Rhapsody 5.6, en0 10.0.2.15, default route via 10.0.2.2.

The blocker was in our driverkit, not the NIC drivers. IOMacRiscPCIBridge -setConfigAddress:offset: always accessed configData + 0. UniNorth, unlike Bandit/Chaos, takes offset bit 2 from the data access rather than the address cycle; Linux handles this with cfg_data + (offset & 7) under has_uninorth. So every config register with bit 2 set hit the wrong register. The NIC drivers' write of 0x16 to the command register (offset 4) landed on offset 0, bus mastering was never enabled, and DMA read zeros. That explains both the tulip's and sungem's "zeroed descriptors". Fixed in d4e78b61d. It applies to real Sawtooth hardware too.

The guest disk also needed two patches to the stock drvPPCGem loadable:

  • Its match gains gmac, so it drives QEMU's sungem.
  • Its interrupt mask keeps the RXMAC summary bit masked. QEMU's sungem raises RXMAC despite MAC_RXMASK, and the driver never clears it, so it caused an interrupt storm.

The longer-term route is to build drvUniNEnet (Apple's GMAC driver, which 5.6 has built in) into our kernel; see #63.

<!-- gh-comment-id:6244041676 --> @raynorpat commented on GitHub (Oct 18, 2026): Networking now works on branch `ppc-qemu`. `vm/ppc-guest.py run` telnets into the guest on our kernel about six minutes after a fresh boot: `Rhapsody 5.6`, `en0` 10.0.2.15, default route via 10.0.2.2. The blocker was in our driverkit, not the NIC drivers. `IOMacRiscPCIBridge -setConfigAddress:offset:` always accessed `configData + 0`. UniNorth, unlike Bandit/Chaos, takes offset bit 2 from the data access rather than the address cycle; Linux handles this with `cfg_data + (offset & 7)` under `has_uninorth`. So every config register with bit 2 set hit the wrong register. The NIC drivers' write of `0x16` to the command register (offset 4) landed on offset 0, bus mastering was never enabled, and DMA read zeros. That explains both the tulip's and sungem's "zeroed descriptors". Fixed in d4e78b61d. It applies to real Sawtooth hardware too. The guest disk also needed two patches to the stock `drvPPCGem` loadable: - Its match gains `gmac`, so it drives QEMU's sungem. - Its interrupt mask keeps the RXMAC summary bit masked. QEMU's sungem raises RXMAC despite `MAC_RXMASK`, and the driver never clears it, so it caused an interrupt storm. The longer-term route is to build `drvUniNEnet` (Apple's GMAC driver, which 5.6 has built in) into our kernel; see #63.
Author
Owner

@raynorpat commented on GitHub (Oct 18, 2026):

Progress on branch ppc-qemu since the last update (still local, not pushed):

  • sawtooth_interrupts matches 5.6 (c14acd573), source for source. The "numbering still differs" item above is closed.
  • GMAC without patched loadables: UniNEnet is built into the kernel (b6e11b19d), so vm/ppc-guest.py no longer patches drvPPCGem (326603484). en0 comes up at 100 Mb full duplex on QEMU's sungem, and telnet works.
  • Floppy: the kernel skips the Floppy probe when there is no swim3, and an unknown interrupt now warns instead of panicking (97dc15f7f).
  • Clocks (#62): New World machines take CPU and bus clocks from Open Firmware, so boot to the Setup Assistant takes about 75 s instead of 3–7 minutes.

The two bugs this issue covers, NVRAM wrap and the MPIC table/base, are fixed on the branch. Leaving it open until ppc-qemu is merged.

<!-- gh-comment-id:6254380315 --> @raynorpat commented on GitHub (Oct 18, 2026): Progress on branch `ppc-qemu` since the last update (still local, not pushed): - **sawtooth_interrupts matches 5.6** (c14acd573), source for source. The "numbering still differs" item above is closed. - **GMAC without patched loadables**: UniNEnet is built into the kernel (b6e11b19d), so `vm/ppc-guest.py` no longer patches `drvPPCGem` (326603484). `en0` comes up at 100 Mb full duplex on QEMU's sungem, and telnet works. - **Floppy**: the kernel skips the Floppy probe when there is no swim3, and an unknown interrupt now warns instead of panicking (97dc15f7f). - **Clocks** (#62): New World machines take CPU and bus clocks from Open Firmware, so boot to the Setup Assistant takes about 75 s instead of 3–7 minutes. The two bugs this issue covers, NVRAM wrap and the MPIC table/base, are fixed on the branch. Leaving it open until `ppc-qemu` is merged.
Author
Owner

@raynorpat commented on GitHub (Oct 23, 2026):

Closing. Everything this issue covers is fixed on branch ppc-qemu (not pushed yet):

  • NVRAM wrap: 1222e3954, eb4b43dd9.
  • MPIC base and table: eb4b43dd9, 86f2501c5, c14acd573.
  • Sawtooth interrupt-map: b25b3c196.
  • UniNorth config cycles: d4e78b61d.

Our ppc kernel boots the 1.2v3 disk on QEMU mac99 to multi-user, and en0 (built-in UniNEnet on the sungem) carries telnet.

The same branch also boots QEMU g3beige (Gossamer). There the built-in DEC21x4 drives the tulip, and telnet and ping work. That needed 79679149d (match pci1011,19) and 2f4326b96, a Heathrow handler fix: it now dispatches level-triggered PCI slot interrupts that stay asserted. Otherwise a held card interrupt storms with nothing dispatched. vm/ppc-guest.py start --machine g3beige boots it.

<!-- gh-comment-id:6363335847 --> @raynorpat commented on GitHub (Oct 23, 2026): Closing. Everything this issue covers is fixed on branch `ppc-qemu` (not pushed yet): - **NVRAM wrap:** `1222e3954`, `eb4b43dd9`. - **MPIC base and table:** `eb4b43dd9`, `86f2501c5`, `c14acd573`. - **Sawtooth interrupt-map:** `b25b3c196`. - **UniNorth config cycles:** `d4e78b61d`. Our ppc kernel boots the 1.2v3 disk on QEMU `mac99` to multi-user, and `en0` (built-in UniNEnet on the sungem) carries telnet. The same branch also boots QEMU `g3beige` (Gossamer). There the built-in DEC21x4 drives the tulip, and telnet and ping work. That needed `79679149d` (match `pci1011,19`) and `2f4326b96`, a Heathrow handler fix: it now dispatches level-triggered PCI slot interrupts that stay asserted. Otherwise a held card interrupt storms with nothing dispatched. `vm/ppc-guest.py start --machine g3beige` boots it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/RhapsodiOS#52
No description provided.