Home‎ > ‎Open Firmware‎ > ‎

Reducing Open Firmware Expansion ROM Images

Note: You will need a binary or "hex" file editor to modify PCI expansion ROM images. I use Hex Workshop from BreakPoint Software, but there are many excellent free and often open solutions available for many platforms.

Before writing or "flashing" Open Firmware expansion ROM images to display adapters shipped with Intel x86 expansion ROM images, it is often necessary to reduce the size of the Open Firmware expansion ROM image to less than or equal to 64 KiB. One simple way to reduce the size of an Open Firmware expansion ROM image (heretofore referred to as "the FCode image") is to remove the encapsulated Apple NDRV driver image from the FCode image.

PCI expansion ROM images are composed of images for one or more architectures, each beginning with a PCI expansion ROM header. The PCI expansion ROM header is indicated by a two-byte ROM signature, 55h at offset 0h and AAh at offset 1h, followed by twenty-two bytes of architecture-specific data and a two-byte, little endian pointer to the PCI data structure. Note that all pointers are stored as offsets from the beginning of the current PCI expansion ROM image. The PCI data structure is indicated for a four-byte signature, the string "PCIR." PCI expansion ROM header and PCI data structure details specific to the FCode image will be described below. For more information regarding PCI expansion ROM images, refer to the PCI Local Bus Specification Revision 2.0 or later.

A typical display adapter FCode image begins with data similar to the following (this example is from an ATI Radeon 9200 Mac Edition Open Firmware expansion ROM image):

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  55 AA 40 00 00 00 00 00 00 00 00 00 00 00 00 00
00000010  00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00
00000020  50 43 49 52 02 10 60 59 00 00 20 00 00 00 00 03
00000030  FE 00 00 00 01 80 00 00 00 00 00 00 00 00 00 00

Offset 00h contains the two-byte ROM signature, 55h AAh.

Offset 02h contains a two-byte, little endian pointer to the FCode program, 0040h.

Offsets 04h-17h are reserved.

Offset 18h contains a two-byte, little endian pointer to the PCI data structure, 20h.

Offset 20h is the beginning of the PCI data structure, indicated by pointer at offset 18h and the string "PCIR" (50h 43h 49h 52h).

Offset 24h contains the two-byte, little endian PCI vendor code for the display adapter. Common values are 1002h (02h 01h) for ATI Technologies Inc, now Advanced Micro Devices, Inc, and 10DEh (DEh 10h) for NVIDIA Corporation.

Offset 26h contains the two-byte, little endian PCI device code for the display adapter.

Offsets 28h-29h are reserved.

Offset 2Ah contains the two-byte, little-endian length of the PCI data structure, 20h bytes.

Offset 2Ch contains PCI data structure revision, 00h.

Offset 2Dh contains the three-byte, little endian class code for VGA-compatible display adapters, 030000h (00h 00h 03h).

Offset 30h contains the two-byte, little endian length of the PCI expansion ROM image in units of 512 bytes, i.e. the number of 512-byte blocks.

Offset 32h contains the two-byte revision level of the code in the ROM image. This can be any vendor-supplied value.

Offset 34h contains the code type. The value is 01h for Open Firmware (FCode) images.

Offset 35h contains the last image indicator. Bit 7 in this field indicates the last image. For images containing a single PCI expansion ROM image, the value is 80h.

Offsets 36h-37h are reserved.

Offsets 38h-3Fh are padded with zeroes.

When modifying FCode images, the key fields in the PCI expansion ROM header and the PCI data structure are offset 02h, the pointer to the FCode program, and offset 30h, the image length. We will use offset 02h to find the start of the FCode image, and we will modify offset 30h after removing the encapsulated Apple NDRV driver image.

In this example, the value at offset 02h is 0040h, which marks the start of the FCode program. The start of the FCode program is represented by an eight-byte header.

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000040  F1 08 72 0C 00 01 FB 06 .. .. .. .. .. .. .. ..

