Low Cost Embedded x86 Teaching Tool

0. Foreword

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.

1. Introduction

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.

2. Prerequisite

2.1. x86 Memory Map

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.

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.

2.2. PnP BIOS Architecture

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.

2.2.1. POST Execution flow

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.

2.2.2. Option ROM Support

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.

2.2.2.1. Option ROM Header

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:

Signature - All ISA expansion ROMs are currently required to identify themselves with a signature WORD of AA55h at offset 0. This signature is used by the System BIOS as well as other software to identify that an Option ROM is present at a given address.

Length - The length of the option ROM in 512 byte increments.

Initialization vector - The system BIOS will execute a FAR CALL to this location to initialize the Option ROM. A Plug and Play System BIOS will identify itself to a Plug and Play Option ROM by passing a pointer to a Plug and Play Identification structure when it calls the Option ROM's initialization vector. If the Option ROM determines that the System BIOS is a Plug and Play BIOS, the Option ROM should not hook the input, display, or IPL device vectors (INT 9h, 10h, or 13h) at this time. Instead, the device should wait until the System BIOS calls the Boot Connection vector before it hooks any of these vectors.

Note: A Plug and Play device should never hook INT 19h or INT 18h until its Boot Connection Vector, offset 16h of the Expansion Header Structure (section 3.2), has been called by the Plug and Play system BIOS.

If the Option ROM determines that it is executing under a Plug and Play system BIOS, it should return some device status parameters upon return from the initialization call. See the section on Option ROM Initialization for further details.

The field is four bytes wide even though most implementations may adhere to the custom of defining a simple three byte NEAR JMP. The definition of the fourth byte may be OEM specific.

Reserved - This area is used by various vendors and contains OEM specific data and copyright strings.

Offset to Expansion Header - This location contains a pointer to a linked list of Option ROM expansion headers. Various Expansion Headers (regardless of their type) may be chained together and accessible via this pointer. The offset specified in this field is the offset from the start of the option ROM header.

2.2.2.2. Expansion Header for Plug and Play

Signature - All Expansion Headers will contain a unique expansion header identifier. The Plug and Play expansion header's identifier is the ASCII string "$PnP" or hex 24 50 6E 50h (Byte 0 = 24h ... Byte 3 = 50h).

Structure Revision - This is an ordinal value that indicates the revision number of this structure only and does not imply a level of compliance with the Plug and Play BIOS version.

Length - Length of the entire Expansion Header expressed in sixteen byte blocks. The length count starts at the Signature field.

Offset of Next Header - This location contains a link to the next expansion ROM header in this Option ROM. If there are no other expansion ROM headers, then this field will have a value of 0h. The offset specified in this field is the offset from the start of the option ROM header.

Reserved - Reserved for Expansion

Checksum - Each Expansion Header is checksummed individually. This allows the software which wishes to make use of an expansion header (in this case, the system BIOS) the ability to determine if the expansion header is valid. The method for validating the checksum is to add up all byte values in the Expansion Header, including the Checksum field, into an 8-bit value. A resulting sum of zero indicates a valid checksum operation.

Device Identifier - This field contains the Plug and Play Device ID.

Pointer to Manufacturer String (Optional) - This location contains an offset relative to the base of the Option ROM which points to an ASCIIZ representation of the board manufacturer's name. This field is optional and if the pointer is 0 (Null) then the Manufacturer String is not supported.

Pointer to Product Name String (Optional) - This location contains an offset relative to the base of the Option ROM which points to an ASCIIZ representation of the product name. This field is optional and if the pointer is 0 (Null) then the Product Name String is not supported.

Device Type Code - This field contains general device type information that will assist the System BIOS in prioritizing the boot devices.

The Device Type code is broken down into three byte fields. The byte fields consist of a Base-Type code that indicates the general device type. The second byte is the device Sub-Type and its definition is Plug and Play BIOS Specification 1.0A Page 18 dependent upon the Base-Type code. The third byte defines the specific device programming interface, IF.-Type, based on the Base-Type and Sub-Type.

Refer to Plug and Play BIOS Specification 1.0A Appendix B for a description of Device Type Codes.

