Skip to content

Theory Open Firmware

pappadf edited this page Sep 14, 2026 · 1 revision

Open Firmware

This page owns: Open Firmware as NT meets it — the device tree, the client interface, and the path grammar that decides whether you get a file or raw sectors.

This is not a specification primer. IEEE 1275 is a large document and most of it does not come up here. What follows is the part of Open Firmware that a Windows NT boot actually touches, written from the behaviour of Apple's real firmware on a real machine rather than from the standard.


1. What it is

Open Firmware is the boot firmware Apple used on PowerPC Macs, standardised as IEEE 1275. Two things distinguish it from a PC BIOS, and both matter here.

It is a Forth interpreter. The firmware is written in Forth, keeps a Forth dictionary, and will happily give you an interactive prompt on a serial console. Typing at that prompt is a first-class way to find things out, and this project has used it to settle questions that would otherwise have taken days.

It describes the machine as a tree, not as a set of fixed addresses. There is no "the keyboard controller is at port 0x60". There is a node, with properties, at a path.

On the Network Server it identifies itself as Open Firmware 2.26NT — a version Apple built with NT in mind, which is why the machine has any chance at all.


2. The device tree

The machine is a tree of nodes. Each has a name, properties, and sometimes methods.

/
├── cpus/
├── memory@0
├── bandit@F2000000              ← the first PCI host bridge
│   ├── 53c825@11                ← a SCSI controller
│   ├── 53c825@12                ← the other one
│   │   └── sd@0,0               ← a disk on it
│   ├── gc@10                    ← Grand Central
│   │   ├── escc@13000
│   │   │   └── ch-a@13020       ← the serial console
│   │   └── via-cuda@16000
│   └── 54m30@F                  ← the video card
├── bandit@F4000000              ← the second bridge
└── chosen                       ← "what was selected", including bootpath

A path such as /bandit@F2000000/53c825@12/sd@0,0 names a node. The @ part is a unit address, interpreted by the parent — for a PCI bridge it is a device number, for a SCSI controller a target and LUN.

/chosen is worth knowing by name: it is where the firmware records what it selected, including bootpath — the device it is going to boot from. Redirecting a boot is largely a matter of setting that property, and this project does exactly that to point at a hard disk rather than the CD.


3. Packages, instances and methods

A node's behaviour comes from a package: a set of methods (open, close, read, write, seek, load) written in Forth. Opening a device creates an instance of the package, and you talk to the instance.

Two consequences that come up constantly:

Instances are a finite resource. Every open must be matched by a close. The veneer had a bug where every close failed, twelve instances leaked, and the CD simply stopped opening — a failure with no message that looked like a media fault.

A package can be layered. When you open a disk with a filename, the firmware instantiates a filesystem package on top of the block device. When you open it without one, it does not. That distinction is the subject of §5, and it is the single most consequential detail on this page.


4. The client interface

A program the firmware has loaded — the veneer, in our case — does not call Forth words directly. It calls the client interface: a single entry point taking a small argument array, with services named by string.

Service What it does
finddevice path → phandle (a node)
getprop read a property
open path → ihandle (an instance)
read, write, seek on an instance
close release an instance
claim, release memory
call-method invoke a package method by name
exit give up and return to the prompt

Everything the veneer does for NT is built from these. When NT's loader asks ARC to read a sector, that becomes a seek and a read on an ihandle, several layers down. → The veneer


5. Path arguments: files versus raw sectors

Here is where the subtleties live.

An Open Firmware path may carry arguments after a colon:

/bandit@F2000000/53c825@12/sd@0,0 : 1 , \os\winnt40\osloader.exe
└──────── the device ───────────┘  │   └── a file on it
                                   └────── which partition
Form What you get
sd@0,0 the whole disk, raw
sd@0,0:0 the whole disk, raw (partition 0 conventionally means "all of it")
sd@0,0:1 partition 1, raw — reads are relative to its start
sd@0,0:1,\path\file a file inside partition 1's filesystem

Why NT needs the raw partition form to work. NT's loader carries its own FAT, NTFS, HPFS and CDFS readers. Before it can open a file it opens the partition and reads sector 0 to work out what filesystem is there. If a raw open silently hands back the whole disk instead of the partition, the loader reads the MBR where it expected a BIOS parameter block, recognises nothing, and reports an error about disk partition tables that has no connection to partition tables.

Apple's firmware handles this correctly on a hard disk. Driven by hand at the prompt, :1 and :2 return each partition's boot sector and :0 returns the MBR — exactly as they should.

On a CD it does not. Asked for :N on an ISO, Apple's disk-label package hands back the root directory as a file. Asking for sector 0 therefore returns a directory record; a checksum over it fails; and NT concludes its boot device is inaccessible. The workaround — forcing raw opens down the whole-device route — is correct for the CD and exactly wrong for a hard disk, which is ledger row 4 and cost this project two separate walls.


6. Driving it by hand

The firmware prompt is 0 >, on the serial console. It is worth being comfortable there, because it answers questions about the machine that no amount of reading will.

dev /bandit@F2000000/53c825@12     \ make a node current
ls                                  \ list its children
.properties                         \ show the current node's properties
words                               \ list the current package's methods

Opening a device and reading from it directly — this is how the :N behaviour in §5 was settled, rather than inferred:

variable ih
" /bandit@F2000000/53c825@12/sd@0,0:1" open-dev ih !
600000 200 " read" ih @ $call-method .
600000 10 dump
ih @ close-dev

Two practical notes from doing a lot of this:

  • The serial input FIFO is small — around sixteen characters. A long line typed in one burst loses its head, silently, and you get a baffling error about a word that is half a word. Feed long lines in chunks.
  • Not all memory is mapped. dump at an address the firmware has not claimed throws DEFAULT CATCH!. Use alloc-mem to get a buffer it knows about.

7. Setting things

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

That sets /chosen's bootpath property — how this project redirects a boot from the CD to a hard disk. encode-string turns a Forth string into a property value; _chosen pushes the node; (property) writes it.

nvram holds settings that survive a reboot — little-endian?, boot-device, and the rest. Two things to know: it is Apple's format, not ARC's, so NT's boot configuration cannot live there without a translation layer that does not exist; and little-endian? true followed by a reboot is what puts the machine into the mode NT runs in.


8. The boot sequence on this machine

For completeness, here is what actually happens before NT is involved at all:

  1. little-endian? true, then reboot the firmware into little-endian mode.
  2. Load VENEER.EXE off a staging disk with the ROM's pe-loader package, laid out at 0x50000.
  3. Apply the veneer patches (the ledger, rows 1–5).
  4. Point /chosen's bootpath at the device to boot from.
  5. go.

From step 5 onward the veneer is in charge and the machine is pretending to be ARC.


Further reading

Clone this wiki locally