Offset 00h contains the FCode F1h (start1). (N.B. An FCode is a tokenized Forth word, and an FCode program is a stream of tokenized Forth words.)

Offset 01h contains the FCode format, 08h, which indicates this FCode program is compliant with IEEE Std 1275-1994.

Offset 02h contains the two-byte, big endian checksum of the FCode program.

Offset 04h contains the four-byte, big endian length of the FCode program in bytes.

Offset 08h and beyond contain the remainder of the FCode program.

We now have a basic understanding of Open Firmware PCI expansion ROM images, the PCI data structure, and the FCode program header. For more information regarding Open Firmware and FCode, refer to IEEE Std 1275-1994, IEEE Standard for Boot (Initialization Configuration) Firmware: Core Requirements and Practices, PCI Bus Binding to IEEE Standard for Boot (Initialization Configuration) Firmware, and Writing FCode Programs for PCI. Draft or final versions of these and other documents may be obtained from the Open Firmware Home Page at http://playground.sun.com/1275/.

To remove the encapsulated Apple NDRV driver image (heretofore referred to as "the driver"), we must find the offset of the first byte of the driver. Apple NDRV driver files are Prferred Executable Format (PEF) containers. All PEF containers begin with the string "Joy!" (4Ah 6Fh 79h 21h). Searching for this string in your binary editor will locate the driver:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00004980  62 6C 65 01 10 C2 12 FC 4A 6F 79 21 70 65 66 66

In this example, the string "Joy!" is found at offset 4988h. Because the driver is encapsulated in the FCode image as binary data, we must understand how the driver data is tokenized to properly remove it. One possible method of encoding binary data in Forth is to use the " (quote) word followed by a sequence of space-delimited hexidecimal values enclosed by parentheses:

"( AA BB CC DD EE FF 00 11 )

This word is represented in FCode as:

b(") "( AA BB CC DD EE FF 00 11 )

and tokenized as:

12 08 AA BB CC DD EE FF 00 11

The FCode b(") is tokenized as 12h, the length of the data as 08h (eight bytes), and the data as raw values. Returning to our example above, we can see that the FCode representation of the driver actuall begins at offset 4986h:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00004980  .. .. .. .. .. .. 12 FC 4A 6F 79 21 70 65 66 66

At offset 4986h, we have the token 12h, followed by the length FCh (252 bytes, the maximum value divisible by 4) and 252 bytes of data. After processing this token, the address and length of the data are written to the stack. The data continues through offset 4A83h.

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00004A80  00 00 00 16 01 15 12 FC 00 00 00 00 00 00 00 33

At offset 4A84h, we encounter the next FCode token, 01h. FCode tokens 01h-0Fh represent the first byte of a two-byte token, in this case 01h 15h (encode-bytes). The FCode encode-bytes reads the address and length of a byte array from the stack, converts it to a prop-format string (a string of data used by properties), and writes the address and length of the prop-format string to the stack.

At offset 4A86h, we again encounter FCode token 12h, or FCode b("), followed by FCh, indicating a new byte array of 252 bytes, with data continuing through offset 4B83h.

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00004B80  00 00 00 00 01 15 01 12 12 FC 4A 2E 42 3A 40 05

At offset 4B84h, we again encounter FCode token 01h 15h, or FCode encode-bytes. The stack now holds two prop-format string address and length pairs. The next FCode token, 01h 12h, or FCode encode+, merges the last two prop-format strings on the stack into a single prop-format string address and length. We then encounter FCode token 12h again. Following the sequence of tokens and data, you will see a pattern emege:

b(") "( ... )
encode-bytes
b(") "( ... )
encode-bytes
encode+
b(") "( ... )
encode-bytes
encode+
b(") "( ... )
encode-bytes
encode+
...

The driver is encapsulated into the FCode image as a prop-format string using a series of byte array and encoding words. To find the total length of the driver, you must "walk" the FCode image until you reach the last encode-bytes, encode+ sequence in the series:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0001F070  01 00 24 0A 21 02 04 21 64 01 22 02 7F 01 22 64
0001F080  4B 01 22 02 02 01 01 15 01 12 C3 08 D7 C3 08 D8
0001F090  A5 B5 0A D7 B8 08 EF 09 DC 0A 0B 0A 26 0A 35 08

In this example, the last sequence in the series occurs at offset 1F086h. After the last encode+ is executed, the stack contains the address and length of a single prop-format string. We now know the total length of the driver, offsets 4986h-1F089h. The story does not end, however. The next FCode token we encounter is C3h, or the FCode b(to), which reads a value from the stack and stores it in a named location. The location is the two-byte token 08D7h. As we have two values on the stack, we need to store them in two locations, and the second location, 08D8h, follows the next occurance of C3h.

If we remove the driver without accounting for the tokens following it, we will almost certainly corrupt the stack. One solution is to remove the FCodes immediately following the driver, i.e. remove the six bytes beginning at 1F08Ah: C3h 08h D7h C3h 08h D8h. While this solutions works, it causes problems for the display adapter's Open Firmware device node and the driver,AAPL,MacOS,PowerPC property. (FIXME: Explain the driver,AAPL,MacOS,PowerPC property, the check for a valid pointer to the prop-format string, and whether or not to remove the property from the FCode image.)

A better solution is to store address and length values of zero in the tokens 08D7h and 08D8h. We do this by pushing zeroes onto the stack immediately before the C3h FCode tokens. The FCode token for pushing zero onto the stack is A5h. Using your binary editor, insert the value A5h immediately before each of the C3h values:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0001F080  4B 01 22 02 02 01 01 15 01 12 A5 C3 08 D7 A5 C3
0001F090  08 D8 A5 B5 0A D7 B8 08 EF 09 DC 0A 0B 0A 26 0A

Using your binary editor, you can now safely delete the driver, which in this example is stored at offsets 4986h-1F089h. After deleting the driver, you should have an FCode stream similar to the following, with the FCode A5h appearing at the offset previously occupied by the driver:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00004980  62 6C 65 01 10 C2 A5 C3 08 D7 A5 C3 08 D8 A5 B5

Assuming the driver represented the majority of the data in the FCode image, the total length of the FCode image should now be less than or equal to 64 KiB. If the image is less than 64 KiB (65536 or 10000h bytes), we must pad the image to 65536 bytes exactly. The easiest way to pad the image is to fill the space between the end of the FCode program and offset 10000h with zeroes. The FCode program is terminated with FCode token 00h, or FCode end0, which in this example, is now at offset 5443h:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00005430  31 2E 39 35 01 14 12 09 41 54 59 2C 46 63 6F 64
00005440  65 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00
00005450  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
.
.
.
0000FFFF  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00010000

Now that the image is the proper length, we must update the length fields of the FCode program header and the PCI expansion ROM header. To determine the length of the FCode program, subtract the offset of the FCode end0 from the offset of FCode program header and add 1:

5443h - 40h + 1h = 5404h

Store this value at the offset of the FCode program header length field, e.g. 44h, as a four-byte (32-bit), big endian value:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000040  F1 08 72 0C 00 00 54 04 .. .. .. .. .. .. .. ..

To determine the length of the FCode image (the PCI expansion ROM image), add 1FFh to the offset of the FCode end0 and divide by 200h, discarding the remainder:

( 5443h + 1FFh ) / 200h = 2Bh

Store this value at the offset of the PCI expansion ROM header length field, e.g. 30h, as a two-byte (16-bit), little endian value:

          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000030  2B 00 00 00 01 80 00 00 00 00 00 00 00 00 00 00

(FIXME: For correctness, describe, compute, and update the FCode program header checksum field. Apple's Open Firmware implementation appears to ignore this field, which is acceptable, implementation-defined behavior.)

The Open Firmware expansion ROM image has now been reduced to 64 KiB and can now be written to any compatible display adapter with at least 64 KiB of available flash memory.
Comments