[GH-ISSUE #79] ppc: PPCSerialPort finds no device on QEMU g3beige (OpenBIOS lists one SCC interrupt, not three) #70

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

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

On QEMU g3beige (Gossamer, booted with vm/ppc-guest.py start --machine g3beige on branch ppc-qemu), /etc/startup/0700_Devices reports:

Using Default table for PPCSerialPort
_IOProbeDriver: No Such Device, device PPCSerialPort unit 0

so the Beige G3 guest has no serial ports.

Cause

OpenBIOS gives the SCC channels a single interrupt on g3beige:

/pci/mac-io/escc/ch-a
  AAPL,interrupts   f        (SCC A, Heathrow bit 15)

A real old-world Mac lists three interrupts per channel: the SCC itself, then its TX and RX DBDMA interrupts. PPCSerialPort -initFromDeviceDescription: refuses any node without exactly three (wrong number of interrupts), and so does the stock 1.2v3 PPCSerialPort_reloc. Its getHandler:level:argument:forInterrupt: hands out PPCSerialISR for interrupt 0, PPCSerialTxDMAISR for 1 and PPCSerialRxDMAISR for 2.

This is a gap in QEMU's old-world firmware, not a driver bug. On mac99 the channels carry all three (MPIC sources 0x25/0x04/0x05 for ch-a), and serial works both ways there: host-to-guest and guest-to-host over /dev/cuaa were checked with a TCP serial backend. QEMU's ESCC has no DBDMA behind it, so the DMA interrupts never fire there either.

Proposed fix

Let our src/drivers-ppc/input/drvPPCSerialPort accept a channel with only the SCC interrupt and run without the DMA ISRs, as a labelled divergence from 5.6. Then build it and install it over the stock PPCSerialPort_reloc in a g3beige guest to test.

Done when the g3beige guest registers PPCSerialPort0 [ch-a] and /dev/cuaa passes data both ways over QEMU's serial backend.

Related: #63.

Originally created by @raynorpat on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/79 On QEMU `g3beige` (Gossamer, booted with `vm/ppc-guest.py start --machine g3beige` on branch `ppc-qemu`), `/etc/startup/0700_Devices` reports: ``` Using Default table for PPCSerialPort _IOProbeDriver: No Such Device, device PPCSerialPort unit 0 ``` so the Beige G3 guest has no serial ports. ## Cause OpenBIOS gives the SCC channels a single interrupt on g3beige: ``` /pci/mac-io/escc/ch-a AAPL,interrupts f (SCC A, Heathrow bit 15) ``` A real old-world Mac lists three interrupts per channel: the SCC itself, then its TX and RX DBDMA interrupts. `PPCSerialPort -initFromDeviceDescription:` refuses any node without exactly three (`wrong number of interrupts`), and so does the stock 1.2v3 `PPCSerialPort_reloc`. Its `getHandler:level:argument:forInterrupt:` hands out `PPCSerialISR` for interrupt 0, `PPCSerialTxDMAISR` for 1 and `PPCSerialRxDMAISR` for 2. This is a gap in QEMU's old-world firmware, not a driver bug. On `mac99` the channels carry all three (MPIC sources 0x25/0x04/0x05 for ch-a), and serial works both ways there: host-to-guest and guest-to-host over `/dev/cuaa` were checked with a TCP serial backend. QEMU's ESCC has no DBDMA behind it, so the DMA interrupts never fire there either. ## Proposed fix Let our `src/drivers-ppc/input/drvPPCSerialPort` accept a channel with only the SCC interrupt and run without the DMA ISRs, as a labelled divergence from 5.6. Then build it and install it over the stock `PPCSerialPort_reloc` in a g3beige guest to test. Done when the g3beige guest registers `PPCSerialPort0 [ch-a]` and `/dev/cuaa` passes data both ways over QEMU's serial backend. Related: #63.
Author
Owner

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

Status: this is blocked on finishing the PPCSerialPort reconstruction.

Every serial test so far ran Apple's stock PPCSerialPort_reloc from the 1.2v3 disk. Our source under src/drivers-ppc/input/drvPPCSerialPort has never been built: src/drivers-ppc/reconstruction/PPCSerialPort/findings.md says so ("nothing was compiled"). Trying to build it now shows that:

  • It doesn't compile. The first pass stops on 27 errors, all syntax:

    • 22 asm clobber lists written as :::, which the old ObjC front end reads as a :: token;
    • declarations after statements (lines 734, 1447, 1894);
    • undefined types IOPPCDeviceDescription and IOMemoryRange.

    Semantic errors will follow once these are fixed.

  • It isn't functional. About 54 places are placeholders or guesses rather than decompiled code:

    • nextEvent returns nothing;
    • the dequeue/enqueue event paths "do not implement actual event queue functionality";
    • locking and the port frees in -free are stubbed;
    • thread_wakeup is stood in for.

Done on master (merged with ppc-qemu in 3c6f465d3), from 6ef37d552:

  • apk/pkginfo was a copy of the bus mouse driver's (pkgname = drvbusmouse). It is now drvppcserialport (version 5), so rbuild can build it as its own package. It builds against a repository that also holds the ppc drivertools/driverkit/kernload apks.
  • -initFromDeviceDescription: accepts a channel with just the SCC interrupt as well as the usual three, as a labelled divergence from 5.6. Its TX/RX DMA handlers only count interrupts, so nothing is lost without them.

Next:

  1. Finish the reconstruction until it compiles, filling the placeholders from the stock binary. The IDA reference analysis is in tools/binrecon/out/ppcserialport-ppc.
  2. Swap it in for the stock PPCSerialPort_reloc on QEMU mac99 and match the stock driver: /dev/cuaa both ways over a TCP serial backend. The test harness I used (sertcp.py + rxtest.py) is local scratch, not in the repo yet: it runs vm/ppc-guest.py start with -serial tcp:... and talks to /dev/cuaa over telnet.
  3. Then g3beige, which this issue is about.
<!-- gh-comment-id:6377720501 --> @raynorpat commented on GitHub (Oct 24, 2026): Status: this is blocked on finishing the PPCSerialPort reconstruction. Every serial test so far ran Apple's stock `PPCSerialPort_reloc` from the 1.2v3 disk. Our source under `src/drivers-ppc/input/drvPPCSerialPort` has never been built: `src/drivers-ppc/reconstruction/PPCSerialPort/findings.md` says so ("nothing was compiled"). Trying to build it now shows that: - **It doesn't compile.** The first pass stops on 27 errors, all syntax: - 22 `asm` clobber lists written as `:::`, which the old ObjC front end reads as a `::` token; - declarations after statements (lines 734, 1447, 1894); - undefined types `IOPPCDeviceDescription` and `IOMemoryRange`. Semantic errors will follow once these are fixed. - **It isn't functional.** About 54 places are placeholders or guesses rather than decompiled code: - `nextEvent` returns nothing; - the dequeue/enqueue event paths "do not implement actual event queue functionality"; - locking and the port frees in `-free` are stubbed; - `thread_wakeup` is stood in for. Done on master (merged with `ppc-qemu` in `3c6f465d3`), from `6ef37d552`: - `apk/pkginfo` was a copy of the bus mouse driver's (`pkgname = drvbusmouse`). It is now `drvppcserialport` (version 5), so rbuild can build it as its own package. It builds against a repository that also holds the ppc `drivertools`/`driverkit`/`kernload` apks. - `-initFromDeviceDescription:` accepts a channel with just the SCC interrupt as well as the usual three, as a labelled divergence from 5.6. Its TX/RX DMA handlers only count interrupts, so nothing is lost without them. Next: 1. Finish the reconstruction until it compiles, filling the placeholders from the stock binary. The IDA reference analysis is in `tools/binrecon/out/ppcserialport-ppc`. 2. Swap it in for the stock `PPCSerialPort_reloc` on QEMU mac99 and match the stock driver: `/dev/cuaa` both ways over a TCP serial backend. The test harness I used (`sertcp.py` + `rxtest.py`) is local scratch, not in the repo yet: it runs `vm/ppc-guest.py start` with `-serial tcp:...` and talks to `/dev/cuaa` over telnet. 3. Then g3beige, which this issue is about.
Author
Owner

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

The PPCSerialPort reconstruction is finished on branch ppcserial-finish (c5d765b43, local, not merged yet). On QEMU g3beige the driver now registers both channels and passes data both ways.

How it was rebuilt. The old draft was replaced with source written from IDA's decompilation of the 1.2v3 PPCSerialPort_reloc. Calls were resolved through the object's relocation table, and the code is typed with the Port struct taken from the binary's own ivar encoding.

  • Class: PPCSerialPort : IODirectDevice <PortDevices>, instance size 664, with Port at offset 296 and port at 660.
  • The built driver's ivar type encoding is byte-identical to Apple's (1312 bytes).
  • Its undefined-symbol set is identical to Apple's, and the kernel exports all of it.
  • Several of the old "placeholders" turned out to be faithful: Apple's nextEvent, enqueueEvent: and dequeueEvent: really are stubs.

Build fixes. The project used a made-up tool.make/Makefile.driver_preamble setup that produced PIC code linked -static. It now uses the same kernelserver.make/driver.make Makefiles as the other ppc drivers, plus a movehelp no-op, since the bundle has no help.

Divergences from 5.6, each marked in the source:

  • Accept a channel with only the SCC interrupt. This is the g3beige fix; the DMA ISRs only count interrupts.
  • PPCSerialISR no longer closes the channel when its 100-poll budget runs out. QEMU's ESCC empties its transmitter as fast as it's fed, so the stock driver shut the port after 101 bytes and then spun in enqueueData:. The stock binary does exactly this on QEMU.
  • enqueueData:/dequeueData: report the whole transfer in transferCount, not just the last chunk.
  • dequeueData: resumes the sender with XON; the binary queues XOFF.
  • RemovefromQueue checks its bound before taking a byte. The binary writes one byte past the buffer and drops one when more is queued than requested.

Tested on QEMU with the master kernel and the guest's serial port on a TCP socket:

mac99 g3beige
Registers PPCSerialPort0 [ch-a] / 1 [ch-b] yes yes (stock: No Such Device)
/dev/cuaa host → guest, guest → host yes yes
20,000-byte write arrives intact yes (stock: stops at 101 bytes, guest hangs) yes

Known limit. Receive bursts larger than about 800 bytes, RXStats.HighWater, stop when the sender ignores XOFF: ours took 1,020 bytes, stock 935 while flooding XOFF. Stock has the same limit, and a sender that honours XON/XOFF shouldn't hit it. Not hardware-tested.

<!-- gh-comment-id:6378879407 --> @raynorpat commented on GitHub (Oct 24, 2026): The PPCSerialPort reconstruction is finished on branch `ppcserial-finish` (`c5d765b43`, local, not merged yet). On QEMU g3beige the driver now registers both channels and passes data both ways. **How it was rebuilt.** The old draft was replaced with source written from IDA's decompilation of the 1.2v3 `PPCSerialPort_reloc`. Calls were resolved through the object's relocation table, and the code is typed with the `Port` struct taken from the binary's own ivar encoding. - Class: `PPCSerialPort : IODirectDevice <PortDevices>`, instance size 664, with `Port` at offset 296 and `port` at 660. - The built driver's ivar type encoding is **byte-identical** to Apple's (1312 bytes). - Its undefined-symbol set is identical to Apple's, and the kernel exports all of it. - Several of the old "placeholders" turned out to be faithful: Apple's `nextEvent`, `enqueueEvent:` and `dequeueEvent:` really are stubs. **Build fixes.** The project used a made-up `tool.make`/`Makefile.driver_preamble` setup that produced PIC code linked `-static`. It now uses the same `kernelserver.make`/`driver.make` Makefiles as the other ppc drivers, plus a `movehelp` no-op, since the bundle has no help. **Divergences from 5.6**, each marked in the source: - Accept a channel with only the SCC interrupt. This is the g3beige fix; the DMA ISRs only count interrupts. - `PPCSerialISR` no longer closes the channel when its 100-poll budget runs out. QEMU's ESCC empties its transmitter as fast as it's fed, so the stock driver shut the port after 101 bytes and then spun in `enqueueData:`. The stock binary does exactly this on QEMU. - `enqueueData:`/`dequeueData:` report the whole transfer in `transferCount`, not just the last chunk. - `dequeueData:` resumes the sender with XON; the binary queues XOFF. - `RemovefromQueue` checks its bound before taking a byte. The binary writes one byte past the buffer and drops one when more is queued than requested. **Tested on QEMU** with the master kernel and the guest's serial port on a TCP socket: | | mac99 | g3beige | |---|---|---| | Registers `PPCSerialPort0 [ch-a]` / `1 [ch-b]` | yes | yes (stock: `No Such Device`) | | `/dev/cuaa` host → guest, guest → host | yes | yes | | 20,000-byte write arrives intact | yes (stock: stops at 101 bytes, guest hangs) | yes | **Known limit.** Receive bursts larger than about 800 bytes, `RXStats.HighWater`, stop when the sender ignores XOFF: ours took 1,020 bytes, stock 935 while flooding XOFF. Stock has the same limit, and a sender that honours XON/XOFF shouldn't hit it. Not hardware-tested.
Author
Owner

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

Merged into master locally as c5d765b43 (not pushed yet). The receive-burst limit noted above, about 800 bytes when the sender ignores XOFF, is shared with the stock driver and left as is.

<!-- gh-comment-id:6378952540 --> @raynorpat commented on GitHub (Oct 24, 2026): Merged into master locally as `c5d765b43` (not pushed yet). The receive-burst limit noted above, about 800 bytes when the sender ignores XOFF, is shared with the stock driver and left as is.
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#70
No description provided.