The wide availability of personal computer based on the x86 architecture that conform to the PCI specification version 2.1 and Plug and Play BIOS specification version 1.0A or higher, along with the existence of free opensource software development tools for this architecture, provides an opportunity to create a low cost embedded system teaching tool based on it. In this article we will explain one of the implementation of this idea by exploiting the so called "Bootstrap Entry Vector" that exist as part of the Plug and Play BIOS.
The main obstacle of teaching embedded system development in various universities is the cost of the hardware used for development and possibly the cost of the software tool. The existence of free opensource software development tools for many processor architecture in recent years has solved the problem of the cost of the software tool needed. However, the cost of the hardware remains quite high for college students to afford. Especially in developing nation like in Indonesia, where cost is the main issue. From cost point of view, the PC itself is still affordable for the students, since it's used for wide variety of task, not just embedded system development. Meanwhile, buying hardware for embedded system development board can easily cost more than an entry-level PC or refurbished PC.
In this article we explore the possibility to develop a low cost tool for teaching embedded system in the x86 processor architecture based on the PCI expansion ROM. It will become a playing-ground for the students to learn embedded system development in x86 platform. The term PCI expansion ROM in this article is the PCI firmware which is embedded in a PCI expansion card. It's also sometimes called PCI option ROM. We will use the term interchangeably. PCI expansion ROM has a broader meaning than the definition that has been mentioned above, but we are focusing on that type of expansion ROM in this article. The reader might be aware that PCI expansion ROM can also be embedded inside the mainboard BIOS as a component. We are not considering the latter type of PCI expansion ROM here.
We are going to demonstrate the development of a custom PCI expansion ROM that is going to be embedded in "special" PCI expansion card by using free opensource software development tool. This PCI expansion ROM and it's corresponding "special" PCI expansion card is the hardware-software complex that acts as the embedded system development teaching tool. The cost of this hardware-software complex can be very low (the cost of the PC is not included), from around Rp. 25.000,00 (around US $ 2.5) to nothing at all if we can find the PCI expansion card from junk yard.
Understanding of the x86 memory map is a must to be able to develop an embedded system based on this platform. We will start with the explanation of the booting process. An x86 CPU begins its execution at address FFFFFFF0h physical address[1]. This address is the address of the first instruction within the mainboard (system) bios. It's the responsibility of the mainboard chipset to remap this address into the system bios chip. The system bios is the very first program that the processor executes. Below is an explanation of the typical memory map of an x86 based system just after the system bios finished initialization.
| Address Range | Detailed Explanation |
| Compatibility area (0_0000h - F_FFFFh) |
DOS Area (00000h-9FFFFh) The DOS area is 640 KB in size and is always mapped to the main memory (RAM) by mainboard chipset |
| Legacy VGA Ranges and/or Compatible SMRAM Address Range (A0000h�BFFFFh) The legacy 128-KB VGA memory range A0000h-BFFFFh (Frame Buffer) can be mapped to AGP or PCI Device. However, when compatible SMM space is enabled, SMM-mode processor accesses to this range are routed to physical system memory at this address. Non-SMM-mode processor accesses to this range are considered to be to the video buffer area as described above. |
|
| Expansion ROM Area (C0000h-DFFFFh) This is the 128-KB ISA/PCI Expansion ROM region. The system BIOS copies PCI expansion ROM to this area (in RAM) from the corresponding PCI expansion card ROM chip and execute it from there. As for ISA expansion ROM, it only exist on system that support ISA expansion card and sometimes the expansion ROM chip of the corresponding card is "hardwired" certain memory range in this area. In most case, part of this memory range can be assigned one of four read/write states: read only, write only, read/write, or disabled. This state assigment is controlled by the setting of certain mainboard chipset registers. The system BIOS is responsible for assigning the correct read/write state. |
|
| Extended System BIOS Area (E0000h-EFFFFh) This 64-KB area is divided into four, 16-KB segments. Each segment can be assigned independent read and write attributes so it can be mapped either to main memory or to the BIOS ROM chip via the system chipset. Typically, this area is used for RAM or ROM. On systems that only supports 64KB BIOS ROM chip capacity, this memory area always mapped to RAM. |
|
| System BIOS Area (F0000h-FFFFFh) This area is a single, 64-KB segment. This segment can be assigned read and write attributes. It is by default (after reset) read/write disabled and cycles are forwarded to BIOS ROM chip via the system chipset. By manipulating the read/write attributes, the system chipset can �shadow� BIOS into the main memory. When disabled, this range is not remapped to main memory by the chipset. |
|
| Extended Memory Area (10_0000h - FFFF_FFFFh) |
Main system memory from 1 MB (10_0000h) to the Top of of RAM This area can have a hole i.e. an area that is not mapped to RAM but mapped to ISA devices. This hole is dependent on the mainboard chipset configuration |
| AGP or PCI memory space from the Top of RAM to 4 GB (FFFF_FFFFh) This area has 2 specific ranges :
|
In the memory map above, of particular interest is the expansion ROM area. We will be dealing with this area later as we are developing the custom PCI expansion ROM.
In this section, we are not going to provide a complete explanation of the PnP BIOS architecture. We will only explain parts of the PnP BIOS architecture that are needed to develop our hardware-software complex. A more thorough explanation regarding the system BIOS can be found in Award BIOS Reverse Engineering Paper [3].
The parts of PnP BIOS that are important to our project are the initialization of expansion/option ROM, i.e. initialization code that resides in the expansion cards and the bootstrap process, i.e. transferring control from BIOS to operating system after the BIOS has done initializing the system. Initialization of option ROM is part of the POST (Power-On Self Test) routine in the system BIOS. The related informations from the PnP BIOS Specification 1.0A [5] are provided below.
The following steps outline a typical flow of a Plug and Play system BIOS POST. All of the standard ISA functionality has been eliminated for clarity in understanding the Plug and Play POST enhancements.
| Step 1 | Disable all configurable devices Any configurable devices known to the system BIOS should be disabled early in the POST process. |
| Step 2 | Identify all Plug and Play ISA devices Assign Card Select Numbers (CSNs) to Plug and Play ISA devices but keep devices disabled. Also determine which devices are boot devices. |
| Step 3 | Construct an initial resource map of allocated resources Construct a resource map of resources that are statically allocated to devices in the system. If the system software has explicitly specified the system resources assigned to ISA devices in the system through the Set Statically Allocated Resource Information function, the system BIOS will create an initial resource map based on this information. If the BIOS implementation provides support for saving the last working configuration and the system software has explicitly assigned system resources to specific devices in the system, then this information will be used to construct the resource map. This information will also be used to configure the devices in the system. |
| Step 4 | Enable Input and Output Devices Select and enable the Input and Output Device. Compatibility devices in the system that are not configurable always have precedence. For example, a standard VGA adapter would become the primary output device. If configurable Input and Output Devices exists, then enable these devices at this time. If Plug and Play Input and Output Devices are being selected, then initialize the option ROM, if it exists, using the Plug and Play option ROM initialization procedure. |
| Step 5 | Perform ISA ROM scan The ISA ROM scan should be performed from C0000h to EFFFFh on every 2K boundary. Plug and Play Option ROMs are disabled at this time (except input and output boot devices) and will not be included in the ROM scan. |
| Step 6 | Configure the Initial Program Load(IPL) device If a Plug and Play device has been selected as the IPL device, then use the Plug and Play Option ROM procedure to initialize the device. If the IPL device is known to the system BIOS, then ensure that interrupt 19h is still controlled by the system BIOS. If not, recapture interrupt 19h and save the vector. |
| Step 7 | Enable Plug and Play ISA and other Configurable Devices If a static resource allocation method is used, then enable the Plug and Play ISA cards with conflict free resource assignments. Initialize the option ROMs and pass along the defined parameters. All other configurable devices should be enabled, if possible, at this time. If a dynamic resource allocation method is used, then enable the bootable Plug and Play ISA cards with conflict free resource assignments and initialize the option ROMs. |
| Step 8 | Initiate the Interrupt 19H IPL sequence Start the bootstrap loader. If the operating system fails to load and a previous ISA option ROM had control of the interrupt 19h vector, then restore the interrupt 19h vector to the ISA option ROM and re-execute the Interrupt 19h bootstrap loader. |
| Step 9 | Operating system takes over resource management If the loaded operating system is Plug and Play compliant, then it will take over management of the system resources. It will use the runtime services of the system BIOS to determine the current allocation of these resources. It is assumed that any unconfigured Plug and Play devices will be configured by the appropriate system software or the Plug and Play operating system. |
This section outlines the Plug and Play Option ROM requirements. This Option ROM support is directed specifically towards boot devices; however, the Static Resource Information Vector permits non-Plug and Play devices which have option ROMs to take advantage of the Plug and Play Option ROM expansion header to assist a Plug and Play environment whether or not it is a boot device. A boot device is defined as any device which must be initialized prior to loading the Operating System. Strictly speaking, the only required boot device is the Initial Program Load (IPL) device upon which the operating system is stored. However, the definition of boot devices is extended to include a primary Input Device and a primary Output device. In some situations these I/O devices may be required for communication with the user. All new Plug and Play devices that support Option ROMs should support the Plug and Play Option ROM Header. In addition, all non-Plug and Play devices may be "upgraded" to support the Plug and Play Option ROM header as well. While these static ISA devices will still not have software configurable resources, the Plug and Play Option ROM Header will greatly assist a Plug and Play System BIOS in identification and selection of the primary boot devices. It is important to note that the Option ROM support outlined here is defined specifically for computing platforms based on the Intel X86 family of microprocessors and may not apply to systems based on other types of microprocessors.
The Plug and Play Option ROM Header follows the format of the Generic Option ROM Header extensions
. The Generic Option ROM header is a mechanism whereby the standard ISA
Option ROM header may be expanded with minimal impact upon existing Option ROMs. The pointer at
offset 1Ah may point to ANY type of header. Each header provides a link to the next header; thus, future
Option ROM headers may use this same generic pointer and still coexist with the Plug and Play Option
ROM header. Each Option ROM header is identified by a unique string. The length and checksum bytes
allow the System BIOS and/or System Software to verify that the header is valid.
Standard Option ROM Header:
| Offset | Length | Value | Description | Type |
| 0h | 2h | AA55h | Signature | Standard |
| 2h | 1h | Varies | Option ROM Length | Standard |
| 3h | 4h | Varies | Initialization Vector | Standard |
| 7h | 13h | Varies | Reserved | Standard |
| 1Ah | 2h | Varies | Offset to Expansion Header Structure | New for Plug and Play |
| Offset | Length | Value | Description | |
| 0h | 4 BYTES | $PnP (ASCII) | Signature | Generic |
| 04h | BYTE | Varies | Structure Revision | 01h |
| 05h | BYTE | Varies | Length (in 16 byte increments) | Generic |
| 06h | WORD | Varies | Offset of next Header (0000h if none) | Generic |
| 08h | BYTE | 00h | Reserved | Generic |
| 09h | BYTE | Varies | Checksum | Generic |
| 0Ah | DWORD | Varies | Device Identifier | PnP Specific |
| 0Eh | WORD | Varies | Pointer to Manufacturer String (Optional) | PnP Specific |
| 10h | WORD | Varies | Pointer to Product Name String (Optional) | PnP Specific |
| 12h | 3 BYTE | Varies | Device Type Code | PnP Specific |
| 15h | BYTE | Varies | Device Indicators | PnP Specific |
| 16h | WORD | Varies | Boot Connection Vector - Real/Protected mode (0000h if none) | PnP Specific |
| 18h | WORD | Varies | Disconnect Vector - Real/Protected mode (0000h if none) | PnP Specific |
| 1Ah | WORD | Varies | Bootstrap Entry Point - Real/Protected mode (0000h if none) | PnP Specific |
| 1Ch | WORD | 0000h | Reserved | PnP Specific |
| 1Eh | WORD | Varies | Static Resource Information Vector- Real/Protected mode (0000h if none) | PnP Specific |
| Bit | Description |
| 7 | A 1 indicates that this ROM supports the Device Driver Initialization Model |
| 6 | A 1 indicates that this ROM may be Shadowed in RAM |
| 5 | A 1 indicates that this ROM is Read Cacheable |
| 4 | A 1 indicates that this option ROM is only required if this device is selected as a boot device. |
| 3 | Reserved (0) |
| 2 | A 1 in this position indicates that this device is an Initial Program Load (IPL) device. |
| 1 | A 1 in this position indicates that this device is an Input device. |
| 0 | A 1 in this position indicates that this device is a Display device. |
| Reg On Entry | Description |
| AX | Provides an indication as to which vectors should be hooked by specifying the
type of boot device this device has been selected as. Bit 7..3 Reserved(0) Bit 2 1=Connect as IPL (INT 13h) Bit 1 1=Connect as primary Video (INT 10h) Bit 0 1=Connect as primary Input (INT 09h) |
| ES:DI | Pointer to System BIOS PnP Installation Check Structure (See section 4.4) |
| BX | CSN for this card, ISA PnP devices only. If not an ISA PnP device then this parameter will be set to FFFFh. |
| DX | Read Data Port, (ISA PnP devices only. If no ISA PnP devices then this parameter will be set to FFFFh. |
| Entry: ES:DI | Pointer to memory buffer to hold the device's static resource configuration information.
The buffer should be a minimum of 1024 bytes. This information should follow the
System Device Node data structure, except that the Device node number field should
always be set to 0, and the information returned should only specify the currently
allocated resources (Allocated resource configuration descriptor block) and not the
block of possible resources (Possible resource configuration descriptor block). The
Possible resource configuration descriptor block should only contain the END_TAG
resource descriptor to indicate that there are no alternative resource configuration
settings for this device because the resource configuration for this device is static. Refer
to the Plug and Play ISA Specification under the section labeled Plug and Play
Resources for more information about the resource descriptors. This data structure has
the following format:
|
The System BIOS will determine if the Option ROM it is about to initialize supports the Plug and Play interface by verifying the Structure Revision number in the device's Plug and Play Header Structure. For all Option ROMs compliant with the 1.0 Plug and Play BIOS Specification, the System BIOS will call the device's initialization vector with the following parameters:
| Reg On Entry | Description |
| ES:DI | Pointer to System BIOS PnP Installation Check Structure (See section 4.4) |
| BX | CSN for this card, ISA PnP devices only. If not an ISA PnP device then this parameter will be set to FFFFh |
| DX | Read Data Port, (ISA PnP devices only. If no ISA PnP devices then this parameter will be set to FFFFh. |
| AX Bit | Description |
| 8 | 1 = IPL Device supports INT 13h Block Device format |
| 7 | 1 = Output Device supports INT 10h Character Output |
| 6 | 1 = Input Device supports INT 9h Character Input |
| 5:4 | 00 = No IPL device attached 01 = Unknown whether or not an IPL device is attached 10 = IPL device attached (RPL devices have a connection). 11 = Reserved |
| 3:2 | 00 = No Display device attached 01 = Unknown whether or not a Display device is attached 10 = Display device attached 11 = Reserved |
| 1:0 | 00 = No Input device attached 01 = Unknown whether or not an Input device is attached 10 = Input device attached 11 = Reserved |
The following outlines the typical steps used to initialize Option ROMs during a Plug and Play system BIOS POST:
| Step 1 | Initialize the boot device option ROMs. This includes the Primary Input, Primary Output, and Initial Program Load (IPL) device option ROMs. |
| Step 2 | Initialize ISA option ROMs by performing ISA ROM scan The ISA ROM scan should be performed from C0000h to EFFFFh on every 2k boundary. Plug and Play option ROMs will not be included in the ROM scan. |
| Step 3 | Initialize option ROMs for ISA devices which have a Plug and Play option ROM. Typically, these devices will not provide support for dynamic configurability. However, the resources utilized by these devices can be obtained through the Static Resource Information Vector as described in section 3.2. |
| Step 4 | Initialize option ROMs for Plug and Play cards which have a Plug and Play option ROM. |
| Step 5 | Initialize option ROMs which support the Device Driver Initialization Model (DDIM). Option ROMs which follow this model make the most efficient use of space consumed by option ROMs. Refer to Appendix B for more information on the DDIM. |
Up to this point we have known that the facility of the PnP BIOS that will help us in developing our teaching tool is the option ROM and
it's corresponding Bootstrap Entry Vector (BEV). The reason for selecting this bootstrap mechanism is: the core functionality of the PC
that will be used must not be disturbed by the the new functionality of the PC as the embedded system development tool and target platform.
In other word, by setting up the Option ROM to behave as RPL device, the Option ROM will only be executed as the bootstrap device
if the RPL i.e. Boot From LAN support is activated in the system BIOS. By doing things this way, we can switch back and forth between
normal usage of the PC and the usage of the PC as embedded system development target platform by setting the appropriate system BIOS setting,
i.e. the Boot From LAN Activation entry.
Later, we well demonstrate how to implement this logic by developing a custom option ROM that can be flashed into a real PCI LAN card or
another type of PCI expansion card that is "hacked" to behave as is if it's a real LAN card from PnP BIOS point of view.
The PCI specification version 2.1 [4] explains that there are two types of PCI devices, i.e. PCI-to-PCI bridge device and Non PCI-to-PCI bridge device. This article only deals with Non PCI-to-PCI bridge device. Eventhough this classification exist, there is one common property that all PCI device inherit, i.e. all PCI device has a predefined 256 byte hardware registers in its chip that is called PCI Configuration Space Header. This header differ quite significantly between PCI-to-PCI bridge device (type 01h header) and Non PCI-to-PCI bridge device (type 00h header). The header is used for many purposes, but the main usage is for configuring the corresponding PCI device. The "type 00h" header layout is provided below :
| 31 | 16 | 15 | 0 | |
| Device ID | Vendor ID | 00h | ||
| Status | Command | 04h | ||
| Class Code | Revision ID | 08h | ||
| BIST | Header Type | Latency Timer | Cache Line Size | 0Ch |
| Base Address Register 0 | 10h | |||
| Base Address Register 1 | 14h | |||
| Base Address Register 2 | 18h | |||
| Base Address Register 3 | 1Ch | |||
| Base Address Register 4 | 20h | |||
| Base Address Register 5 | 24h | |||
| Cardbus CIS Pointer | 28h | |||
| Subsystem ID | Subsystem Vendor ID | 2Ch | ||
| Expansion ROM Base Address (XROMBAR) | 30h | |||
| Reserved | 34h | |||
| Reserved | 38h | |||
| Max_Lat | Min_Gnt | Interrupt Pin | Interrupt Line | 3Ch |
| Type 00h Configuration Space Header | ||||
Some PCI devices, especially those that are intended for use on add-in modules in PC architectures, require local EPROMs for expansion ROM. The four-byte register at offset 30h in a type 00h predefined header is defined to handle the base address and size information for this expansion ROM. The figure below shows how this word is organized. The register functions exactly like a 32-bit Base Address register except that the encoding (and usage) of the bottom bits is different. The upper 21 bits correspond to the upper 21 bits of the Expansion ROM base address. The number of bits (out of these 21) that a device actually implements depends on how much address space the device requires. For instance, a device that requires a 64 KB area to map its expansion ROM would implement the top 16 bits in the register, leaving the bottom 5 (out of these 21) hardwired to 0. Devices that support an expansion ROM must implement this register. Device independent configuration software can determine how much address space the device requires by writing a value of all 1�s to the address portion of the register and then reading the value back. The device will return 0�s in all don�t-care bits, effectively specifying the size and alignment requirements. The amount of address space a device requests must not be greater than 16 MB.
| 31 | 11 | 10 | 1 | 0 |
| Expansion ROM Base Address (Upper 21 bits) |
Reserved | |||
| Expansion ROM Base Address Register Layout | ||||
The PCI specification provides a mechanism where devices can provide expansion ROM code that can be executed for device-specific initialization and, possibly, a system boot function. The mechanism allows the ROM to contain several different images to accommodate different machine and processor architectures. This section specifies the required information and layout of code images in the expansion ROM. Note that PCI devices that support an expansion ROM must allow that ROM to be accessed with any combination of byte enables. This specifically means that DWORD accesses to the expansion ROM must be supported.
The information in the ROMs is laid out to be compatible with existing Intel x86 Expansion ROM headers for ISA, EISA, and MC adapters, but it will also support other machine architectures. The information available in the header has been extended so that more optimum use can be made of the function provided by the adapter and so that the minimum amount of Memory Space is used by the runtime portion of the expansion ROM code.
The PCI Expansion ROM header information supports the following functions:
| Image 0 |
| Image 1 |
| . . . |
| Image N |
| PCI Expansion ROM Structure |
| Offset | Length | Value | Description |
| 0h | 1 | 55h | ROM Signature, byte 1 |
| 1h | 1 | AAh | ROM Signature, byte 2 |
| 2h-17h | 16h | xx | Reserved (processor architecture unique data) |
| 18h-19h | 2 | xx | Pointer to PCI Data Structure |
| ROM Signature | The ROM Signature is a two-byte field containing a 55h in the first byte and AAh in the second byte. This signature must be the first two bytes of the ROM address space for each image of the ROM. |
| Pointer to PCI Data Structure | The Pointer to the PCI Data Structure is a two-byte pointer in little endian format that points to the PCI Data Structure. The reference point for this pointer is the beginning of the ROM image. |
| Offset | Length | Description |
| 0 | 4 | Signature, the string "PCIR" |
| 4 | 2 | Vendor Identification |
| 6 | 2 | Device Identification |
| 8 | 2 | Pointer to Vital Product Data |
| A | 2 | PCI Data Structure Length |
| C | 1 | PCI Data Structure Revision |
| D | 3 | Class Code |
| 10 | 2 | Image Length |
| 12 | 2 | Revision Level of Code/Data |
| 14 | 1 | Code Type |
| 15 | 1 | Indicator |
| 16 | 2 | Reserved |
| Signature | These four bytes provide a unique signature for the PCI Data Structure. The string "PCIR" is the signature with "P" being at offset 0, "C" at offset 1, etc. | |
| Vendor Identification | The Vendor Identification field is a 16-bit field with the same definition as the Vendor Identification field in the Configuration Space for this device. | |
| Device Identification | The Device Identification field is a 16-bit field with the same definition as the Device Identification field in the Configuration Space for this device. | |
| Pointer to Vital Product Data | The Pointer to Vital Product Data (VPD) is a 16-bit field that is the offset from the start of the ROM image and points to the VPD. This field is in little-endian format. The VPD must be within the first 64 KB of the ROM image. A value of 0 indicates that no Vital Product Data is in the ROM image. Section 6.4 describes the format and information contained in Vital Product Data. | |
| PCI Data Structure Length | The PCI Data Structure Length is a 16-bit field that defines the length of the data structure from the start of the data structure (the first byte of the Signature field). This field is in little-endian format and is in units of bytes. | |
| PCI Data Structure Revision | The PCI Data Structure Revision field is an eight-bit field that identifies the data structure revision level. This revision level is 0. | |
| Class Code | The Class Code field is a 24-bit field with the same fields and definition as the class code field in the Configuration Space for this device. | |
| Image Length | The Image Length field is a two-byte field that represents the length of the image. This field is in little-endian format, and the value is in units of 512 bytes. | |
| Revision Level | The Revision Level field is a two-byte field that contains the revision level of the code in the ROM image. | |
| Code Type | The Code Type field is a one-byte field that identifies the type of code contained in this section of the ROM. The code may be executable binary for a specific processor and system architecture or interpretive code. The following code types are assigned: | |
| Type | Description | |
| 0 | Intel x86, PC-AT compatible | |
| 1 | Open Firmware standard for PCI42 | |
| 2-FF | Reserved | |
| Indicator | Bit 7 in this field tells whether or not this is the last image in the ROM. A value of 1 indicates "last image;" a value of 0 indicates that another image follows. Bits 0-6 are reserved. | |
For the most part, system POST code treats add-in PCI devices identically to those that are soldered on to the motherboard. The one exception is the handling of expansion ROMs. POST code detects the presence of an option ROM in two steps. First the code determines if the device has implemented an Expansion ROM Base Address register in Configuration Space. If the register is implemented, the POST must map and enable the ROM in an unused portion of the address space, and check the first two bytes for the AA55h signature. If that signature is found, there is a ROM present; otherwise, no ROM is attached to the device.
If a ROM is attached, POST must search the ROM for an image that has the proper code type and whose Vendor ID and Device ID fields match the corresponding fields in the device.
After finding the proper image, POST copies the appropriate amount of data into RAM. Then the device�s initialization code is executed. Determining the appropriate amount of data to copy and how to execute the device�s initialization code will depend on the code type for the field.
This section describes further requirements on ROM images and the handling of ROM images that are used in PC-compatible systems. This applies to any image that specifies Intel x86, PC-AT compatible in the Code Type field of the PCI Data Structure, and any platform that is PC-compatible.
The standard header for PCI Expansion ROM images is expanded slightly for PC compatibility. Two fields are added, one at offset 02h provides the initialization size for the image. Offset 03h is the entry point for the expansion ROM INIT function.| Offset | Length | Value | Description |
| 0h | 1 | 55h | ROM Signature byte 1 |
| 1h | 1 | AAh | ROM Signature byte 2 |
| 2h | 1 | xx | Initialization Size - size of the code in units of 512 bytes. |
| 3h | 3 | xx | Entry point for INIT function. POST does a FAR CALL to this location. |
| 6h-17h | 12h | xx | Reserved (application unique data) |
| 18h-19h | 2 | xx | Pointer to PCI Data Structure |
POST code in these systems copies the number of bytes specified by the Initialization Size field into RAM, and then calls the INIT function whose entry point is at offset 03h. POST code is required to leave the RAM area where the expansion ROM code was copied to as writable until after the INIT function has returned. This allows the INIT code to store some static data in the RAM area, and to adjust the runtime size of the code so that it consumes less space while the system is running.
The PC-compatible specific set of steps for the system POST code when handling each expansion ROM are:
The INIT function can store static parameters inside its RAM area during the INIT function. This data can then be used by the runtime BIOS or device drivers. This area of RAM will not be writable during runtime.
The INIT function can also adjust the amount of RAM that it consumes during runtime. This is done by modifying the size byte at offset 02h in the image. This helps conserve the limited memory resource in the expansion ROM area (0C0000h - 0DFFFFh).
For example, a device expansion ROM may require 24 KB for its initialization and runtime code, but only 8 KB for the runtime code. The image in the ROM will show a size of 24 KB, so that the POST code copies the whole thing into RAM. Then when the INIT function is running, it can adjust the size byte down to 8 KB. When the INIT function returns, the POST code sees that the runtime size is 8 KB and can copy the next expansion BIOS to the optimum location.
The INIT function is responsible for guaranteeing that the checksum across the size of the image is correct. If the INIT function modifies the RAM area in any way, then a new checksum must be calculated and stored in the image.
If the INIT function wants to completely remove itself from the expansion ROM area, it does so by writing a zero to the Initialization Size field (the byte at offset 02h). In this case, no checksum has to be generated (since there is no length to checksum across). On entry, the INIT function is passed three parameters: the bus number, device number, and function number of the device that supplied the expansion ROM. These parameters can be used to access the device being initialized. They are passed in x86 registers, [AH] contains the bus number, the upper five bits of [AL] contain the device number, and the lower three bits of [AL] contain the function number.
Prior to calling the INIT function, the POST code will allocate resources to the device (via the Base Address and Interrupt Line registers) and will complete any User Definable Features handling.
A PC-compatible image has three lengths associated with it, a runtime length, an initialization length, and an image length. The image length is the total length of the image and it must be greater than or equal to the initialization length.
The initialization length specifies the amount of the image that contains both the initialization and runtime code. This is the amount of data that POST code will copy into RAM before executing the initialization routine. Initialization length must be greater than or equal to runtime length. The initialization data that is copied into RAM must checksum to 0 (using the standard algorithm).
The runtime length specifies the amount of the image that contains the runtime code. This is the amount of data the POST code will leave in RAM while the system is operating. Again, this amount of the image must checksum to 0.
The PCI Data structure must be contained within the runtime portion of the image (if there is any) otherwise it must be contained within the initialization portion.
It is very clear from section 2.2 and 2.3 above that PCI specification and PnP BIOS specification has a "flaw" that can be exploited for our own purpose.
Both of the specification don't impose that a PCI expansion ROM functionality has to be cross-checked by the system BIOS against the physical Class Code that is hardwired inside the PCI chip itself. Meaning, any PCI expansion card that implement an expansion ROM can be given a different functionality in its expansion ROM code, i.e. a functionality not related to the corresponding PCI chip itself. To be able to use PCI expansion ROM, the PCI chip only need to enable it's expansion ROM support in its XROMBAR.
For example, we can hack a PCI SCSI controller card that has an expansion ROM to behave as if it's a LAN card from the system BIOS point of view. We will be able to "Boot from LAN" by using this card.
We have been experimenting with this "flaw" and it works as predicted above. By making the PCI expansion ROM contents to conform to an RPL(Remote Program Load) PCI card ( LAN card that supports boot from LAN), we were able to execute our custom made PCI expansion ROM code. The detail of PCI card that we have tested as follows :
This section provides an implementation sample that has been tested in our testbed. The sample is a custom PCI expansion ROM that will be executed after the mainboard BIOS has done initialization. The sample is "jumped into" through its BEV by the mainboard BIOS during bootstrap.
The hardware used for this sample is Adaptec AHA-2940U PCI SCSI controller card (Vendor ID = 9004, Device ID = 8178). It has a soldered PLCC SST 29EE512 flash rom (64 KB) for its firmware. It cost Rp. 25.000 (around US$2.5). We get this hardware from refurbished PC component seller.
The PC that's used for expansion ROM development and also as the target platform has the following hardware configuration :
| Processor | : | Intel Celeron 300A, overclocked to 518 MHz by using ABIT Slotket II adapter |
| Mainboard | : | Iwill VD133 (slot 1) with VIA693A northbridge and VIA596B southbridge |
| Videocard | : | PowerColor Nvidia Riva TNT2 M64 32MB |
| RAM | : | 256MB SDRAM with unknown chip |
| Soundcard | : | Addonics Yamaha YMF724 |
| Network Card | : | Realtek RTL8139C |
| "Hacked" PCI Card | : | Adaptec AHA-2940U PCI SCSI controller card |
| Harddrive | : | Maxtor 20GB 5400RPM |
| CDROM | : | Teac 40X |
| Monitor | : | Samsung SyncMaster 551v (15') |
There are three kind of software that are needed for the development of this sample :
The basic run-down of what happens when the compiled source code executed as follows:
The purpose of the source code that is provided in this section is to show how a PCI PnP Expansion ROM source code might look like. The role of each file as follows :
# ------------------------------------------------------------------------ # Copyright (C) Darmawan Mappatutu Salihun # File name : Makefile # This file is released to the public for non-commercial use only # ------------------------------------------------------------------------ CC= gcc CFLAGS= -c LD= ld LDFLAGS= -T pci_rom.ld ASM= as OBJCOPY= objcopy OBJCOPY_FLAGS= -v -O binary OBJS:= crt0.o main.o ROM_OBJ= rom.elf ROM_BIN= rom.bin ROM_SIZE= 65536 all: $(OBJS) $(LD) $(LDFLAGS) -o $(ROM_OBJ) $(OBJS) $(OBJCOPY) $(OBJCOPY_FLAGS) $(ROM_OBJ) $(ROM_BIN) build_rom $(ROM_BIN) $(ROM_SIZE) crt0.o: crt0.S $(ASM) -o $@ $< %.o: %.c $(CC) -o $@ $(CFLAGS) $< clean: rm -rf *~ *.o *.elf *.bin
# ----------------------------------------------------------------------------------------------------------------
# Copyright (C) Darmawan Mappatutu Salihun
# File name : crt0.S
# This file is released to the public for non-commercial use only
# -----------------------------------------------------------------------------------------------------------------
.text
.code16 # Real mode by default (prefix 66 or 67 to 32 bits instructions)
# ------------------------- WARNING!!! --------------------------------------------
# 1. Make sure to synchronize the absolute address used to load the OS code here and
# in the address defined in the linker script
# 2. Make sure the rom size is correct
#
rom_size = 0x04 # ROM size in multiple of 512 bytes
os_load_seg = 0x0000 # this is working if lgdt is passed with an absolute address
os_code_size = ((rom_size - 1)*512)
os_code_size16 = ( os_code_size / 2 )
# -------------------------------------------
# Option rom header
#
.word 0xAA55 # Rom signature
.byte rom_size # Size of this ROM, see definition above
jmp _init # jump to PCI initialization function
.org 0x18
.word _pci_data_struct # Pointer to PCI HDR structure (at 18h)
.word _pnp_header # PnP Expansion Header Pointer (at 1Ah)
# --------------------------------------------
# PCI data structure
#
_pci_data_struct:
.ascii "PCIR" # PCI Header Sign
.word 0x9004 # Vendor ID
.word 0x8178 # Device ID
.word 0x00 # VPD
.word 0x18 # PCI data struc length (byte)
.byte 0x00 # PCI Data struct Rev
.byte 0x02 # Base class code, 02h == Network Controller
.byte 0x00 # Sub class code = 00h and interface = 00h -->Ethernet Controller
.byte 0x00 # Interface code, see PCI Rev2.1 Spec Appendix D
.word rom_size # Image length in mul of 512 byte, little endian format
.word 0x00 # rev level
.byte 0x00 # Code type = x86
.byte 0x80 # last image indicator
.word 0x00 # reserved
# -----------------------------
# PnP ROM Bios Header
#
_pnp_header:
.ascii "$PnP" # PnP Rom header sign
.byte 0x01 # Structure Revision
.byte 0x02 # Header structure Length in mul of 16 bytes
.word 0x00 # Offset to next header (00 if none)
.byte 0x00 # reserved
.byte 0x00 # 8-Bit checksum for this header, calculated and patched by build_rom
.long 0x00 # PnP Device ID (0h in Realtek RPL ROM, we just follow it)
.word 0x00 # pointer to manufacturer string, we use empty string
.word 0x00 # pointer to product string, we use empty string
.byte 0x02,0x00,0x00 # Device Type code 3 byte
.byte 0x14 # Device Indicator, 14h from Realtek RPL ROM-->See Page 18 of
# PnP BIOS spec., Lo nibble (4) means IPL device
.word 0x00 # Boot Connection Vector, 00h = disabled
.word 0x00 # Disconnect Vector, 00h = disabled
.word _start # Bootstrap Entry Vector (BEV)
.word 0x00 # reserved
.word 0x00 # Static resource Information vector (0000h if unused)
#--------------------------------------------------------------------
# PCI Option ROM initialization Code (init function)
#
_init:
andw $0xCF, %ax # inform system BIOS that an IPL device attached
orw $0x20, %ax # see PnP spec 1.0A p21 for info's
lret # return far to system BIOS
#--------------------------------------------------------------------
# entry point/BEV implementation (invoked during bootstrap / int 19h)
#
.global _start # entry point
_start:
movw $0x9000, %ax # setup temporary stack
movw %ax, %ss # ss = 0x9000
# move ourself from "ROM" -> RAM 0x0000
movw %cs, %ax # initialize source address
movw %ax, %ds
movw $os_load_seg, %ax # point to OS segment
movw %ax, %es
movl $os_code_size16, %ecx
subw %di, %di
subw %si, %si
cld
rep
movsw
ljmp $os_load_seg, $_setup
_setup:
movw %cs, %ax # initialize segment registers (this is needed since lgdt is %ds dependent??)
movw %ax, %ds
enable_a20:
cli
call a20wait
movb $0xAD, %al
outb %al, $0x64
call a20wait
movb $0xD0, %al
outb %al, $0x64
call a20wait2
inb $0x60, %al
pushl %eax
call a20wait
movb $0xD1, %al
outb %al, $0x64
call a20wait
popl %eax
or $2, %al
outb %al, $0x60
call a20wait
movb $0xAE, %al
outb %al, $0x64
call a20wait
jmp continue
a20wait:
1: movl $65536, %ecx
2: inb $0x64, %al
test $2, %al
jz 3f
loop 2b
jmp 1b
3: ret
a20wait2:
1: movl $65536, %ecx
2: inb $0x64, %al
test $1, %al
jnz 3f
loop 2b
jmp 1b
3: ret
continue:
sti # enable interrupt
# ---------------------------------------------------------------------
# Switch to P-Mode and jump to C-Compiled kernel
#
cli # disable interrupt
lgdt gdt_desc # load GDT to GDTR (we load both limit and base address)
movl %cr0, %eax # switch to P-Mode
or $1, %eax
movl %eax, %cr0 # haven't yet in P-Mode, we need a FAR Jump
.byte 0x66, 0xea # prefix + jmpi-opcode (this force P-Mode to be reached i.e. CS updated)
.long do_pm # 32-bit linear address (jump target)
.word SEG_CODE_SEL # code segment selector
.code32
do_pm:
xorl %esi, %esi
xorl %edi, %edi
movw $0x10, %ax # Save data segment identifier (see GDT)
movw %ax, %ds
movw $0x18, %ax # Save stack segment identifier
movw %ax, %ss
movl $0x90000, %esp
jmp main # jump to main function
.align 8, 0 # align GDT in 8 bytes boundary
# -----------------------------------------------------
# GDT definition
#
gdt_marker: # dummy Segment Descriptor (GDT)
.long 0
.long 0
SEG_CODE_SEL = ( . - gdt_marker)
SegDesc1: # kernel CS (08h) PL0, 08h is an identifier
.word 0xffff # seg_length0_15
.word 0 # base_addr0_15
.byte 0 # base_addr16_23
.byte 0x9A # flags
.byte 0xcf # access
.byte 0 # base_addr24_31
SEG_DATA_SEL = ( . - gdt_marker)
SegDesc2: # kernel DS (10h) PL0
.word 0xffff # seg_length0_15
.word 0 # base_addr0_15
.byte 0 # base_addr16_23
.byte 0x92 # flags
.byte 0xcf # access
.byte 0 # base_addr24_31
SEG_STACK_SEL = ( . - gdt_marker)
SegDesc3: # kernel SS (18h) PL0
.word 0xffff # seg_length0_15
.word 0 # base_addr0_15
.byte 0 # base_addr16_23
.byte 0x92 # flags
.byte 0xcf # access
.byte 0 # base_addr24_31
gdt_end:
gdt_desc: .word (gdt_end - gdt_marker - 1) # GDT limit
.long gdt_marker # physical addr of GDT
/* ----------------------------------------------------------------------------------------------------------------
Copyright (C) Darmawan Mappatutu Salihun
File name : main.c
This file is released to the public for non-commercial use only
-----------------------------------------------------------------------------------------------------------------*/
unsigned char in(unsigned short _port)
{
// "=a" (result) means: put AL register in variable result when finished
// "d" (_port) means: load EDX with _port
unsigned char result;
__asm__ ("in %%dx, %%al" : "=a" (result) : "d" (_port));
return result;
}
void out(unsigned short _port, unsigned char _data)
{
// "a" (_data) means: load EAX with _data
// "d" (_port) means: load EDX with _port
__asm__ ("out %%al, %%dx" : :"a" (_data), "d" (_port));
}
void clrscr()
{
unsigned char *vidmem = (unsigned char *)0xB8000;
const long size = 80*25;
long loop;
// Clear visible video memory
for (loop=0; loop < size; loop++){
*vidmem++ = 0;
*vidmem++ = 0xF;
}
// Set cursor position to 0,0
out(0x3D4, 14);
out(0x3D5, 0);
out(0x3D4, 15);
out(0x3D5, 0);
}
void print(const char *_message)
{
unsigned short offset;
unsigned long i;
unsigned char *vidmem = (unsigned char *)0xB8000;
// Read cursor position
out(0x3D4, 14);
offset = in(0x3D5) << 8;
out(0x3D4, 15);
offset |= in(0x3D5);
// Start at writing at cursor position
vidmem += offset*2;
// Continue until we reach null character
i = 0;
while (_message[i] != 0) {
*vidmem = _message[i++];
vidmem += 2;
}
// Set new cursor position
offset += i;
out(0x3D5, (unsigned char)(offset));
out(0x3D4, 14);
out(0x3D5, (unsigned char)(offset >> 8));
}
int main()
{
const char *hello = "Hello World";
clrscr();
print(hello);
for(;;);
return 0;
}
/*==========================================================================*/
/* Copyright (C) Darmawan Mappatutu Salihun */
/* File name : pci_rom.ld */
/* This file is released to the public for non-commercial use only */
/*==========================================================================*/
OUTPUT_FORMAT("elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
__boot_vect = 0x0000;
SECTIONS
{
.text __boot_vect :
{
*( .text)
} = 0x00
.rodata ALIGN(4) :
{
*( .rodata)
} = 0x00
.data ALIGN(4) :
{
*( .data)
} = 0x00
.bss ALIGN(4) :
{
*( .bss)
} = 0x00
}
The source code that is provided in this section is used to build the build_rom utility which is used to patch the checksums of the PCI PnP Expansion ROM binary produced by section 3.3.1. The role of each file as follows :
# ------------------------------------------------------------------------
# Copyright (C) Darmawan Mappatutu Salihun
# File name : Makefile
# This file is released to the public for non-commercial use only
# ------------------------------------------------------------------------
CC= gcc
CFLAGS= -Wall -O2 -march=i686 -mcpu=i686 -c
LD= gcc
LDFLAGS=
all: build_rom.o
$(LD) $(LDFLAGS) -o build_rom build_rom.o
cp build_rom ../
%.o: %.c
$(CC) $(CFLAGS) -o $@ $<
clean:
rm -rf *~ build_rom *.o
/* --------------------------------------------------------------------------------------
Copyright (c) Darmawan MS
File name : build_rom.c
This file is released to the public for non-commercial use only
Description :
This program zero-extend its input binary file and then patch it
into a valid PCI PnP ROM binary.
---------------------------------------------------------------------------------------- */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
enum {
MAX_FILE_NAME = 100,
ITEM_COUNT = 1,
ROM_SIZE_INDEX = 0x2,
PnP_HDR_PTR = 0x1A,
PnP_CHKSUM_INDEX = 0x9,
PnP_HDR_SIZE_INDEX = 0x5,
ROM_CHKSUM = 0x10, /* reserved position in PCI PnP ROM, that can be used */
};
static int
ZeroExtend(char * f_name, u32 target_size)
{
FILE* f_in;
long file_size, target_file_size, padding_size;
char* pch_buff;
target_file_size = target_size; // cast ulong to long
if( (f_in = fopen(f_name, "ab")) == NULL)
{
printf("error opening file\n closing program...\n");
return -1;
}
if(fseek(f_in, 0, SEEK_END) != 0)
{
printf("error seeking file\n closing program...\n");
fclose(f_in);
return -1;
}
if( (file_size = ftell(f_in)) == -1)
{
printf("error counting file size\n closing program...\n");
fclose(f_in);
return -1;
}
if( file_size >= target_file_size)
{
printf("Input error, Target file size is smaller than the original file size\n");
fclose(f_in);
return -1;
}
/*
Zero extend the target file
*/
padding_size = target_file_size - file_size;
pch_buff = (char*) malloc(sizeof(char) * padding_size );
if(NULL != pch_buff) {
memset(pch_buff, 0, sizeof(char) * padding_size );
fseek(f_in, 0, SEEK_END);
fwrite( pch_buff, sizeof(char), padding_size, f_in);
fclose(f_in);
free(pch_buff);
return 0;//success
} else {
fclose(f_in);
return -1;
}
}
static u8 CalcChecksum(FILE* fp, u32 size)
{
u32 position = 0x00;/* Position of file pointer */
u8 checksum = 0x00;
/* set file pointer to the beginning of file */
if(!fseek(fp,0,SEEK_SET))
{
/*
calculate 8 bit checksum 8
file size = size * 512 byte = size * 0x200
*/
for(; position < (size * 0x200) ; position++)
{
checksum = ( (checksum + fgetc(fp)) % 0x100);
}
printf("calculated checksum = %#x \n",checksum);
}
else
{
printf("function CalcChecksum:Failed to seek through the beginning of file\n");
}
return checksum;
}
static int
Patch2PnpRom(char* f_name)
{
FILE* fp;
u8 checksum_byte;
u32 rom_size; /* size of ROM source code in multiple of 512 bytes */
u8 pnp_header_pos;
u8 pnp_checksum = 0x00;
u8 pnp_checksum_byte;
u8 pnp_hdr_counter = 0x00;
u8 pnp_hdr_size;
if( (fp = fopen( f_name , "rb+")) == NULL)
{
printf("Error opening file\nclosing program ...");
return -1;
}
/* Save ROM source code file size which is located
at index 0x2 from beginning of file (zero based index) */
fseek(fp, ROM_SIZE_INDEX, SEEK_SET);
rom_size = fgetc(fp);
/* Patch the PnP Header checksum */
if(fseek(fp,PnP_HDR_PTR,SEEK_SET) != 0)
{
printf("Error seeking PnP Header");
fclose(fp);
return -1;
}
pnp_header_pos = fgetc(fp);/* save PnP header offset */
if(fseek(fp,(pnp_header_pos + PnP_HDR_SIZE_INDEX), SEEK_SET) != 0)
{
printf("Error seeking PnP Header Checksum\n");
fclose(fp);
return -1;
}
pnp_hdr_size = fgetc(fp);/* save PnP header size*/
/* reset current checksum to 0x00 so that
the checksum won't be wrong if calculated */
if(fseek(fp,(pnp_header_pos + PnP_CHKSUM_INDEX),SEEK_SET) != 0)
{
printf("Error seeking PnP Header Checksum\n");
fclose(fp);
return -1;
}
if(fputc(0x00,fp) == EOF)
{
printf("Error resetting PnP Header checksum value\n");
fclose(fp);
return -1;
}
/* calculate PnP Header Checksum */
if(fseek(fp,pnp_header_pos,SEEK_SET) != 0)
{
printf("Error seeking to calculate PnP Header checksum");
fclose(fp);
return -1;
}
/*
PnP BIOS Header size is calculated in every 16 bytes increment
*/
for(; pnp_hdr_counter < (pnp_hdr_size * 0x10) ; pnp_hdr_counter++)
{
pnp_checksum = ( (pnp_checksum + fgetc(fp)) % 0x100);
}
if(pnp_checksum != 0 ) {
pnp_checksum_byte = 0x100 - pnp_checksum;
} else {
pnp_checksum_byte = 0;
}
/* write PnP Header Checksum */
fseek(fp,(pnp_header_pos + PnP_CHKSUM_INDEX), SEEK_SET);
fputc(pnp_checksum_byte ,fp);
/* Overall file checksum handled from here on */
/* reset current checksum on checksum byte */
if( fseek(fp, ROM_CHKSUM, SEEK_SET) != 0 ) {
fclose(fp);
return -1;
} else {
fputc(0x00,fp);
}
/* calculate checksum byte */
if(CalcChecksum(fp,rom_size) == 0x00) {
checksum_byte = 0x00; /* checksum already O.K */
} else {
checksum_byte = 0x100 - CalcChecksum(fp,rom_size);
}
/* Write Checksum byte */
/* Put the file pointer at the checksum byte */
if(fseek(fp, ROM_CHKSUM, SEEK_SET) != 0)
{
printf("Failed to seek through the file\nclosing program ...");
fclose(fp);
return -1;
} else {
/* write the checksum to the checksum byte in the file */
fputc(checksum_byte, fp);
}
/* write to disk */
fclose(fp);
printf("PnP ROM successfully created\n");
return 0;
}
int main(int argc, char* argv[])
{
char out_f_name[MAX_FILE_NAME];
u32 target_size;
char* pch_temp[15];
if(argc != 3) /* not enough parameter */
{
printf("Usage: %s [input_filename] [target_binary_size]\n",argv[0]);
printf("input_filename = binary file that need to be patched into PCI PnP ROM\n"
"target_binary_size = the intended size of the PCI PnP ROM\n");
return -1;
}
strncpy(out_f_name, argv[1], MAX_FILE_NAME - 1);
target_size = strtoul(argv[2], pch_temp, 10);
if( 0 != (target_size % 512) ) {
printf("Error on input parameter. Invalid target binary size!\n");
return -1;
}
/* argv[1] is pointer to the filename parameter from user */
if(ZeroExtend(out_f_name, target_size) != 0)
{
printf("Error zero-extending output file ! \nclosing program ...");
return -1;
}
if(Patch2PnpRom(out_f_name) != 0)
{
printf("Error patching checksums ! \nclosing program ...");
return -1;
}
return 0;
}
The steps below is needed to be carried out to build a valid PCI PnP Expansion ROM from the code provided above. We are assuming that all of the command mentioned here is typed in a bash shell within Linux. We are using Slackware 9.0 linux distribution in our development testbed.
00000000 55 aa 04 eb 4f 00 00 00 00 00 00 00 00 00 00 00 |U...O...........|
00000010 2a 00 00 00 00 00 00 00 1c 00 34 00 50 43 49 52 |*.........4.PCIR|
00000020 04 90 78 81 00 00 18 00 00 02 00 00 04 00 00 00 |..x.............|
00000030 00 80 00 00 24 50 6e 50 01 02 00 00 00 5a 00 00 |....$PnP.....Z..|
00000040 00 00 00 00 00 00 02 00 00 14 00 00 00 00 5b 00 |..............[.|
00000050 00 00 00 00 25 cf 00 83 c8 20 cb b8 00 90 8e d0 |....%.... ......|
00000060 8c c8 8e d8 b8 00 00 8e c0 66 b9 00 03 00 00 29 |.........f.....)|
00000070 ff 29 f6 fc f3 a5 ea 7b 00 00 00 8c c8 8e d8 fa |.).....{........|
00000080 e8 2e 00 b0 ad e6 64 e8 27 00 b0 d0 e6 64 e8 31 |......d.'....d.1|
00000090 00 e4 60 66 50 e8 19 00 b0 d1 e6 64 e8 12 00 66 |..`fP......d...f|
000000a0 58 0c 02 e6 60 e8 09 00 b0 ae e6 64 e8 02 00 eb |X...`......d....|
000000b0 22 66 b9 00 00 01 00 e4 64 a8 02 74 04 e2 f8 eb |"f......d..t....|
000000c0 f0 c3 66 b9 00 00 01 00 e4 64 a8 01 75 04 e2 f8 |..f......d..u...|
000000d0 eb f0 c3 fb fa 0f 01 16 28 01 0f 20 c0 66 83 c8 |........(.. .f..|
000000e0 01 0f 22 c0 66 ea ec 00 00 00 08 00 31 f6 31 ff |..".f.......1.1.|
000000f0 66 b8 10 00 8e d8 66 b8 18 00 8e d0 bc 00 00 09 |f.....f.........|
00000100 00 e9 ea 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000110 ff ff 00 00 00 9a cf 00 ff ff 00 00 00 92 cf 00 |................|
00000120 ff ff 00 00 00 92 cf 00 1f 00 08 01 00 00 00 00 |................|
00000130 55 89 e5 83 ec 04 8b 45 08 66 89 45 fe 0f b7 55 |U......E.f.E...U|
00000140 fe ec 88 45 fd 0f b6 45 fd c9 c3 55 89 e5 83 ec |...E...E...U....|
00000150 04 8b 45 08 8b 55 0c 66 89 45 fe 88 55 fd 0f b6 |..E..U.f.E..U...|
00000160 45 fd 0f b7 55 fe ee c9 c3 55 89 e5 83 ec 18 c7 |E...U....U......|
00000170 45 fc 00 80 0b 00 c7 45 f8 d0 07 00 00 c7 45 f4 |E......E......E.|
00000180 00 00 00 00 8b 45 f4 3b 45 f8 7c 02 eb 1d 8b 45 |.....E.;E.|....E|
00000190 fc c6 00 00 8d 45 fc ff 00 8b 45 fc c6 00 0f 8d |.....E....E.....|
000001a0 45 fc ff 00 8d 45 f4 ff 00 eb d9 c7 44 24 04 0e |E....E......D$..|
000001b0 00 00 00 c7 04 24 d4 03 00 00 e8 8c ff ff ff c7 |.....$..........|
000001c0 44 24 04 00 00 00 00 c7 04 24 d5 03 00 00 e8 78 |D$.......$.....x|
000001d0 ff ff ff c7 44 24 04 0f 00 00 00 c7 04 24 d4 03 |....D$.......$..|
000001e0 00 00 e8 64 ff ff ff c7 44 24 04 00 00 00 00 c7 |...d....D$......|
000001f0 04 24 d5 03 00 00 e8 50 ff ff ff c9 c3 55 89 e5 |.$.....P.....U..|
00000200 83 ec 18 c7 45 f4 00 80 0b 00 c7 44 24 04 0e 00 |....E......D$...|
00000210 00 00 c7 04 24 d4 03 00 00 e8 2d ff ff ff c7 04 |....$.....-.....|
00000220 24 d5 03 00 00 e8 06 ff ff ff 66 0f b6 c0 c1 e0 |$.........f.....|
00000230 08 66 89 45 fe c7 44 24 04 0f 00 00 00 c7 04 24 |.f.E..D$.......$|
00000240 d4 03 00 00 e8 02 ff ff ff c7 04 24 d5 03 00 00 |...........$....|
00000250 e8 db fe ff ff 66 0f b6 d0 0f b7 45 fe 09 d0 66 |.....f.....E...f|
00000260 89 45 fe 0f b7 45 fe 8d 14 00 8d 45 f4 01 10 c7 |.E...E.....E....|
00000270 45 f8 00 00 00 00 8b 45 f8 03 45 08 80 38 00 75 |E......E..E..8.u|
00000280 02 eb 1b 8b 55 f4 8b 45 f8 03 45 08 0f b6 00 88 |....U..E..E.....|
00000290 02 8d 45 f8 ff 00 8d 45 f4 83 00 02 eb d8 0f b7 |..E....E........|
000002a0 55 fe 8b 45 f8 8d 04 10 66 89 45 fe 0f b6 45 fe |U..E....f.E...E.|
000002b0 89 44 24 04 c7 04 24 d5 03 00 00 e8 8b fe ff ff |.D$...$.........|
000002c0 c7 44 24 04 0e 00 00 00 c7 04 24 d4 03 00 00 e8 |.D$.......$.....|
000002d0 77 fe ff ff 0f b7 45 fe c1 e8 08 0f b6 c0 89 44 |w.....E........D|
000002e0 24 04 c7 04 24 d5 03 00 00 e8 5d fe ff ff c9 c3 |$...$.....].....|
000002f0 55 89 e5 83 ec 08 83 e4 f0 b8 00 00 00 00 29 c4 |U.............).|
00000300 c7 45 fc 1c 03 00 00 e8 5d fe ff ff 8b 45 fc 89 |.E......]....E..|
00000310 04 24 e8 e6 fe ff ff eb fe 00 00 00 48 65 6c 6c |.$..........Hell|
00000320 6f 20 57 6f 72 6c 64 00 00 00 00 00 00 00 00 00 |o World.........|
00000330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00010000
Testing the binary is trivial. We used the aforementioned flash4.exe to flash our rom.bin file from real mode DOS by invoking the command below :
flash4.exe -w rom.binThen we can see the result by activating boot from lan in our BIOS. We will see the "Hello World" displayed on the screen.
We have to emphasize that anyone who is building a PCI expansion ROM has to check the value of the Vendor ID and device ID within their source code. It's possible that the expansion ROM code is not executed at all (not "jumped-into" by the system bios) since there is a mismatch Vendor ID or Device ID between the expansion ROM and the value hardwired into the PCI chip. We haven't done further work on this issue, but we strongly suggest to avoid this mismatch.
There is a very specific circumstance where the PCI initialization routine that we make being screwed-up during development using this board ( Adaptec AHA-2940U SCSI controller card with soldered PLCC SST 29EE512 flash rom ). In this very specific case we were not be able to complete the boot of the testbed PC, since the mainboard BIOS possibly will hang at POST. In our case this was due to wrong placement of the entry point to the PCI initialization routine. This entry point is a jump instruction at offset 03h from the beginning of the rom binary image file, it should have been placed there but we inadvertently placed it at offset 04h. Thus, during the execution of PCI init function, the PC hangs. The "brute force" workaround for this is as follows :
This article have proved that it's possible to build a low cost x86 embedded system teaching tool by exploiting "flaw" in the PCI specification and PnP BIOS specification. The usability of our teaching tool described here can be improved by developing emulator in Linux for testing the expansion ROM binary developed by the students. We are looking forward to do research on such an emulator in the future. If you have any feedback regarding this article please don't hesitate to email us.
copyright © 2005,2006 Darmawan M S a.k.a Pinczakko