Device Indicators - This field contains indicator bits that identify the device as being capable of being one of the three identified boot devices: Input, Output, or Initial Program Load (IPL).

Boot Connection Vector (Real/Protected mode) - This location contains an offset from the start of the option ROM header to a routine that will cause the Option ROM to hook one or more of the primary input, primary display, or Initial Program Load (IPL) device vectors (INT 9h, INT 10h, or INT 13h), depending upon the parameters passed during the call.

When the system BIOS has determined that the device controlled by this Option ROM will be one of the boot devices (the Primary Input, Primary Display, or IPL device), the System ROM will execute a FAR CALL to the location pointed to by the Boot Connection Vector. The system ROM will pass the following parameters to the options ROM's Boot Connection Vector:

Disconnect Vector (Real/Protected mode) - This vector is used to perform a cleanup from an unsuccessful boot attempt on an IPL device. The system ROM will execute a FAR CALL to this location on IPL failure.

Bootstrap Entry Vector (Real/Protected mode) - This vector is used primarily for RPL (Remote Program Load) support. To RPL (bootstrap), the System ROM will execute a FAR CALL to this location. The System ROM will call the Real/Protected Mode Bootstrap Entry Vector instead of INT 19h if:

a) The device indicates that it may function as an IPL device,

b) The device indicates that it does not support the INT 13h Block Mode interface,

c) The device has a non-null Bootstrap Entry Vector,

d) The Real/Protected Mode Boot Connection Vector is null.

The method for supporting RPL is beyond the scope of this specification. A separate specification should define the explicit requirements for supporting RPL devices.

Reserved - Reserved for Expansion

Static Resource Information Vector - This vector may be used by non-Plug and Play devices to report static resource configuration information. Plug and Play devices should not support the Static Resource Information Vector for reporting their configuration information. This vector should be callable both before and/or after the option ROM has been initialized. The call interface for the Static Resource Information Vector is as follows:

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:

Refer to section 4.2 for a complete description of the elements that make up the System Device Node data structure.

For example, an existing, non-Plug and Play SCSI card vendor could choose to rev the SCSI board's Option ROM to support the Plug and Play Expansion Header. While this card wouldn't gain any of the configuration benefits provided to full hardware Plug and Play cards, it would allow Plug and Play software to determine the devices configuration and thus ensure that Plug and Play cards will map around the static SCSI board's allocated resources.

2.2.2.3. Option ROM Initialization

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:

For other bus architectures refer to the appropriate specification for register parameters on entry. During initialization, a Plug and Play Option ROM may hook any vectors and update any data structures required for it to access any attached devices and perform the necessary identifications and initializations. However, upon exit from the initialization call, the Option ROM must restore the state of any vectors or data structures related to boot devices (INT 9h, INT 10h, INT 13h, and associated BIOS Data Area [BDA] and Extended BIOS Data Area [EBDA] variables).

Upon exit from the initialization call, Plug and Play Option ROMs should return some boot device status information in the following format:

Return Status from Initialization Call:

2.2.2.4. Option ROM Initialization flow

The following outlines the typical steps used to initialize Option ROMs during a Plug and Play system BIOS POST:

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.

2.3. PCI PnP Expansion ROM Architecture

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 :

As we can see in this header layout, there exist a special register that handle expansion ROM addressing. Below is an explanation about the Expansion ROM Base Address Register (XROMBAR) and the related Expansion ROM from the PCI specification[4].

2.3.1. Expansion ROM Base Address Register

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.

Bit 0 in the register is used to control whether or not the device accepts accesses to its expansion ROM. When this bit is 0, the device�s Expansion ROM address space is disabled. When the bit is 1, address decoding is enabled using the parameters in the other part of the base register. This allows a device to be used with or without an expansion ROM depending on system configuration. The Memory Space bit in the Command register has precedence over the Expansion ROM enable bit. A device must respond to accesses to its expansion ROM only if both the Memory Space bit and the Expansion ROM Base Address Enable bit are set to 1. This bit's state after RST# is 0. In order to minimize the number of address decoders needed on a device, it may share a decoder between the Expansion ROM Base Address register and other Base Address registers.41 When expansion ROM decode is enabled, the decoder is used for accesses to the expansion ROM and device independent software must not access the device through any other Base Address registers.

41Note that it is the address decoder that is shared, not the registers themselves. The Expansion ROM Base Address register and other Base Address registers must be able to hold unique values at the same time.

2.3.2. PCI Expansion ROMs

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:

One major difference in the usage model between PCI expansion ROMs and standard ISA, EISA, and MC ROMs is that the ROM code is never executed in place. It is always copied from the ROM device to RAM and executed from RAM. This enables dynamic sizing of the code (for initialization and runtime) and provides speed improvements when executing runtime code.

2.3.2.1. PCI Expansion ROM Contents

PCI device expansion ROMs may contain code (executable or interpretive) for multiple processor architectures. This may be implemented in a single physical ROM which can contain as many code images as desired for different system and processor architectures as shown in the picture below. Each image must start on a 512-byte boundary and must contain the PCI expansion ROM header. The starting point of each image depends on the size of previous images. The last image in a ROM has a special encoding in the header to identify it as the last image.

2.3.2.1.1. PCI Expansion ROM Header Format

The information required in each ROM image is split into two different areas. One area, the ROM header, is required to be located at the beginning of the ROM image. The second area, the PCI Data Structure, must be located in the first 64 KB of the image. The format for the PCI Expansion ROM header is given below. The offset is a hexadecimal number from the beginning of the image and the length of each field is given in bytes. Extensions to the PCI Expansion ROM Header and/or the PCI Data Structure may be defined by specific system architectures. Extensions for PC-AT compatible systems are described later.

ROM Signature

Pointer to PCI Data Structure

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.

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.

2.3.2.1.2. PCI Data Structure Format

The PCI Data Structure must be located within the first 64 KB of the ROM image and must be DWORD aligned. The PCI Data Structure contains the following information:

2.3.2.2. Power-on Self Test (POST) Code

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.

2.3.2.3. PC-compatible Expansion ROMs

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.

2.3.2.3.1. POST Code Extensions

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:

Before system boot, the POST code must make the RAM area containing expansion ROM code read-only. POST code must handle VGA devices with expansion ROMs in a special way. The VGA device�s expansion BIOS must be copied to 0C0000h. VGA devices can be identified by examining the Class Code field in the device�s Configuration Space.

2.3.2.3.2. INIT Function Extensions

PC-compatible expansion ROMs contain an INIT function that is responsible for initializing the I/O device and preparing for runtime operation. INIT functions in PCI expansion ROMs are allowed some extended capabilities because the RAM area where the code is located is left writable while the INIT function executes.

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.

2.3.2.3.3. Image Structure

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.

2.4. PCI PnP Expansion ROM Peculiarity

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 :

3. Implementation Sample

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.

3.1. Hardware

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 :

3.2. Software Development Tool

There are three kind of software that are needed for the development of this sample :

3.3. The PCI PnP Expansion ROM Source Code

The basic run-down of what happens when the compiled source code executed as follows:

In the next two sections we will be dealing with the expansion rom source code. The first section will provide the source code of the expansion ROM itself, while the second one will provide the source code of the utility used to patch the binary file resulting from the first-section's source code into a valid PCI PnP Expansion ROM.

3.3.1. Core PCI PnP Expansion ROM Source Code

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

  }

3.3.2. PCI PnP Expansion ROM Checksum Utility Source Code

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;

 } 

3.3.3. PCI PnP Expansion ROM Build Step

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.

The result of these build steps is shown below. We are using hexdump utility from our Slackware Linux to obtain the result by invoking "hexdump -C rom.bin" in bash shell.

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

3.4. Testing The Custom Build PCI PnP Expansion ROM

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.bin

Then we can see the result by activating boot from lan in our BIOS. We will see the "Hello World" displayed on the screen.

3.5. Potential Bug and Its Workaround

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 :

These procedure probably a dangerous procedure, so it has to be carried-out very carefully. However, our experience shows that it works perfectly in our testbed without causing any damages.

4. Closing

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.

5. References

Copyright © Darmawan M S a.k.a Pinczakko