Pinczakko's Guide to Award BIOS Reverse Engineering

Click here to download the off-line version of this article.

Table of Contents

Side Note

A more detailed tutorial on BIOS Reverse engineering is in my book. The book is not just a compilation of the articles I wrote in this website. It's much more than that and it has a fundamental explanation on issues regarding the BIOS security aspect and analysis. I regard it more like a "log-book" about the work that I've done through the last couple of years in BIOS subject, including undisclosed research results. It provides a thorough tutorial on Award BIOS version 6.00PG reverse engineering as well as AMI BIOS version 8 reverse engineering. Moreover, the basics and fundamental concepts is explained in detail in the first part of the book. Therefore, newbies will find it very helpful. Here it is:

It takes quite a lot of effort to finish this book due to the excessive amount of material I wrote in it. Nonetheless, this is just the starting point. I plan to update the book some time in the future. The TIANO Core/UEFI has been gaining momentum recently and also the advances in LinuxBIOS are not minor either. These advances should take the BIOS technology one step above the ladder.

1. Foreword

I would like to welcome you to the darkside of a working example of spaghetty code, The Award BIOS. This article is not an official guide to award bios reverse engineering nor it's compiled by an Award Corp. insider. I'm just an ordinary curious person who really attracted to know how my computer BIOS works. I made this article available to the public to share my findings and looking for feedback from others since I'm sure I've made some "obscure mistakes" that I didn't realize during my reverse engineering process. There are several possibilities that make you reading this article now, perhaps you are an old-time BIOS hacker, perhaps you are a kind of person who really love "system programming" like me or you are just a curious person who like to tinker. One thing for sure, you'll get most of out of this article if you've done some BIOS hacking before and looking forward to improve your skill. However, I've made a prerequisite section below to ensure you've armed yourself with knowledge needed to get most out of this article.

You may be asking, why would anyone need this guide ? indeed, you need this guide if you found yourself cannot figure out how award BIOS code works. In my experience, unless you are disassembling a working BIOS binary, you won't be able to comprehend it. Also, you have to have the majority (if not all) of your mainboard chips datasheets. The most important one is the chipset datasheet.

The purpose of this article is to clean up the mess and positioned as a handy reference for myself and the reader as we are going through the BIOS disassembling session. I'm not held responsible about the correctness of any explanation in this article, you have to cross-check what I wrote here and what you have in your hand. Note that what I explain here based on 2Mbit(256KB) award bios version 4.51PGNM which I have. You can check it against award bios version 6.0PG or 6.0 to see if it's still valid. I'll working on that version when I have enough time. As an addition, I suggest you to read this article throughly from beginning to end to get most out of it.

2. Prerequisite

First, I would like to thank to the readers of the earlier "beta-version" of this article, from whom I consider that this part of the article should be included.

I have to admit that BIOS is somehow a state of the art code that requires lots of low level x86 knowledge that only matter to such a small audience such as operating system developer, BIOS developer, driver writer, possibly exploit and virus writer (yes exploit and virus writer! coz they are curious people). Due to this fact, there are couple of things that I won't explain here and it's your homework that you should do to comprehend this guide. They are :

2.1. PCI BUS

We'll begin with the PCI bus. I've been working with this stuff for quite a while. The official standard for the PCI bus system is maintained by a board named PCISIG (PCI Special Interest Group). This board actually is some sort of cooperation between Intel and some other big corporation such as Microsoft. Anyway, in the near future PCI bus will be fully replaced by a much more faster bus system such as Arapahoe (PCI-Express a.k.a PCI-e) and Hypertransport. But PCI will still remain a standard for sometime I think. I've read some of the specification of the Hypertansport bus, it's backward compatible with PCI. This means that the addressing scheme will remains the same or at least only needs a minor modification. This also holds true for the Arapahoe. One thing I hate about this PCI stuff is that the standard is not an open standard. Thus, you gotta pay a lot to get the datasheets and whitepapers. This become my main reason providing you with this sort of tute.

First, PCI bus is a bus which is 32 bits wide. This imply that communicating using this bus should be in 32 bits addressing mode. Pretty logical isn't it? So, writing or reading to this bus will require 32 bits addresses. Note that eventhough there is a 64-bit PCI bus, it's not natively supported, since PCI uses a dual address cycle to implement it. So, we can say that PCI primarily a 32-bit bus system.

Second, this bus system is defined in the port CF8h - CFBh which acts as the configuration address port and port CFCh - CFFh which acts as the configuration data port. These ports are used to configure the corresponding PCI chip, i.e. reading/writing the PCI chip configuration register values.

Third, this bus system force us to communicate with PCI chips with the following algorithm (from host CPU point of view):

As a note, as far as I know every bus/communication protocol implemented in chip design today uses similar algorithm to communicate with another chip which has a different bus protocol.

With the above definition, now I'll provide you with an x86 assembly code snippet that shows how to use those configuration ports.

I think the code above clear enough. In line one the current data in the processors general purpose registers were saved. Then comes the crucial part. As I said above, PCI is 32 bits bus system hence we have to use 32 bits chunk of data to communicate with them. We do this by sending the PCI chip a 32 bits address through eax register, and using port CF8h as the port to send this data. Here's an example of the PCI register (sometimes called offset) address format. In the routine above you saw :

....

mov eax,80000064h

....

the 80000064h is the address. The meaning of these bits are:

Now, we'll examine the previous value that was sent. If you're curious, you'll find out that 80000064h means we're communicating with the device in bus 0, device 0 , function 0 and at offset 64. Actually this is the memory controller configuration register of my mainboard's Northbridge. In most circumstances the PCI device that occupy bus 0, device 0, function 0 is the Hostbridge, but you'll need to consult your chipset datasheet to verify this. This stuff is pretty easy to be understood, isn't it ? The next routines are pretty easy to understand. But if you still feel confused you'd better learn assembly language a bit, since I'm not here to teach you assembly (!_!) . But, in general they do the following jobs: reading the offset data then modifying it then writing it back to the device, if not better to say tweaking it (^__^) .

2.2. ISA BUS

AFAIK, ISA bus is not a well standardized bus. Thus, any ISA device can reside virtually almost anywhere in the system's 16-bit I/O address space. My experience with ISA bus is very limited. I've only play with two chips this time around, the first is the CMOS chip and the second one is my mainboard's hardware monitoring chip, i.e. Winbond W83781D. Both chips uses the same "general algorithm" as mentioned above in the PCI BUS, i.e. :

My hardware monitoring chip defines port 295h as its address port (a.k.a index port) and port 296h as its data port. CMOS chip defines port 70h as its address port and port 71h as its data port.

3. Some Hardware Peculiarities

Due to its history, the x86 platform contains lots of hacks, especially its BIOS. This is due to the backward compatiblity that should be maintained by any x86 system. In this section I'll try to explain couple of stuff that I found during my BIOS disassembly journey that reveal these peculiarities.

3.1. BIOS Chip Addressing

The most important chips which responsible for the BIOS code handling are the southbridge and northbridge. In this respect, the northbridge is responsible for the system address space management, i.e. BIOS shadowing, handling accesses to RAM and forwarding transaction which uses BIOS ROM as its target to the southbridge which then eventually forwarded to BIOS ROM by southbridge. While the southbridge is responsible for enabling the ROM decode control, which will forward (or not) the memory addresses to be accessed to the BIOS ROM chip. The addresses shown below can reside either in the system DRAM or in BIOS ROM chip, depending on the southbridge and northbridge register setting at the time the BIOS code is executed.

The address ranges shown above contain the BIOS code and pretty much system specific. So, you have to consult your chipset datasheets to understand it. Also, note that the address above which will be occupied by the BIOS code during runtime (after BIOS code executes) is only F_seg i.e. F_0000h - F_FFFFh. However, certain operating system might "trash" this address and use it for their own purposes. The addresses written above only reflect the addressing of the BIOS ROM chip to the system address space when it's set to be accessed by the BIOS code or another code that accesses the BIOS ROM chip directly. The mainboard chipsets are responsible for the mapping of certain BIOS ROM chip area to the system address space. As we will see later, this mapping can be changed by programming certain chipset registers.

BIOS chip with capacity bigger than 1 Mbit, i.e. 2 Mbit and 4 Mbit chips has a quite different addressing for their lower bios area, i.e. C_seg, D_seg and other lower segment(s). In most cases, this area is mapped to near-4GB address range. This address range is handled by the norhtbridge analogous to the PCI address range. In this scheme the chipset behaves as follows:

Below is an example:

The conclusion is: modern day chipsets performs an emulation for F_seg and E_seg handling. This is a proof that modern day x86 systems maintains backward compatibility. However, this "kludge" sometimes referred to as the thing of the past that vendors should've been removed from x86 systems.

Below is the VIA693A chipset (Northbridge) system memory map just after system power-up as written in its datasheet.

     Table 4. System Memory MapSpace Start    Size  Address Range      Comment DOS   0        640K  00000000-0009FFFF  Cacheable VGA   640K     128K  000A0000-000BFFFF  Used for SMM BIOS  768K     16K   000C0000-000C3FFF  Shadow Ctrl 1 BIOS  784K     16K   000C4000-000C7FFF  Shadow Ctrl 1 BIOS  800K     16K   000C8000-000CBFFF  Shadow Ctrl 1 BIOS  816K     16K   000CC000-000CFFFF  Shadow Ctrl 1 BIOS  832K     16K   000D0000-000D3FFF  Shadow Ctrl 2 BIOS  848K     16K   000D4000-000D7FFF  Shadow Ctrl 2 BIOS  864K     16K   000D8000-000DBFFF  Shadow Ctrl 2 BIOS  880K     16K   000DC000-000DFFFF  Shadow Ctrl 2 BIOS  896K     64K   000E0000-000EFFFF  Shadow Ctrl 3 BIOS  960K     64K   000F0000-000FFFFF  Shadow Ctrl 3 Sys   1MB            00100000-DRAM Top  Can have hole Bus   D Top          DRAM Top-FFFEFFFF Init  4G-64K   64K   FFFEFFFF-FFFFFFFF  000Fxxxx alias

The most important thing to take into account here is the address aliasing, as you can see the FFFE_FFFFh- FFFF_FFFFh address range is an alias into 000Fxxxxh, this is where the BIOS ROM chip address mapped (at least in my mainboard, cross check with yours). But, we also have to consider that this only applies at the very beginning of boot stage (just after reset). After the chipset reprogrammed by the BIOS, this address range will be mapped into system DRAM chips. We can consider this as the Power-On default values. As a note, the majority of x86 chipsets use this address aliasing scheme, at least for the F-segment address range.

Another fact that we have to take into account: most chipset only provides default addressing scheme for F-segment just after power-up in its configuration registers, other "BIOS ROM segment(s)" remains inaccessible. The addressing scheme for these segments will be configured later by the bootblock code by altering the related chipset registers (in most cases the southbridge registers). The chipset that's being dissected here also belongs to this group.

Modern day systems connect the BIOS ROM chip to the southbridge through LPC(Low Pin Count) interface. However, the southbridge described in this article don't have such a interface. It's an old chipset and still uses ISA BUS to interface with the BIOS ROM chip.

3.2. Obscure Hardware Port

Some "obscure" hardware port which sometimes not documented in the chipset datasheets described below. Note that these info's were found from Intel ICH5, VIA 586B and VIA596B datasheet.

I/O Port address     Purpose 92h                  Fast A20 and Init Register 4D0h                 Master PIC Edge/Level Triggered (R/W) 4D1h                 Slave PIC Edge/Level Triggered (R/W)   Table 146. RTC I/O Registers (LPC I/F D31:F0)

I/O Port Locations    If U128E bit = 0            Function 70h and 74h           Also alias to 72h and 76h   Real-Time Clock (Standard RAM) Index Register 71h and 75h           Also alias to 73h and 77h   Real-Time Clock (Standard RAM) Target Register 72h and 76h                                       Extended RAM Index Register (if enabled) 73h and 77h                                       Extended RAM Target Register (if enabled)  NOTES: 1. I/O locations 70h and 71h are the standard ISA location for the real-time clock. The map for this bank is    shown in Table 147. Locations 72h and 73h are for accessing the extended RAM. The extended RAM bank is    also accessed using an indexed scheme. I/O address 72h is used as the address pointer and I/O address    73h is used as the data register. Index addresses above 127h are not valid. If the extended RAM is not    needed, it may be disabled. 2. Software must preserve the value of bit 7 at I/O addresses 70h. When writing to this address, software     must first read the value, and then write the same value for bit 7 during the sequential address write.     Note that port 70h is not directly readable. The only way to read this register is through Alt Access     mode. If the NMI# enable is not changed during normal operation, software can alternatively read this bit     once and then retain the value for all subsequent writes to port 70h.  The RTC contains two sets of indexed registers that are accessed using the two separate Index and Target registers (70/71h or 72/73h), as shown in Table 147.  Table 147. RTC (Standard) RAM Bank (LPC I/F D31:F0)

Index   Name 00h     Seconds 01h     Seconds Alarm 02h     Minutes 03h     Minutes Alarm 04h     Hours 05h     Hours Alarm 06h     Day of Week 07h     Day of Month 08h     Month 09h     Year 0Ah     Register A 0Bh     Register B 0Ch     Register C 0Dh     Register D 0Eh-EFh 114 Bytes of User RAM

3.3. "Relocatable" Hardware Port

There are several kinds of hardware port that is relocatable in the system I/O address space. In this BIOS, those ports include SMBus-related ports and Power-Management-Related ports. These ports has certain base address. This so called base address is controlled via programmable base address register. SMBus has SMBus base address register and Power-Management has Power-Management I/O base address register. Since these ports are programmable, the bootblock routine initializes the value of the base address registers in the very beginning of BIOS routine execution. Due to the programmable nature of these ports, one must start reverse engineering of BIOS in the bootblock to find out which port address(es) used by these programmable hardware ports. Otherwise one will be confused by the occurence of "weird" ports later during the reverse engineering process. An example of this case provided below.

Address    Hex                  Mnemonic F000:F604 BE C4 F6                  mov   si, 0F6C4h          ; addr of chipset reg mask F000:F607                         next_PCI_reg:               ; CODE XREF: Chipset_Reg_Early_Init+29 F000:F607 2E 8B 0C                  mov   cx, cs:[si] F000:F60A BC 10 F6                  mov   sp, 0F610h F000:F60D E9 F8 00                  jmp   Read_PCI_Byte F000:F60D                         ; --------------------------------------------------------------------------- F000:F610 12 F6                     dw 0F612h F000:F612                         ; --------------------------------------------------------------------------- F000:F612 2E 22 44 02               and   al, cs:[si+2] F000:F616 2E 0A 44 03               or    al, cs:[si+3] F000:F61A BC 20 F6                  mov   sp, 0F620h F000:F61D E9 02 01                  jmp   Write_PCI_Byte F000:F61D                         ; --------------------------------------------------------------------------- F000:F620 22 F6                     dw 0F622h F000:F622                         ; --------------------------------------------------------------------------- F000:F622 83 C6 04                  add   si, 4 F000:F625 81 FE 04 F7               cmp   si, 0F704h          ; are we done yet? ......... F000:F6F4 48 3B                     dw 3B48h                  ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr lo_byte

F000:F6F6 00                        db 0                      ; and mask

F000:F6F7 00                        db 0                      ; or mask F000:F6F7                                                     ; F000:F6F8 49 3B                     dw 3B49h                  ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr hi_byte F000:F6FA 40                        db 40h                    ; and mask F000:F6FB 40                        db 40h                    ; PwrMngmt IO Base Addr = IO Port 4000h ......... F000:F643 B9 90 3B                  mov   cx, 3B90h           ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr lo_byte F000:F646 B0 00                     mov   al, 0               ; set SMBus IO Base lo_byte to 00h F000:F648 BC 4E F6                  mov   sp, 0F64Eh F000:F64B E9 D4 00                  jmp   Write_PCI_Byte F000:F64B                         ; --------------------------------------------------------------------------- F000:F64E 50 F6                     dw 0F650h F000:F650                         ; --------------------------------------------------------------------------- F000:F650 B9 91 3B                  mov   cx, 3B91h           ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr hi_byte F000:F653 B0 50                     mov   al, 50h ; 'P'       ; set SMBus IO Base hi_byte to 50h,

F000:F653                                                     ; so, now SMBus IO Base is at port 5000h !!! F000:F655 BC 5B F6                  mov   sp, 0F65Bh F000:F658 E9 C7 00                  jmp   Write_PCI_Byte F000:F658                         ; --------------------------------------------------------------------------- F000:F65B 5D F6                     dw 0F65Dh ......... F000:F66A BA 05 40                  mov   dx, 4005h           ; access ACPI Reg 05h F000:F66D B0 80                     mov   al, 80h ;        ; setting reserved bit? .........

Certainly, there are more relocatable hardware ports than those described here, but at least you've been given the hints about it. So that, once you found code in the BIOS that seems to be accessing "weird" ports, you'll know where to go.

3.4. Expansion ROM Handling

There are couples of more things to take into account, such as the Video BIOS and other expansion ROM handling. I'll try to cover that stuff later when I've done dissecting BIOS code that handle it. But here's the basic run-down of PCI Expansion ROM handling in BIOS:

ISA Expansion ROM is not relevant these days. Therefore it's not discussed here.

4. Some Software Peculiarities

There are some tricky areas in the BIOS code due to the execution of some of its parts in ROM. I'll present some of my findings below.

4.1. call Instruction Peculiarity

call instruction is not available during bios code execution from within BIOS ROM chip. This is due to call instruction uses/manipulate stack while we don't have writeable area in BIOS ROM chip to be used for stack. What I mean by manipulating stack here is the implicit push instruction which is executed by the call instruction to write/save the return address in the stack. As we know clearly, address pointed to by ss:sp at this point is in ROM, meaning: we can't write into it. If you think, why don't we use the RAM altogether? the DRAM chip is not even available at this point. It hasn't been tested by the BIOS code. Thus, we haven't even know if RAM exists!

These days, to handle the lack of RAM in this stage of execution BIOS writers uses the so called "cache-as-RAM". In this approach, part of the CPU cache is used to provide stack functions. Therefore, from code-execution point of view it looks like RAM. Unfortunately, due to NDA restriction, I cannot elaborate on a sample code for cache-as-RAM implementation. If you want to find out for yourself. You can disassemble recent motherboard BIOS which is based on AMIBIOS8 code. You will find the code in its bootblock. I believe recent Phoenix-Award BIOS incorporate cache-as-RAM in its recent bootblock code as well.

4.2. retn Instruction Peculiarity

The peculiarity of retn instruction. There is macro that's called ROM_call as follows :

ROM_CALL MACRO RTN_NAME

LOCAL RTN_ADD

mov sp,offset DGROUP:RTN_ADD

jmp RTN_NAME

RTN_ADD: dw DGROUP:$+2

ENDM

an example of this macro "in action" as follows :

Address    Hex                  Mnemonic F000:6000                       F000_6000_read_pci_byte proc near   F000:6000   66 B8 00 00 00 80   mov   eax, 80000000h F000:6006   8B C1               mov   ax, cx          ; copy offset addr to ax F000:6008   24 FC               and   al, 0FCh        ; mask it F000:600A   BA F8 0C            mov   dx, 0CF8h F000:600D   66 EF               out   dx, eax F000:600F   B2 FC               mov   dl, 0FCh F000:6011   0A D1               or    dl, cl          ; get the byte addr F000:6013   EC                  in    al, dx          ; read the byte F000:6014   C3                  retn                  ; Return Near from Procedure F000:6014                       F000_6000_read_pci_byte endp ...... F000:6043 18 00                   GDTR_F000_6043 dw 18h   ; limit of GDTR (3 valid desc entry) F000:6045 49 60 0F 00               dd 0F6049h            ; GDT physical addr (below) F000:6049 00 00 00 00 00 00 00 00   dq 0                  ; null descriptor F000:6051 FF FF 00 00 0F 9F 00 00   dq 9F0F0000FFFFh      ; code descriptor: F000:6051                                                 ; base addr = F 0000h; limit=FFFFh; DPL=0; F000:6051                                                 ; exec/ReadOnly, conforming, accessed; F000:6051                                                 ; granularity=byte; Present; 16-bit segment F000:6059 FF FF 00 00 00 93 8F 00   dq 8F93000000FFFFh    ; data descriptor: F000:6059                                                 ; base addr = 00h; seg_limit=F FFFFh; DPL=0; F000:6059                                                 ; Present; read-write, accessed;  F000:6059                                                 ; granularity = 4 KByte; 16-bit segment ...... F000:619B 0F 01 16 43 60        lgdt  qword ptr GDTR_F000_6043 ; Load Global Descriptor Table Register F000:61A0 0F 20 C0              mov   eax, cr0 F000:61A3 0C 01                 or    al, 1           ; set PMode flag F000:61A5 0F 22 C0              mov   cr0, eax F000:61A8 EA AD 61 08 00        jmp   far ptr 8:61ADh ; jmp below in 16-bit PMode (abs addr F 61ADh) F000:61A8                                                 ; (code segment with base addr = F 0000h) F000:61AD                       ; --------------------------------------------------------------------- F000:61AD B8 10 00              mov   ax, 10h         ; load ds with valid data descriptor F000:61B0 8E D8                 mov   ds, ax          ; ds = data descriptor (GDT 3rd entry) ...... F000:61BC  B9 6B 00             mov   cx, 6Bh         ; DRAM arbitration control F000:61BF  BC C5 61             mov   sp, 61C5h

F000:61C2  E9 3B FE             jmp   F000_6000_read_pci_byte ; Jump

F000:61C2                       ; ------------------------------------------------------------------

F000:61C5  C7 61                dw 61C7h F000:61C7                       ; ------------------------------------------------------------------ F000:61C7  0C 02                or    al, 2           ; enable VC-DRAM

as you can see, you have to take into account that the retn instruction is affected by the current value of ss:sp register pair, but ss register is not even loaded with correct 16-bit protected mode value prior to using it! How this code even works? the answer is a bit complicated. Let's look at the last time ss register value was manipulated before the code above executed :

Address    Hex                  Mnemonic F000:E060 8C C8                 mov   ax, cs F000:E062 8E D0                 mov   ss, ax          ; ss = cs (ss = F000h a.k.a F_segment) F000:E064                       assume ss:F000 Note: this routine is executed in real-mode

as you can see, ss register is loaded with F000h (the current BIOS code 16-bit segment in real-mode). This code implies that the hidden descriptor cache register (that exist for every selector/segment register) is loaded with ss*16 or F_0000h physical address value. And this value is retained even when the machine is switched into 16-bit Protected Mode above since ss register is not reloaded. A snippet from Intel Software Developer Manual Vol.3 :

8.1.4. First Instruction Executed

The first instruction that is fetched and executed following a hardware reset is located at physical address FFFFFFF0H. This address is 16 bytes below the processor’s uppermost physical address. The EPROM containing the software-initialization code must be located at this address. The address FFFFFFF0H is beyond the 1-MByte addressable range of the processor while in real-address mode. The processor is initialized to this starting address as follows. The CS register has two parts: the visible segment selector part and the hidden base address part. In real address mode, the base address is normally formed by shifting the 16-bit segment selector value 4 bits to the left to produce a 20-bit base address. However, during a hardware reset, the segment selector in the CS register is loaded with F000H and the base address is loaded with FFFF0000H. The starting address is thus formed by adding the base address to the value in the EIP register (that is, FFFF0000 + FFF0H = FFFFFFF0H).

The first time the CS register is loaded with a new value after a hardware reset, the processor will follow the normal rule for address translation in real-address mode (that is, [CS base address = CS segment selector * 16]). To insure that the base address in the CS register remains unchanged until the EPROM based software-initialization code is completed, the code must not contain a far jump or far call or allow an interrupt to occur (which would cause the CS selector value to be changed).

also a snippet from DDJ (Doctor Dobbs Journal):

At power-up, the descriptor cache registers are loaded with fixed, default values, the CPU is in real mode, and all segments are marked as read/write data segments, including the code segment (CS). According to Intel, each time the CPU loads a segment register in real mode, the base address is 16 times the segment value, while the access rights and size limit attributes are given fixed, "real-mode compatible" values. This is not true. In fact, only the CS descriptor cache access rights get loaded with fixed values each time the segment register is loaded - and even then only when a far jump is encountered. Loading any other segment register in real mode does not change the access rights or the segment size limit attributes stored in the descriptor cache registers. For these segments, the access rights and segment size limit attributes are honored from any previous setting (see Figure 3). Thus it is possible to have a four giga-byte, read-only data segment in real mode on the 80386, but Intel will not acknowledge, or support this mode of operation.

If you want to know more about descriptor cache and how it works, you can search the web for articles about "descriptor cache" or "x86 unreal mode", the most comprehensive guide can be found in one of Doctor Dobbs Journal and Intel Software Developer Manual Vol.3 chapter 3 Protected Mode Memory Management in section 3.4.2 Segment Registers. Back to our ss register, now you know that the "actor" here is the descriptor cache register, especially its base address part. The visible part of ss is only a "place holder" and the "register-in-charge" for the "real" address calculation/translation is the hidden descriptor cache. Whatever you do to this descriptor cache will be in effect when any code, stack or data value addresses are translated/calculated. In our case, we have to use "stack segment" with "base address" at F_0000h physical address in 16-bit protected mode. This is not a problem, since the base address part of ss descriptor cache register already filled with F_0000h in one of the code above. This explains why the code above can be executed flawlessly. Another example:

Address    Hex                  Mnemonic F000:61BF  BC C5 61             mov   sp, 61C5h F000:61C2  E9 3B FE             jmp   F000_6000_read_pci_byte ; Jump F000:61C2                       ; ------------------------------------------------------------------ F000:61C5  C7 61                dw 61C7h

in this code we have to make ss:sp points to F_61C5h for retn instruction to work. Indeed, we've done it, since ss contains F_0000h (its descriptor cache base address part) and as you can see, sp contains 61C5h, the physical address pointed to by ss:sp is F_0000h+61C5h which is F_61C5h physical address.

5. Our Tools of Trade

This section will explain the tools that are needed to carry out the reverse engineering process. There's a new sub-section that exclusively explains about IDA Pro disassembler. It's provided due to sheer amount of request from readers. Anyway, thanks for the suggestions. I just got so bored this weekend and I decided to fulfill your requests (^__^)

5.1. What do we need anyway?

You are only as good as your tools. Yeah, this also holds true here. To begin the journey, we'll need some tools as follows:

5.2. Intro to IDA Pro Techniques

This sub-section will clear up the issue on using IDA Pro. Once you grasped the concept, you'll be able to use IDA Pro efficiently.

5.2.1. Introducing IDA Pro

Reverse code engineering is carried out to comprehend the algorithm used in a software by analyzing the executable file of the corresponding software. In most cases, the software only comes with the executable, without its source code. The same is true for the BIOS, it’s only the executable binary file that’s accessible to us. Reverse code engineering carried out with the help of some tools, i.e. debugger, disassembler, hexadecimal file-editor a.k.a hex-editor, ICE (In-Circuit Emulator), etc. We will only deal with disassembler in this sub-section, i.e. IDA Pro disassembler.

IDA Pro is a very powerful disassembler. It comes with support for plugin and scripting facility and support for more than fifty processor architectures. However, every powerful tool has its downside of being hard to use and IDA Pro is not an exception. This sub-section especially designed to address the issue.

There are several editions of IDA Pro: freeware edition, standard edition and advanced edition. The latest freeware edition is IDA Pro version 4.3 (AFAIK) and it's available for download at http://www.dirfile.com/ida_pro_freeware_version.htm. Newer IDA Pro freeware version is available for download here.It's the most limited of all IDA Pro version. It only supports x86 processor and doesn't come with plugin feature, but it comes at no cost, that's why it's presented here. Fortunately, it still comes with scripting feature. The standard and advanced editions of IDA Pro 4.3 of course differ from this freeware edition, they come with support for plugin and support for much more processor architecture. We are going to learn how to use the scripting feature in the next section.

Now, let's start to use IDA Pro freeware version to open a BIOS binary file. First, IDA Pro freeware version has to be installed. After the installation finished, one special step must be carried-out to prevent unwanted bug when IDA Pro v4.3 freeware opens up a BIOS file with *.rom extension. To do so, one must edit the IDA Pro configuration file located in the root directory of the IDA Pro installation directory. The name of the file is ida.cfg. Open this file by using any text editor (such as notepad) and look for the following lines:

 DEFAULT_PROCESSOR = {

/* Extension    Processor */

 "com" :       "8086"                  // IDA will try the specified

 "exe" :       ""                      // extensions if no extension is

 "dll" :       ""                      // given.

 "drv" :       ""

 "sys" :       ""

 "bin" :       ""  // Empty processor means the default processor

 "ovl" :       ""

 "ovr" :       ""

 "ov?" :       ""

 "nlm" :       ""

 "lan" :       ""

 "dsk" :       ""

 "obj" :       ""

 "prc" :       "68000"                 // PalmPilot programs

 "axf" :       "arm710a"

 "h68" :       "68000"                 // MC68000 for *.H68 files

 "i51" :       "8051"                  // i8051   for *.I51 files

 "sav" :       "pdp11"                 // PDP-11  for *.SAV files

 "rom" :       "z80"                   // Z80     for *.ROM files   "cla*":       "java"   "s19":        "6811"   "o":          ""   "*":          ""                      // Default processor }

Notice the line: "rom" : "z80" // Z80 for *.ROM files

This line must be removed or just replace the "z80" with "" in this line to disable the automatic request to load z80 processor module in IDA Pro upon opening a *.rom file. The bug occurred if the *.rom file is opened while this line is not changed since freeware IDA Pro doesn't come with z80 processor module. Thus, opening *.rom file by default will terminate IDA Pro. Some motherboard BIOS files comes with *.rom extension by default, even though it's very clear that it won't be executed in z80 processor. Fixing this bug will ensure that we will be able to open motherboard BIOS file with *.rom extension flawlessly. Note that the steps needed to remove other file-extension to processor-type "mapping" in this version of IDA Pro is similar to the z80 processor that is just described. Note that I haven't test the last version of IDA Pro freeware. Therefore I will appreciate if someone can inform me whether the bug that exists in verion 4.3 freeware still exists in the latest version.

Now let's proceed to open a sample BIOS file. This BIOS file is da8r9025.rom, BIOS file for Supermicro H8DAR-8 (OEM Only) motherboard. This motherboard used AMD-8131™ HyperTransport™ PCI-X Tunnel chip and AMD-8111™ HyperTransport™ I/O Hub chip. The dialog box below will be displayed when you start IDA Pro freeware version 4.3.

Just click OK to proceed. Then the next dialog box shown below will be displayed.

In this dialog box, you can try one of the three options, but we will just click on the Go button. This will start IDA Pro with empty workspace as shown below

Then locate and drag the file to be disassembled to the IDA Pro window (as shown above). In this case, IDA Pro will show the following dialog box.

In this dialog box, we will select Intel 80x86 processors: athlon as the Processor type in the drop down list box. Then click on the Set button to activate the new processor selection. Let the other option as it is. Code relocation will be carried out by using IDA Pro scripts in later section, then click OK. IDA Pro then shows the following dialog box.

This dialog box asks us to choose the default operating-mode of the x86 compatible processor during the disassembling process. AMD64 Architecture Programmer's Manual Volume 2: System Programming, February 2005 in section 14.1.5 page 417 states that:

"After a RESET# or INIT, the processor is operating in 16-bit real mode."

In addition, IA-32 Intel® Architecture Software Developer's Manual Volume 3: System Programming Guide 2004 section 9.1.1 states that:

"Table 9-1 shows the state of the flags and other registers following power-up for the Pentium 4, Intel Xeon, P6 family, and Pentium processors. The state of control register CR0 is 60000010H (see Figure 9-1), which places the processor is in real-address mode with paging disabled."

Thus, we can conclude that any x86 compatible processors start their execution in 16-bit real mode just after power-up and we have to choose 16-bit mode in this dialog box. It’s accomplished by clicking No in the dialog box. Then the following dialog box pops up.

This dialog box told us that IDA Pro can’t decide where the entry-point located. We have to locate it ourselves later. Just click OK to continue to the main window for the disassembly process.

Up to this point we are able to open the binary file within IDA Pro. This is not a trivial task for people new to IDA Pro. That's why it's presented in a step-by-step fashion. However, the output in the workspace is not yet usable. The next step is learning the scripting facility that IDA Pro provides to make sense about the disassembly database that IDA Pro generates.

5.2.2. IDA Pro Scripting And Key Bindings

Now we will proceed to try to decipher IDA Pro disassembly database shown in the previous sub-section with the help of the scripting facility. Before we proceed to analyze the binary, we have to learn some basic concepts about the IDA Pro scripting facility. IDA Pro scripts syntax are similar to C programming language. The syntax as follows:

Now is the time to put the theory into a simple working example, an IDA Pro sample script.

#include <idc.idc>

// relocate one segment

static relocate_seg(src, dest)

{

auto ea_src, ea_dest, hi_limit;

hi_limit = src + 0x10000;

ea_dest = dest;

for(ea_src = src; ea_src < hi_limit ; ea_src = ea_src + 4 )

  {

  PatchDword( ea_dest, Dword(ea_src));

  ea_dest = ea_dest + 4;

}

 Message("segment relocation finished (inside relocate_seg function)..\n");

}

static main()

{

 Message("creating target segment (inside entry point function main)...\n");

 SegCreate([0xF000, 0], [0x10000, 0], 0xF000, 0, 0, 0);

 SegRename([0xF000, 0], "_F000");

 relocate_seg([0x7000,0], [0xF000, 0]);

}

The square bracket, i.e. [ ] in the script above is an operator used to form the linear address from its parameters by shifting the first parameter to left four bits and then adding the second parameter into the result, for example: [0x7000, 0] means (0x7000 << 4) + 0 , i.e. 0x7_0000 linear address. This operator is just the same as MK_FP( , ) operator in previous versions of IDA Pro. One must read idc.idc file to see the "exported" function definition to understand this script completely, such as the Message, SegCreate and SegRename function. Another "exported" function that maybe of interest can be found in numerous *.idc file in the idc directory of IDA Pro installation folder. To be able to use the function, its definition have to be looked up in the exported function definition in the corresponding *.idc header file. For example, SegCreate function is defined in idc.idc as follows:

// Create a new segment

//      startea  - linear address of the start of the segment

//      endea    - linear address of the end of the segment

//                 this address will not belong to the segment

//                 'endea' should be higher than 'startea'

//      base     - base paragraph or selector of the segment.

//                 a paragraph is 16byte memory chunk.

//            If a selector value is specified, the selector should be

//                 already defined.

//      use32    - 0: 16bit segment, 1: 32bit segment

//      align    - segment alignment. see below for alignment values

//      comb  - segment combination. see below for combination values.

// returns: 0-failed, 1-ok

success SegCreate( long startea,long endea,long base,  long use32,

            long align,long comb);

A 512KB BIOS binary file must be opened in IDA Pro with the loading address set to 0000h to be able to execute the sample script above. This loading scheme is the same as explained in the previous sub-section. In this case, we will just open the binary file of Supermicro H8DAR-8 motherboard as in the previous sub-section and then execute the script. First, we must type the script above in a plain text file. We can use notepad or another ASCII file editor for this purpose. We will name the file as function.idc. The script then executed by clicking on the File | IDC file... menu or by pressing F2, then the dialog box below will be shown.

Just select the file and click open to execute the script. If there's any mistake in the script, IDA Pro will warn you with a warning dialog box. Executing the script will display the corresponding message in the message pane of IDA Pro as shown below.

The script above relocates the last segment (64KB) of the Supermicro H8DAR-8 BIOS code to the right place. One must be aware that IDA Pro is only an advanced tool to help the reverse code engineering task, it's not a magical tool that's going to reveal the overall structure of the BIOS binary without us being significantly involve in the process. The script relocates/copies BIOS code from physical/linear address 0x7_0000-0x7_FFFF to 0xF_0000-0xF_FFFF. The logical reason behind this algorithm is explained below.

AMD-8111 HyperTransport IO Hub Datasheet chapter 4 page 153 says that:

Note: The following ranges are always specified as BIOS address ranges. See DevB:0x80 for more information about how access to BIOS spaces may be controlled.

In addition, AMD64 Architecture Programmer's Manual Volume 2: System Programming, February 2005 in section 14.1.5 page 417 says that:

"Normally within real mode, the code-segment base address is formed by shifting the CS-selector value left four bits. The base address is then added to the value in EIP to form the physical address into memory. As a result, the processor can only address the first 1 Mbyte of memory when in real mode. However, immediately following RESET# or INIT, the CS selector register is loaded with F000h, but the CS base-address is not formed by left-shifting the selector. Instead, the CS base address is initialized to FFFF_0000h. EIP is initialized to FFF0h. Therefore, the first instruction fetched from memory is located at physical-address FFFF_FFF0h (FFFF_0000h+0000_FFF0h). The CS base-address remains at this initial value until the CS selector register is loaded by software. This can occur as a result of executing a far jump instruction or call instruction, for example. When CS is loaded by software, the new base-address value is established as defined for real mode (by left shifting the selector value four bits)."

From the references above, we conclude that address 000F_0000h - E000F_FFFFh is an alias to address FFFF_0000h - EFFFF_FFFFh, i.e. they both points to the same physical address range. Whenever the host (CPU) accesses some value in 000F_0000h - E000F_FFFFh address range, it's actually accessing the value at FFFF_0000h - EFFFF_FFFFh range and the reverse is also true. From this fact, we know that we have to relocate 64KB of the uppermost BIOS code to address 000F_0000h - E000F_FFFFh for further investigation. This decision is made based on my previous experience with various BIOS binary files, they generally references address with F000h used as the segment value within the BIOS code. Also, note that the last 64KB of the BIOS binary file is mapped to last 64KB of the 4GB address space, i.e. 4GB-64KB to 4GB, that's why we have to relocate the last 64KB.

Simple script that is only several lines can be typed and executed directly within IDA Pro without opening a text editor. IDA Pro provides a specific dialog box for this purpose and it can be accessed by pressing Shift+F2. This is more practical for simple task, but as the number of the routine grows, one might consider coding the script as described in the previous explanation due to limitation of the number of instruction that can be entered in the dialog box. In this dialog box, enter the script to be executed and click OK to execute the script. Below is an example script.

Note that there is no need for #include statement in the beginning of the script, since by default all of the functions that are exported by IDA Pro in its scripts header files (*.idc) is accessible within the scripting dialog box shown above. The main function is also doesn't need to be defined. In fact, anything you write within the dialog box entry will behave as if it's written inside the main function in an IDA Pro script file.

Anyway, you might want to go to the IDA Palace for more IDA Pro script samples. It will take a while to grasp them, but IDA Palace is definitely the place to go if you're curious about IDA Pro scripting.

At present, we are able to relocate the binary within IDA Pro; the next step is to disassemble the binary within IDA Pro. Before that, we need to know how the default key binding works in IDA Pro. Key binding is the "mapping" between the keyboard button and the command carried-out when the corresponding key is pressed. The cursor must be placed in the workspace before any command is carried-out in IDA Pro. The key binding is defined in idagui.cfg file that's located in IDA Pro installation directory. An excerpt of the key binding (hot key) is provided below.

"MakeCode"              =       'C'

"MakeData"              =       'D'

"MakeAscii"             =       'A'

"MakeUnicode"           =       0           // create unicode string

"MakeArray"             =       "Numpad*"

"MakeUnknown"           =       'U'

"MakeName"              =       'N'

//"MakeAnyName"           =       "Ctrl-N"

"ManualOperand"         =       "Alt-F1"

"MakeFunction"          =       'P'

"EditFunction"          =       "Alt-P"

"DelFunction"           =       0

"FunctionEnd"           =       'E'

One can alter idagui.cfg to change the default key binding, but we will only consider the default key binding. Now we have grasped the key binding concept, let's see how to use it in our binary. In the previous example, we are creating a new segment, i.e. 0xF000. Now, we will go to the first instruction that's executed in the BIOS within that segment, i.e. address 0xF000:0xFFF0. Press G, the dialog box below will be shown.

In this dialog box, enter the destination address. You must enter the address in complete form (segment:offset) as shown above, i.e. F000:FFF0. Then, click OK to go to the intended address. Note that you don't have to type the leading 0x character, since by default, the value within the input box is in hexadecimal. The result will be as shown below (inside IDA Pro workspace).

The next step to do is to convert the value in this address into a meaningful machine instruction. To do so, press C. The result is as shown below.

Then, we can follow the jump by pressing Enter. The result is as shown below.

We can return from the jump that we've just made by pressing Esc.

Up to this point, you've gained significant intuition to use IDA Pro. You just need to consult the key bindings in idagui.cfg in case want to do something and don't know what key to press. Now we're armed. What we need to do next is to understand the basic stuff by using the hex editor before proceeding through the disassembling session.

6. Award BIOS File Structure

Award BIOS file consists of several components. Some of the components are LZH level-1 compressed. We can recognize them by looking at the -lh5- signature in the beginning of that component using hex editor. Here's an example :

Address  Hex                                     ASCII

00000000 25F2 2D6C 6835 2D85 3A00 00C0 5700 0000 %.-lh5-.:...W...

00000010 0000 4120 010C 6177 6172 6465 7874 2E72 ..A ..awardext.r

00000020 6F6D DB74 2000 002C F88E FBDF DD23 49DB om.t ..,.....#I.

Beside the compressed components, there are also some "pure" 16-bit x86 binary components. Award BIOS execution begins in this "pure" binary (uncompressed) components.

We have to know the entry point to start our disassembly to this BIOS binary. We know that the execution of x86 processor begins in 16-bit real mode at address F000:FFF0 (physical address FFFF_FFF0h) following restart or power up, as per Intel Software Developer Manual Vol.3 "System Programming". Based on our intuition, this address must contain a 16-bit real mode x86 executable code. That's true. Below is the explanation of the memory map of the bios binary that's dissected in this article. It's a 2MBit/256 KB bios image for Iwill VD133 mainboard.

6.1. The Compressed Components

Memory map as seen in hex editor:

Note:

6.2. The Pure Binary Components

Memory map as seen in hex editor:

Note: Between some of the components lies padding bytes. Some are FFh bytes and some are 00h bytes.

6.3. The Memory Map In The Real System (Mainboard)

We have to note that the memory map described previously is for the BIOS binary as we see it from within a hex editor. In the mainboard BIOS chip, it's a bit different and more complex. It's mapped in my mainboard as follows (it may differ a bit with yours, consult your chipset documentation):

We have to be aware of this mapping during our journey. It's very easy to get lost due to the sheer complexity of the BIOS binary address mapping into the real system. But, there's a tip that will ease our effort during our disassembly session using IDA Pro:

Begin the disassembly session with the pure binary components. Start disassembly at address F000:FFF0h (3_FFF0h if we look at the binary from within hex editor). To do so, open the binary (VD30728.BIN , i.e. Iwill VD133 BIOS binary) in IDA Pro, then disassemble this file by setting its address mapping to start at C000:0000h and remember to disable segment naming so that we can see its real-mode address in the system during its execution. Adjust the other segments address by using IDA Pro scripts and remember to adjust the addressing scheme to conform to the chipset datasheet.

7. Disassembling the BIOS

Due to Intel System Programming Guide, we'll begin the disassembly session at address F000:FFF0h (note: look at the memory mapping above and adjust IDA Pro to suit it). You may ask: How this is even possible? Intel Software Developer Manual Vol. 3 (PROCESSOR MANAGEMENT AND INITIALIZATION - First Instruction Executed) says :

The first instruction that is fetched and executed following a hardware reset is located at physical address FFFFFFF0H.

The answer is : the northbridge chipset aliases address range FFFE_FFFFh-FFFF_FFFFh to 000F_xxxxh. Also, note that the southbridge has no means to alter the translation of this address range. It just passes the addresses directly to the BIOS ROM chip. Hence, there's no difference between address FFFF_FFF0h and F_FFF0h (or F000:FFF0 in "real-mode lingo") just after power-on or reset. It's that simple heh (-_^) . This is the BootBlock area. It always contains a far jump into the bootblock routine, mostly to F000:E05Bh. From this point we can continue the disassembly to cover the majority of the pure binary part. In reality, lots of the pure binary code are never executed at all since it's very seldom your system BIOS gets corrupted and the Bootblock POST (Power On Self Test) routine takes place, unless you're messing up with it :P.

7.1. Bootblock

From this point we can disassemble the bootblock routines. Now, I'll present some of the obscure and important areas of the BIOS code in the disassembled bootblock. This is with respect to my BIOS, yours may vary but IMHO it will be similar.

7.1.1 "Virtual Shutdown" routine

Address    Hex                       Mnemonic

F000:E07F BC 0B F8                  mov   sp, 0F80Bh          ; ret from this jmp redirected to 0E103h (F000:E103h) F000:E082 E9 7B 15                  jmp   Chipset_Reg_Early_Init

7.1.2 Chipset_Reg_Early_Init routine

Address    Hex                       Mnemonic

F000:F600                         Chipset_Reg_Early_Init proc near ; CODE XREF: F000:E082j F000:F600 66 C1 E4 10               shl   esp, 10h F000:F604 BE C4 F6                  mov   si, 0F6C4h          ; addr of chipset reg mask F000:F607                         next_PCI_reg:               ; CODE XREF: Chipset_Reg_Early_Init+29j F000:F607 2E 8B 0C                  mov   cx, cs:[si] F000:F60A BC 10 F6                  mov   sp, 0F610h F000:F60D E9 F8 00                  jmp   Read_PCI_Byte F000:F60D                         ; --------------------------------------------------------------------------- F000:F610 12 F6                     dw 0F612h F000:F612                         ; --------------------------------------------------------------------------- F000:F612 2E 22 44 02               and   al, cs:[si+2] F000:F616 2E 0A 44 03               or    al, cs:[si+3] F000:F61A BC 20 F6                  mov   sp, 0F620h F000:F61D E9 02 01                  jmp   Write_PCI_Byte F000:F61D                         ; --------------------------------------------------------------------------- F000:F620 22 F6                     dw 0F622h F000:F622                         ; --------------------------------------------------------------------------- F000:F622 83 C6 04                  add   si, 4 F000:F625 81 FE 04 F7               cmp   si, 0F704h          ; are we done yet? F000:F629 75 DC                     jnz   next_PCI_reg F000:F62B BA D0 04                  mov   dx, 4D0h            ; Master PIC Edge/Level Triggered F000:F62E 32 C0                     xor   al, al F000:F630 EE                        out   dx, al F000:F631 FE C2                     inc   dl                  ; Slave PIC Edge/Level Triggered F000:F633 EE                        out   dx, al F000:F634 B9 08 38                  mov   cx, 3808h           ; Bus#0 Dev#7 Func#0 (PCI2ISA Bridge) reg 8h(revision id) F000:F637 BC 3D F6                  mov   sp, 0F63Dh F000:F63A E9 CB 00                  jmp   Read_PCI_Byte F000:F63A                         ; --------------------------------------------------------------------------- F000:F63D 3F F6                     dw 0F63Fh F000:F63F                         ; --------------------------------------------------------------------------- F000:F63F 3C 10                     cmp   al, 10h             ; is silicon revision > 10h F000:F641 72 53                     jb    silicon_rev_lt_10h  ; in current mobo this jmp NOT taken (rev id = 12h) F000:F643 B9 90 3B                  mov   cx, 3B90h           ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr lo_byte F000:F646 B0 00                     mov   al, 0               ; set SMBus IO Base lo_byte to 00h F000:F648 BC 4E F6                  mov   sp, 0F64Eh F000:F64B E9 D4 00                  jmp   Write_PCI_Byte F000:F64B                         ; --------------------------------------------------------------------------- F000:F64E 50 F6                     dw 0F650h F000:F650                         ; --------------------------------------------------------------------------- F000:F650 B9 91 3B                  mov   cx, 3B91h           ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr hi_byte F000:F653 B0 50                     mov   al, 50h ; 'P'       ; set SMBus IO Base hi_byte to 50h,

F000:F653                                                     ; so, now SMBus IO Base is at port 5000h !!! F000:F655 BC 5B F6                  mov   sp, 0F65Bh F000:F658 E9 C7 00                  jmp   Write_PCI_Byte F000:F658                         ; --------------------------------------------------------------------------- F000:F65B 5D F6                     dw 0F65Dh F000:F65D                         ; --------------------------------------------------------------------------- F000:F65D B9 D2 3B                  mov   cx, 3BD2h           ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus Ctrl F000:F660 B0 01                     mov   al, 1               ; SMBus Controller Enable F000:F662 BC 68 F6                  mov   sp, 0F668h F000:F665 E9 BA 00                  jmp   Write_PCI_Byte F000:F665                         ; --------------------------------------------------------------------------- F000:F668 6A F6                     dw 0F66Ah F000:F66A                         ; --------------------------------------------------------------------------- F000:F66A BA 05 40                  mov   dx, 4005h           ; access ACPI Reg 05h  F000:F66A                                                     ; (see chipset init above for init of port 4000h as ACPI IO) F000:F66D B0 80                     mov   al, 80h ; ''       ; setting reserved bit? F000:F66F EE                        out   dx, al F000:F670 B9 41 3B                  mov   cx, 3B41h           ; general config F000:F673 BC 79 F6                  mov   sp, 0F679h F000:F676 E9 8F 00                  jmp   Read_PCI_Byte F000:F676                         ; --------------------------------------------------------------------------- F000:F679 7B F6                     dw 0F67Bh F000:F67B                         ; --------------------------------------------------------------------------- F000:F67B 0C 04                     or    al, 4               ; RTC Enable Signal Gated with PSON (SUSC#) in F000:F67B                                                     ; Soft-Off Mode F000:F67D BC 83 F6                  mov   sp, 0F683h F000:F680 E9 9F 00                  jmp   Write_PCI_Byte F000:F680                         ; --------------------------------------------------------------------------- F000:F683 85 F6                     dw 0F685h F000:F685                         ; --------------------------------------------------------------------------- F000:F685                         reinit_SCI_interrupt:       ; CODE XREF: Chipset_Reg_Early_Init+92j F000:F685 B9 42 3B                  mov   cx, 3B42h           ; B#0 D#7 F#3: SCI Interrupt Configuration F000:F688 BC 8E F6                  mov   sp, 0F68Eh F000:F68B EB 7B                     jmp   short Read_PCI_Byte F000:F68B                         ; --------------------------------------------------------------------------- F000:F68D 90                        db  90h ; E F000:F68E 90 F6                     dw 0F690h F000:F690                         ; --------------------------------------------------------------------------- F000:F690 A8 40                     test  al, 40h             ; check SUSC State F000:F692 74 F1                     jz    reinit_SCI_interrupt F000:F694 EB 27                     jmp   short exit F000:F696                         ; --------------------------------------------------------------------------- F000:F696                         silicon_rev_lt_10h:         ; CODE XREF: Chipset_Reg_Early_Init+41j F000:F696 B9 80 3B                  mov   cx, 3B80h F000:F699 B0 00                     mov   al, 0 F000:F69B BC A1 F6                  mov   sp, 0F6A1h F000:F69E E9 81 00                  jmp   Write_PCI_Byte F000:F69E                         ; --------------------------------------------------------------------------- F000:F6A1 A3 F6                     dw 0F6A3h F000:F6A3                         ; --------------------------------------------------------------------------- F000:F6A3 B9 81 3B                  mov   cx, 3B81h F000:F6A6 B0 50                     mov   al, 50h ; 'P' F000:F6A8 BC AE F6                  mov   sp, 0F6AEh F000:F6AB EB 75                     jmp   short Write_PCI_Byte F000:F6AB                         ; --------------------------------------------------------------------------- F000:F6AD 90                        db  90h ; E F000:F6AE B0 F6                     dw 0F6B0h F000:F6B0                         ; --------------------------------------------------------------------------- F000:F6B0 B9 84 3B                  mov   cx, 3B84h F000:F6B3 B0 07                     mov   al, 7 F000:F6B5 BC BB F6                  mov   sp, 0F6BBh F000:F6B8 EB 68                     jmp   short Write_PCI_Byte F000:F6B8                         ; --------------------------------------------------------------------------- F000:F6BA 90                        db  90h ;  F000:F6BB BD F6                     dw 0F6BDh F000:F6BD                         ; --------------------------------------------------------------------------- F000:F6BD                         exit:                       ; CODE XREF: Chipset_Reg_Early_Init+94j F000:F6BD 66 C1 EC 10               shr   esp, 10h F000:F6C1 C3                        retn F000:F6C1                         Chipset_Reg_Early_Init endp ......... F000:F6C4                         Begin_Chipset_Reg_Val F000:F6C4 47 38                     dw 3847h                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg 47h F000:F6C6 BF                        db 0BFh                   ; disable PCI delay transaction F000:F6C7 A0                        db 0A0h                   ; - Use INIT as CPU Reset F000:F6C7                                                     ; - Enable (ports 4D0-1 per EISA specification) F000:F6C7                                                     ;  F000:F6C8 50 00                     dw 50h                    ; Host Bridge reg 50h Request Phase Control F000:F6CA FF                        db 0FFh                   ; and mask F000:F6CB 10                        db 10h                    ; enable Defer Retry When HLOCK Active F000:F6CB                                                     ;  F000:F6CC 51 00                     dw 51h                    ; Host Bridge reg 51h Response Phase Control F000:F6CE DD                        db 0DDh                   ; reset reserved bits F000:F6CF 0D                        db 0Dh                    ; enable: F000:F6CF                                                     ; - Non-Posted IOW F000:F6CF                                                     ; - Concurrent PCI Master / Host Operation F000:F6CF                                                     ;  F000:F6D0 52 00                     dw 52h                    ; Host Bridge reg 52h Dynamic Defer Timer F000:F6D2 E0                        db 0E0h                   ; disable dynamic defer F000:F6D3 08                        db 8                      ; snoop stall count = 8 F000:F6D3                                                     ;  F000:F6D4 6B 00                     dw 6Bh                    ; Host Bridge reg 6Bh DRAM Arbitration Control F000:F6D6 00                        db 0                      ; - Park at last bus owner F000:F6D6                                                     ; - Disable Fast Read to Write turn-around F000:F6D6                                                     ; - Memory Module Configuration - Normal Operation F000:F6D6                                                     ; - MD Bus 2nd Level Strength Control = Normal Slew rate F000:F6D6                                                     ; - CAS Bus 2nd Level Strength Control = Normal Slew rate F000:F6D6                                                     ; - VC-DRAM disable F000:F6D6                                                     ; - Multi-Page Open disable F000:F6D7 01                        db 1                      ; Enable Multi-Page Open F000:F6D7                                                     ;  F000:F6D8 71 00                     dw 71h                    ; Host Bridge reg 71h CPU to PCI Flow Control 1 F000:F6DA 00                        db 0                      ; Disable:  F000:F6DA                                                     ; - Dynamic Burst F000:F6DA                                                     ; - Byte Merge F000:F6DA                                                     ; - PCI I/O Cycle Post Write F000:F6DA                                                     ; - PCI Burst F000:F6DA                                                     ; - PCI Fast Back-to-Back Write F000:F6DA                                                     ; - Quick Frame Generation F000:F6DA                                                     ; - 1 Wait State PCI Cycles F000:F6DB 08                        db 8                      ; Enable PCI Burst F000:F6DB                                                     ;  F000:F6DC 75 00                     dw 75h                    ; Host Bridge reg 75h PCI Arbitration 1 F000:F6DE 00                        db 0                      ; - PCI has priority F000:F6DE                                                     ; - REQ-based (arbitrate at end of REQ#) F000:F6DE                                                     ; - Disable PCI Master Bus Time-Out F000:F6DF 80                        db 80h                    ; Fair arbitration between PCI and CPU F000:F6DF                                                     ;  F000:F6E0 76 00                     dw 76h                    ; Host Bridge reg 76h PCI Arbitration 2 F000:F6E2 CF                        db 0CFh                   ; and mask F000:F6E3 80                        db 80h                    ; or mask F000:F6E3                                                     ;  F000:F6E4 41 38                     dw 3841h                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg 41h - ISA Bus control F000:F6E6 FF                        db 0FFh                   ; and mask F000:F6E7 01                        db 1                      ; ROM Write Enable F000:F6E7                                                     ;  F000:F6E8 43 38                     dw 3843h                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg43h-ROM Decode Control F000:F6EA 00                        db 0                      ; Disable ROMCS# decode for all ranges F000:F6EB 30                        db 30h                    ; Enable ROMCS# decode for the following ranges:

F000:F6EB                                                     ; 1. FFF00000h-FFF7FFFFh

F000:F6EB                                                     ; 2. 000E0000h-000EFFFFh F000:F6EB                                                     ;  F000:F6EC 4A 38                     dw 384Ah                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg4Ah-IDE Interrupt Routing F000:F6EE FF                        db 0FFh                   ; and mask F000:F6EF 40                        db 40h                    ; Access ports 00-FFh via SD bus (applies to F000:F6EF                                                     ; external devices only; internal devices such as F000:F6EF                                                     ; the mouse controller are not effected) F000:F6EF                                                     ;  F000:F6F0 5A 38                     dw 385Ah                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg5Ah-KBC / RTC Control F000:F6F2 F8                        db 0F8h                   ; Disable: F000:F6F2                                                     ; - Internal RTC F000:F6F2                                                     ; - Internal PS2 Mouse F000:F6F2                                                     ; - Internal KBC F000:F6F3 04                        db 4                      ; Internal RTC Enable F000:F6F3                                                     ;  F000:F6F4 48 3B                     dw 3B48h                  ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr lo_byte F000:F6F6 00                        db 0                      ; and mask F000:F6F7 00                        db 0                      ; or mask F000:F6F7                                                     ;  F000:F6F8 49 3B                     dw 3B49h                  ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr hi_byte F000:F6FA 40                        db 40h                    ; and mask F000:F6FB 40                        db 40h                    ; PwrMngmt IO Base Addr = IO Port 4000h F000:F6FB                                                     ;  F000:F6FC 41 3B                     dw 3B41h                  ; bus#0 dev#7 func#3: PwrMngmt&SMBus - General Config F000:F6FE FF                        db 0FFh                   ; and mask F000:F6FF 80                        db 80h                    ; enable: I/O for ACPI I/O Base F000:F6FF                                                     ;  F000:F700 76 38                     dw 3876h                  ; bus#0 dev#7 func#0: PCI2ISA Bridge reg76h-GPIO/Chip Select Control F000:F702 00                        db 0                      ; Disable GPO 24-26 F000:F703 02                        db 2                      ; Enable GPO 25 F000:F703                         End_Chipset_Reg_Val

7.1.3 Init_Interrupt_n_PwrMgmt routine

Address    Hex                       Mnemonic

F000:E1A0 BF A6 E1                  mov   di, 0E1A6h          ; ret addr below

F000:E1A3 E9 42 99                  jmp   Init_Interrupt_n_PwrMgmt

F000:E1A6                         ; ---------------------------------------------------------------------------

F000:E1A6 B0 C1                     mov   al, 0C1h ; '+'

....

F000:7CDD                         delay:                      ; CODE XREF: Init_Interrupt_n_PwrMgmt:delay

F000:7CDD E2 FE                     loop  delay

F000:7CDF FF E7                     jmp   di                  ; jmp to F000:E1A6h F000:7CDF                         Init_Interrupt_n_PwrMgmt endp

7.1.4 Call To "Early Silicon Support" Routine

Address    Hex                       Mnemonic

F000:E1AD E9 47 02                  jmp   Search_BBSS_Signature F000:E1AD                         ; --------------------------------------------------------------------------- F000:E1B0 B2 E1                     dw 0E1B2h F000:E1B2                         ; --------------------------------------------------------------------------- F000:E1B2 0B F6                     or    si, si F000:E1B4 74 38                     jz    short _BBSS_not_found F000:E1B6 8B DE                     mov   bx, si F000:E1B8 2E 8B 04                  mov   ax, cs:[si] F000:E1BB 25 00 F0                  and   ax, 0F000h F000:E1BE C1 E8 04                  shr   ax, 4 F000:E1C1 05 00 F0                  add   ax, 0F000h F000:E1C4 8E D8                     mov   ds, ax              ; ds=base addr for calculation of BBSS checksum F000:E1C6                           assume ds:nothing F000:E1C6 32 E4                     xor   ah, ah F000:E1C8 33 F6                     xor   si, si F000:E1CA B9 FF 0F                  mov   cx, 0FFFh           ; length of BBSS F000:E1CD                         next_byte:                  ; CODE XREF: 000FE1D0j F000:E1CD AC                        lodsb F000:E1CE 02 E0                     add   ah, al              ; calc 8-bit chksum for BBSS F000:E1D0 E2 FB                     loop  next_byte F000:E1D2 3A 24                     cmp   ah, [si] F000:E1D4 75 18                     jnz   short _BBSS_not_found F000:E1D6 2E 8B 07                  mov   ax, cs:[bx]         ; chsum ok, jump to (exec) the BBSS engine F000:E1D9 25 00 F0                  and   ax, 0F000h F000:E1DC 8B F0                     mov   si, ax F000:E1DE 81 C6 FC 0F               add   si, 0FFCh F000:E1E2 2E 8B 34                  mov   si, cs:[si] F000:E1E5 BC EC E1                  mov   sp, 0E1ECh F000:E1E8 FF E6                     jmp   si                  ; jmp to F000:60B4h (BBSS Engine), returns at F000:E1F8h F000:E1E8                                                     ; the BBSS engine is silicon support routine, used to F000:E1E8                                                     ; initialize PwrMgmt GPIO Ctlr, Host Ctlr and DRAM Ctlr F000:E1E8                         ; --------------------------------------------------------------------------- F000:E1EA 87                        db  87h ; E F000:E1EB DB                        db 0DBh ; - F000:E1EC F8 E1                     dw 0E1F8h ......... F000:E1F8                         BBSS_exec_done: F000:E1F8 B8 00 00                  mov   ax, 0 .........

The code above gets executed before the bootblock is copied to RAM. In case the RAM is faulty, the system will halt and output error code from system speaker.

7.1.5 Bootblock Is Copied And Executed In RAM

Address    Hex                       Mnemonic

F000:E2AA                         ;----------- Enter 16-bit Protected Mode (Flat) ------------ F000:E2AA                           assume ds:_F000h F000:E2AA 0F 01 16 F6 E4            lgdt  qword ptr word_F000_E4F6 F000:E2AF 0F 20 C0                  mov   eax, cr0 F000:E2B2 0C 01                     or    al, 1 F000:E2B4 0F 22 C0                  mov   cr0, eax F000:E2B7 EB 00                     jmp   short $+2           ; clear prefetch, enter 16-bit PMode. We're F000:E2B7                                                     ; using the "unchanged" hidden value of CS F000:E2B7                                                     ; register (descriptor cache) from previous F000:E2B7                                                     ; "PMode session" in memory_check_routine F000:E2B7                                                     ; for code segment desc F000:E2B9 B8 08 00                  mov   ax, 8 F000:E2BC 8E D8                     mov   ds, ax              ; init ds to 4GB-wide segment F000:E2BE                           assume ds:nothing F000:E2BE 8E C0                     mov   es, ax              ; init es to 4GB-wide segment F000:E2C0 F000:E2C0                         There are two locations to access E0000H ROM space, one is 0E0000H F000:E2C0                         and another is 0FFFE0000H. Some chipsets can not access onboard ROM F000:E2C0                         space at 0E0000H if any device also use the space on ISA bus. F000:E2C0                         To solve this problem , we need to change address to 0FFFE0000H F000:E2C0                         to read BIOS contents at 0E0000H space. F000:E2C0                           assume es:nothing F000:E2C0 66 BE 00 00 0E 00         mov   esi, 0E0000h F000:E2C6 67 66 81 7E 02 2D 6C 68+  cmp   dword ptr [esi+2], '5hl-' F000:E2CF 74 07                     jz    short LHA_sign_OK F000:E2D1 66 81 CE 00 00 F0 FF      or    esi, 0FFF00000h F000:E2D8 F000:E2D8                         move entire BIOS (i.e. original.tmp and bootblock) F000:E2D8                         from ROM at E0000h-FFFFFh to RAM at 10000h-2FFFFh F000:E2D8                         LHA_sign_OK:                ; CODE XREF: 000FE2CF F000:E2D8 66 BF 00 00 01 00         mov   edi, 10000h         ; destination addr = 1000:0

F000:E2DE 66 B9 00 80 00 00         mov   ecx, 8000h          ; copy 128 KByte to buffer (original.tmp n Bootblock)

F000:E2E4 67 F3 66 A5               rep movs dword ptr es:[edi], dword ptr [esi] F000:E2E8 0F 20 C0                  mov   eax, cr0 F000:E2EB 24 FE                     and   al, 0FEh F000:E2ED 0F 22 C0                  mov   cr0, eax F000:E2F0 EB 00                     jmp   short $+2           ; leave 16-bit protected mode (voodoo mode) F000:E2F2 EA F7 E2 00 20            jmp   far ptr _bootblock_in_RAM ......... 2000:E2F7                         ; --------------------------------------------------------------------------- 2000:E2F7                         Setup temporary stack at 0:1000H, at this point 2000:E2F7                         Bios code (last 128 Kbyte) is still compressed 2000:E2F7                         except the bootblock and decompression code 2000:E2F7 2000:E2F7                         _bootblock_in_RAM: 2000:E2F7 33 C0                     xor   ax, ax 2000:E2F9 8E D0                     mov   ss, ax 2000:E2FB                           assume ss:nothing 2000:E2FB BC 00 10                  mov   sp, 1000h

The last 128KB of BIOS code (E000:0000h - F000:FFFFh) get copied to RAM as follows :

7.1.6 Call to bios decompression routine and the jump into decompressed system bios

Address    Hex                       Mnemonic

2000:E3DC E8 33 01                  call  Decompress_System_BIOS 2000:E3DF EB 03                     jmp   short _sysbios_chksum_ok 2000:E3E1                         ; --------------------------------------------------------------------------- 2000:E3E1                         _sysbios_chksum_error:      ; CODE XREF: 0002E347 2000:E3E1                                                     ; 0002E350 ... 2000:E3E1 B8 00 10                  mov   ax, 1000h 2000:E3E4 2000:E3E4                         _sysbios_chksum_ok:         ; CODE XREF: 0002E3DF 2000:E3E4 8E D8                     mov   ds, ax              ; ax = 5000h if decompression successful, otherwise ax = 1000h 2000:E3E6                           assume ds:_1000h 2000:E3E6 B0 C5                     mov   al, 0C5h ; '+' 2000:E3E8 E6 80                     out   80h, al             ; manufacture's diagnostic checkpoint 2000:E3EA 2000:E3EA                         The source data segment is 5000H if checksum is good. 2000:E3EA                         the contents in this area is decompressed by routine "Decompress_System_BIOS". 2000:E3EA                         And segment 1000H is for shadowing original BIOS image if checksum 2000:E3EA                         is bad. BIOS will shadow bootblock and boot from it. 2000:E3EA E8 87 EB                  call  Copy_Decomprssd_E_n_F_Seg ; relocate dcomprssed from decompression seg to 2000:E3EA                                                     ; E_n_F_seg in RAM (Shadow the system BIOS) 2000:E3ED B0 00                     mov   al, 0 2000:E3EF E8 C7 10                  call  Setup_Cpu_Cache 2000:E3F2 2000:E3F2                         BIOS decide where to go from here. 2000:E3F2                         If BIOS checksum is good, this address F80DH is shadowed by 2000:E3F2                         decompressed code (i.e. original.bin and others), 2000:E3F2                         And "BootBlock_POST" will be executed if checksum is bad. 2000:E3F2 EA 0D F8 00 F0            jmp   far ptr loc_F000_F80D ; jump to decompressed system BIOS (F_seg) in RAM if decompression 2000:E3F2                                                     ; successful, otherwise jump to bootblock at the same address if 2000:E3F2                                                     ; decompression failed.

During execution of Decompress_System_BIOS routine, the compressed BIOS code (original.tmp) at 1000:0000h - 2000:FFFFh in RAM decompressed into 5000:0000h - 6000:FFFFh also in RAM. This decompressed System BIOS later relocated to E000:0000h - F000:FFFFh also in RAM. However, if decompression process failed, current compressed E_seg and F_seg located in RAM at 1000:0000h - 2000:FFFFh (of course including the copy of bootblock in RAM) will be relocated to E000:0000h-F000:0000h in RAM and then the bootblock error handling code will be executed. Note that the problem due to address aliasing and DRAM shadowing are handled during the relocation by setting the appropriate chipset registers. Below is the basic run-down of this routine:

Now, I'll present the "memory map" of the compressed and decompressed BIOS components just before jump into decompressed original.tmp is made. This is important since it will ease us in dissecting the decompressed original.tmp later. We have to note that by now, all code execution happens in RAM, no more code execution from within BIOS ROM chip.

The last thing to note is: what explained about bootblock here only covers the normal Bootblock code execution path, which means it didn't explain about the Bootblock POST that takes place in case original.tmp corrupted. I'll try to cover it later when I have time to dissect it. This is all about the bootblock right now, from this point on we'll dissect the original.tmp.

7.2. System BIOS a.k.a Original.tmp

We'll just proceed as in bootblock above, I'll just highlight the places where the "code execution path" are obscure. So, by now, you're looking at the disassembly of the decompressed original.tmp of my bios.

7.2.1. Entry point from "Bootblock in RAM"

Address    Hex                       Mnemonic F000:F80D                            This code is jumped into by the bootblock code  F000:F80D                            if everything went OK F000:F80D E9 02 F6                        jmp sysbios_entry_point ; 

This is where the bootblock jumps after relocating and write-protecting the system BIOS.

7.2.2. The awardext.rom and Extension BIOS components (lower 128KB bios-code) Relocation Routine

Address    Assembly Code

F000:EE12 sysbios_entry_point:    ; CODE XREF: F000:F80D

F000:EE12      mov ax, 0

F000:EE15      mov ss, ax         ; ss = 0000h

F000:EE17      mov sp, 1000h      ; setup stack at 0:1000h

F000:EE1A      call setup_stack   ; Call Procedure

F000:EE1D      call init_DRAM_shadowRW ; Call Procedure

F000:EE20      mov si, 5000h      ; ds=5000h (look at copy_mem_word)

F000:EE23      mov di, 0E000h     ; es=E000h (look at copy_mem_word)

F000:EE26      mov cx, 8000h      ; copy 64KByte

F000:EE29      call copy_mem_word ; copy E000h segment routine, i.e.

F000:EE29                         ; copy 64Kbyte from 5000:0h to E000:0h

F000:EE2C      call j_init_DRAM_shadowR ; Call Procedure

F000:EE2F      mov si, 4100h      ; ds = XGroup segment decompressed, i.e.

F000:EE2F                         ; at this point 4100h

F000:EE32      mov di, 6000h      ; es = new XGroup segment

F000:EE35      mov cx, 8000h      ; copy 64KByte

F000:EE38      call copy_mem_word ; copy XGroup segment , i.e.

F000:EE38                         ; 64Kbyte from 4100:0h to 6000:0h F000:EE3B      call Enter_UnrealMode ; jump below in UnrealMode F000:EE3E Begin_in_UnrealMode F000:EE3E      mov ax, ds F000:EE40      mov es, ax         ; es = ds (3rd entry in GDT) F000:EE40                         ; base_addr=0000 0000h;limit 4GB F000:EE42      assume es:nothing F000:EE42      mov esi, 80000h    ; mov esi,(POST_Cmprssed_Temp_Seg shl 4)

F000:EE42                         ; relocate lower 128KB bios code

F000:EE48      mov edi, 160000h

F000:EE4E      mov ecx, 8000h

F000:EE54      cld                ; Clear Direction Flag

F000:EE55      rep movs dword ptr es:[edi], dword ptr [esi] ; move

F000:EE55                         ; 128k data to 160000h (phy addr) F000:EE59      call Leave_UnrealMode ; Call Procedure F000:EE59 End_in_UnrealMode F000:EE5C      mov byte ptr [bp+214h], 0 ; mov byte ptr  F000:EE5C                         ; POST_SPEED[bp],Normal_Boot F000:EE61      mov si, 626Bh      ; offset 626Bh (E000h POST tests) F000:EE64      push 0E000h        ; segment E000h F000:EE67      push si            ; next instruction offset (626Bh) F000:EE68      retf               ; jmp to E000:626Bh

F000:7440 Enter_UnrealMode proc near   ; CODE XREF: F000:EE3B

F000:7440      mov ax, cs

F000:7442      mov ds, ax         ; ds = cs

F000:7444      assume ds:F000

F000:7444      lgdt qword ptr GDTR_F000_5504 ; Load Global Descriptor Table Register F000:7449      mov eax, cr0 F000:744C      or al, 1           ; Logical Inclusive OR F000:744E      mov cr0, eax F000:7451      mov ax, 10h F000:7454      mov ds, ax         ; ds = 10h (3rd entry in GDT) F000:7456      assume ds:nothing F000:7456      mov ss, ax         ; ss = 10h (3rd entry in GDT) F000:7458      assume ss:nothing F000:7458      retn               ; Return Near from Procedure F000:7458 Enter_UnrealMode endp

F000:5504 GDTR_F000_5504 dw 30h  ; DATA XREF: Enter_PMode+4

F000:5504                         ; GDT limit (6 valid desc)

F000:5506      dd 0F550Ah         ; GDT phy addr (below)

F000:550A      dq 0               ; null desc

F000:5512      dq 9F0F0000FFFFh   ; code desc (08h)

F000:5512                         ; base_addr=F0000h;seg_limit=64KB;code,execute/ReadOnly

F000:5512                         ; conforming,accessed;granularity=1Byte;16-bit segment;

F000:5512                         ; segment present,code,DPL=0

F000:551A      dq 8F93000000FFFFh ; data desc (10h)

F000:551A                         ; base_addr=0000 0000h;seg_limit=4GB;data,R/W,accessed;

F000:551A                         ; granularity=4KB;16-bit segment; segment present,

F000:551A                         ; data,DPL=0

F000:5522      dq 0FF0093FF0000FFFFh ; data desc 18h

F000:5522                         ; base_addr=FFFF0000h;seg_limit=64KB;data,R/W,accessed;

F000:5522                         ; 16-bit segment,granularity = 1 byte;

F000:5522                         ; segment present, data, DPL=0.

F000:552A      dq 0FF0093FF8000FFFFh ; data desc 20h

F000:552A                         ; base_addr=FFFF8000h;seg_limit=64KB;data,R/W,accessed;

F000:552A                         ; 16-bit segment,granularity = 1 byte;

F000:552A                         ; segment present, data, DPL=0.

F000:5532      dq 930F0000FFFFh   ; data desc 28h

F000:5532                         ; base_addr=F0000h;seg_limit=64KB;data,R/W,accessed;

F000:5532                         ; 16-bit segment,granularity = 1 byte;

F000:5532                         ; segment present, data, DPL=0.

Note: after the execution of code above, the "memory map" is changed once again. But this time only for the compressed "BIOS extension" i.e. the lower 128KB of BIOS code and the decompressed awardext.rom, the "memory map" mentioned in the Bootblock explanation above partially overwritten.

7.2.3. call to the POST routine a.k.a "POST jump table execution"

Address    Assembly Code

E000:626B The last of the these POST routines starts the EISA/ISA E000:626B section of POST and thus this call should never return. E000:626B If it does, we issue a POST code and halt. E000:626B  E000:626B This routine called from F000:EE68h E000:626B  E000:626B sysbios_entry_point_contd a.k.a NORMAL_POST_TESTS E000:626B      mov cx, 3          ; mov cx,STD_POST_CODE E000:626E      mov di, 61C2h      ; mov di,offset STD_POST_TESTS

E000:6271      call RAM_POST_tests ; this won't return in normal condition E000:6274      jmp short Halt_System ; Jump

E000:6276 ; --------------- S U B R O U T I N E --------------------------------------- E000:6276  E000:6276 RAM_POST_tests proc near ; CODE XREF: last_E000_POST+D

E000:6276                         ; last_E000_POST+18 ...

E000:6276      mov al, cl         ; cl = 3

E000:6278      out 80h, al        ; manufacture's diagnostic checkpoint

E000:627A      push 0F000h

E000:627D      pop fs             ; fs = F000h

E000:627F

E000:627F ;This is the beginning of the call into E000 segment

E000:627F ;POST function table

E000:627F      assume fs:F000

E000:627F      mov ax, cs:[di]    ; in the beginning :

E000:627F                         ; di = 61C2h ; ax = cs:[di] = 154Eh

E000:627F                         ; called from E000:2489 w/ di=61FCh (dummy) E000:6282      inc di             ; Increment by 1 E000:6283      inc di             ; di = di + 2 E000:6284      or ax, ax          ; Logical Inclusive OR E000:6286      jz RAM_post_return ; RAM Post Error E000:6288      push di            ; save di E000:6289      push cx            ; save cx E000:628A      call ax            ; call 154Eh (relative call addr)

E000:628A                         ; ,one of this call

E000:628A                         ; won't return in normal condition E000:628C      pop cx             ; restore all E000:628D      pop di E000:628E      jb RAM_post_return ; Jump if Below (CF=1) E000:6290      inc cx             ; Increment by 1 E000:6291      jmp short RAM_POST_tests ; Jump E000:6293 ; --------------------------------------------------------------------------- E000:6293  E000:6293 RAM_post_return:        ; CODE XREF: RAM_POST_tests+10 E000:6293                         ; RAM_POST_tests+18 E000:6293      retn               ; Return Near from Procedure E000:6293 RAM_POST_tests endp

E000:61C2 E0_POST_TESTS_TABLE:E000:61C2      dw 154Eh           ; Restore boot flag

E000:61C4      dw 156Fh           ; Chk_Mem_Refrsh_Toggle

E000:61C6      dw 1571h           ; keyboard (and its controller) POST

E000:61C8      dw 16D2h           ; chksum ROM, check EEPROM

E000:61C8                         ; on error generate spkr tone

E000:61CA      dw 1745h           ; Check CMOS circuitry

E000:61CC      dw 178Ah           ; "chipset defaults" initialization

E000:61CE      dw 1798h           ; init CPU cache (both Cyrix and Intel)

E000:61D0      dw 17B8h           ; init interrupt vector, also initialize

E000:61D0                         ; "signatures" used for Ext_BIOS components

E000:61D0                         ; decompression

E000:61D2      dw 194Bh           ; Init_mainboard_equipment & CPU microcode

E000:61D2                         ; chk ISA CMOS chksum ?

E000:61D4      dw 1ABCh           ; Check checksum. Initialize keyboard controller

E000:61D4                         ; and set up all of the 40: area data.

E000:61D6      dw 1B08h           ; Relocate extended BIOS code

E000:61D6                         ; init CPU MTRR, PCI REGs(Video BIOS ?)

E000:61D8      dw 1DC8h           ; Video_Init (including EPA proc)

E000:61DA      dw 2342h

E000:61DC      dw 234Eh

E000:61DE      dw 2353h           ; dummy

E000:61E0      dw 2355h           ; dummy

E000:61E2      dw 2357h           ; dummy

E000:61E4      dw 2359h           ; init Programmable Timer (PIT)

E000:61E6      dw 23A5h           ; init PIC_1 (programmable Interrupt Ctlr)

E000:61E8      dw 23B6h           ; same as above ?

E000:61EA      dw 23F9h           ; dummy

E000:61EC      dw 23FBh           ; init PIC_2

E000:61EE      dw 2478h           ; dummy

E000:61F0      dw 247Ah           ; dummy

E000:61F2      dw 247Ah

E000:61F4      dw 247Ah

E000:61F6      dw 247Ah

E000:61F8      dw 247Ch           ; this will call RAM_POST_tests again

E000:61F8                         ; for values below(a.k.a ISA POST) E000:61FA      dw 0 E000:61FA END_E0_POST_TESTS_TABLE

E000:247C last_E000_POST proc near E000:247C      cli                ; Clear Interrupt Flag

E000:247D      mov word ptr [bp+156h], 0

E000:2483      mov cx, 30h ; '0'

E000:2486      mov di, 61FCh      ; this addr contains 0000h E000:2489  E000:2489 repeat_RAM_POST_tests:  ; CODE XREF: last_E000_POST+10 E000:2489      call RAM_POST_tests ; this call immediately return

E000:2489                         ; since cs:[di]=0000h

E000:248C      jb repeat_RAM_POST_tests ; jmp if CF=1; not taken E000:248E      mov cx, 30h ; '0' E000:2491      mov di, 61FEh      ; cs:[di] contains 249Ch E000:2494  E000:2494 repeat_RAM_POST_tests_2: ; CODE XREF: last_E000_POST+1B E000:2494      call RAM_POST_tests ; this call should nvr return if

E000:2494                         ; everything is ok E000:2497      jb repeat_RAM_POST_tests_2 ; Jump if Below (CF=1) E000:2499      jmp Halt_System    ; E000:2499 last_E000_POST endp

E000:61FC ISA_POST_TESTSE000:61FC      dw 0

E000:61FE      dw 249Ch

E000:6200      dw 26AFh

E000:6202      dw 29DAh

E000:6204      dw 2A54h           ; dummy

E000:6206      dw 2A54h

E000:6208      dw 2A54h

E000:620A      dw 2A54h

E000:620C      dw 2A54h

E000:620E      dw 2A54h

E000:6210      dw 2A56h           ; dummy

E000:6212      dw 2A56h

E000:6214      dw 2A56h

E000:6216      dw 2A58h

E000:6218      dw 2A64h

E000:621A      dw 2B38h

E000:621C      dw 2B5Eh           ; dummy

E000:621E      dw 2B60h           ; dummy

E000:6220      dw 2B62h

E000:6222      dw 2BC8h           ; HD init ?

E000:6224      dw 2BF0h           ; game io port init ?

E000:6226      dw 2BF5h           ; dummy

E000:6228      dw 2BF7h           ; FPU error interrupt related

E000:622A      dw 2C53h           ; dummy

E000:622C      dw 2C55h

E000:622E      dw 2C61h           ; dummy

E000:6230      dw 2C61h

E000:6232      dw 2C61h

E000:6234      dw 2C61h

E000:6236      dw 2C61h

E000:6238      dw 2C61h

E000:623A      dw 2CA6h

E000:623C      dw 6294h           ; set cursor charcteristic

E000:623E      dw 62EAh

E000:6240      dw 6329h

E000:6242      dw 6384h

E000:6244      dw 64D6h           ; dummy

E000:6246      dw 64D6h

E000:6248      dw 64D6h

E000:624A      dw 64D6h

E000:624C      dw 64D6h

E000:624E      dw 64D6h

E000:6250      dw 64D6h

E000:6252      dw 64D6h

E000:6254      dw 64D6h

E000:6256      dw 64D6h

E000:6258      dw 64D6h

E000:625A      dw 64D6h

E000:625C      dw 64D6h

E000:625E      dw 64D8h           ; bootstrap

E000:6260      dw 66A1h

E000:6262      dw 673Ch

E000:6264      dw 6841h           ; issues int 19h (bootstrap) E000:6266      dw 0 E000:6266 END_ISA_POST_TESTS

Note:

The "POST jump table" procedures will set the Carry Flag (CF=1) if they encounter something wrong during their execution. Upon returning of the POST procedure, the Carry Flag will be tested, if it's set, then the "RAM_POST_TESTS" will immediately returns which will Halt the machine and output sound from system speaker.

7.2.4. The "segment vector" Routines

Below is only an example of its usage. There are lot of places where it's implemented. There are couple of variation of this "segment vector". Some will jump from segment E000h to F000h, some will jump from segment F000h to E000h, some jump from E000h to 6000h(relocated decompressed awardext.rom) and some jump from F000h to 6000h(relocated decompressed awardext.rom).

Address    Assembly Code

E000:1553 Restore_Warm_Boot_Flag proc near ; CODE XREF: POST_1S

.........

E000:155A   call  F_Vect_Read_CMOS_Byte ......... E000:156E Restore_Warm_Boot_Flag endp

Address    Machine Code          Assembly CodeE000:6CA2 F_Vect_Read_CMOS_Byte proc near ; CODE XREF: Restore_Warm_Boot_Flag+7p

E000:6CA2                             ; 000E1747p ...

E000:6CA2   push  0E000h

E000:6CA5   push  6CB3h

E000:6CA8   push  0EC31h E000:6CAB   push  0E4FDh              ; Read_CMOS_Byte

E000:6CAE   jmp   far ptr F000_Vector E000:6CB3 ; --------------------------------------------------------------------------- E000:6CB3   retn E000:6CB3 F_Vect_Read_CMOS_Byte endp

F000:EC30 F000_Vector:                ; CODE XREF: 000E1781J 000E17AAJ ...

F000:EC30   retn

F000:EC31 ; ---------------------------------------------------------------------------

F000:EC31   retf

F000:E4FD Read_CMOS_Byte proc near F000:E4FD   xchg  bx, bx F000:E4FF   nop F000:E500   out   70h, al             ; CMOS Memory: F000:E500                             ; used by real-time clock F000:E502   jcxz  short $+2 F000:E504   jcxz  short $+2 F000:E506   xchg  bx, bx F000:E508   in    al, 71h             ; CMOS Memory F000:E50A   jcxz  short $+2 F000:E50C   jcxz  short $+2 F000:E50E   retnF000:E50E Read_CMOS_Byte endp

Second variant: jump from segment E000h to 6000h

Address    Machine Code          Assembly Code

......... E000:1737   push  cs E000:1738   push  1743h               ; ret addr below

E000:173B   push  1829h               ; func addr in XGROUP ROM E000:173E   jmp   far ptr loc_6000_2 E000:1743 ; --------------------------------------------------------------------------- E000:1743   clc

E000:1744   retn

.........

6000:0000 locret_6000_0:              ; CODE XREF: 00060017j 6000:0000   retn                      ; jump to target procedure 6000:0001 ; --------------------------------------------------------------------------- 6000:0001   retf                      ; back to caller

6000:0002 loc_6000_2:                 ; push return addr for retn

6000:0002   push  1                   ; (addr_of retf above) 6000:0005   push  ax 6000:0006   pushf 6000:0007   cli 6000:0008   xchg  bp, sp 6000:000A   mov   ax, [bp+4]          ; mov ax,1 ; look at 1st inst above 6000:000D   xchg  ax, [bp+6]          ; xchg ax,word_pushed_by_org_tmp 6000:0010   mov   [bp+4], ax          ; [sp+4] = word_pushed_by_org_tmp 6000:0013   xchg  bp, sp              ; modify sp 6000:0015   popf 6000:0016   pop   ax 6000:0017   jmp   short locret_6000_0 ; jump into word_pushed_by_original.tmp

6000:1829      cli                    ; Clear Interrupt Flag

.........

6000:18B3      retn                   ; Return Near from Procedure

Third variant: jump from segment 6000h to F000h

Address    Assembly Code

6000:4F60 reinit_chipset proc far 6000:4F60      push ds 6000:4F61      mov ax, 0F000h 6000:4F64      mov ds, ax             ; ds = F000h 6000:4F66      assume ds:nothing 6000:4F66      mov bx, 0E38h          ; ptr to PCI reg vals (ds:bx = F000:E38h) 6000:4F69  6000:4F69 next_PCI_reg:               ; CODE XREF: reinit_chipset+3D 6000:4F69      cmp bx, 0EF5h          ; are we finished ? 6000:4F6D      jz exit_PCI_init       ; if yes, then exit 6000:4F6F      mov cx, [bx+1]         ; cx = PCI addr to read 6000:4F72      call setup_read_write_PCI ; on ret, ax = F70Bh, di = F725h

6000:4F75      push cs

6000:4F76      push 4F7Fh 6000:4F79      push ax                ; goto F000:F70B (Read_PCI_Byte) 6000:4F7A      jmp far ptr 0E000h:6188h ; goto_seg_F000 6000:4F7F ; --------------------------------------------------------------------------- 6000:4F7F      mov dx, [bx+3]         ; reverse-and mask

.........

E000:6188                   goto_F000_seg:          ; CODE XREF: HD_init_?+3BD

E000:6188                                           ; HD_init_?+578 ...

E000:6188 68 31 EC               push 0EC31h

E000:618B 50                     push ax

E000:618C 9C                     pushf              ; Push Flags Register onto the Stack

E000:618D FA                     cli                ; Clear Interrupt Flag

E000:618E 87 EC                  xchg bp, sp        ; Exchange Register/Memory with Register

E000:6190 8B 46 04               mov ax, [bp+4]     ; mov ax, EC31h

E000:6193 87 46 06               xchg ax, [bp+6]    ; xchg ret addr and EC31h

E000:6196 89 46 04               mov [bp+4], ax     ; mov [sp+4],[sp+6]

E000:6199 87 EC                  xchg bp, sp        ; Exchange Register/Memory with Register

E000:619B 9D                     popf               ; Pop Stack into Flags Register

E000:619C 58                     pop ax

E000:619D EA 30 EC 00 F0         jmp far ptr F000_func_vector ; Jump

F000:EC30                   F000_func_vector:       ; CODE XREF: chk_cmos_circuit+3C

F000:EC30 C3                     retn               ; jump to target function

F000:EC31                   ; -------------------------------------------------------------------

F000:EC31 CB                     retf               ; return to calling segment:offset (6000:4F7F)

F000:F70B read_PCI_byte proc near ; CODE XREF: enable_ROM_write?+4

.........

F000:F724      retn               ; Return Near to F000:EC31hF000:F724 read_PCI_byte endp

7.2.5. "chksum_ROM" Procedure

This procedure is part of the "E0_POST_TESTS", which is the POST routine invoked using the "POST jump table". There's no immediate return from within this procedure. But, a call into "Check_F_Next" will accomplish the "near return" needed to proceed into the next "POST procedure" execution.

E000:16D2                   chksum_ROM proc near ......... E000:16FF 74 1E                  jz Check_F_Next    ; yes. This jump will return this routine  E000:16FF                                           ; to where it's called ......... E000:171D EB E6                  jmp short spkr_endless_loop ; Jump E000:171D                   chksum_ROM endp

E000:171F                   Check_F_Next proc near  ; CODE XREF: chksum_ROM+2D

.........

E000:1743 F8                     clc                ; signal successful execution

E000:1744 C3                     retn               ; retn to RAM_POST_TESTS, proceed to next POST procE000:1744                   Check_F_Next endp ; sp = -6

7.2.6. Original.tmp Decompression routine for The "Extension_BIOS Components"

This is one of the most confusing thing to comprehend at first. But, by understanding it, we "virtually" have no more thing to worry about the "BIOS code execution path". I suspect that the same technique as what I'm going to explain here is used accross the majority of award bios. The basic run-down of this routine is explained below.

After the explanation above, we only need to follow the "POST jump table execution" to be able to know which "execution path" is taken by the BIOS in which circumstances. Having doing this approach we'll be able to do what we please to our "to be hacked" award bios >:). However, I ain't going to stop here, in the following sections I'll present the handling of some extension components that are interesting to me (^_^).

7.2.7. Microcode Update Routine

The microcode update routine is called from within POST_9S. The routine as follows:

E000:3BBE                         init_microcode proc near    ; CODE XREF: POST_9S+52

E000:3BBE E8 4E 38                  call  is_intel_CPU

E000:3BC1 75 5F                     jnz   short exit

E000:3BC3 66 B8 01 00 00 00         mov   eax, 1

E000:3BC9 0F A2                     cpuid

E000:3BCB BB 00 20                  mov   bx, 2000h

E000:3BCE 8E DB                     mov   ds, bx

E000:3BD0                           assume ds:_2000h

E000:3BD0 BB 00 90                  mov   bx, 9000h

E000:3BD3 3B 47 0C                  cmp   ax, [bx+0Ch]

E000:3BD6 75 05                     jnz   short decompress_microcode

E000:3BD8 E8 48 00                  call  get_cpu_microcode_ver

E000:3BDB 74 2C                     jz    short update_microcode

E000:3BDD                         decompress_microcode:       ; CODE XREF: init_microcode+18

E000:3BDD 66 50                     push  eax

E000:3BDF BF 08 00                  mov   di, 8               ; compressed microcode index (4*(lo_byte(4001h)+1))

E000:3BE2 E8 64 32                  call  near ptr decompress_BIOS_component E000:3BE5 66 58                     pop   eax E000:3BE7 72 39                     jb    short exit E000:3BE9 66 C1 EB 0B               shr   ebx, 0Bh E000:3BED 66 8B CB                  mov   ecx, ebx E000:3BF0 BB 00 40                  mov   bx, 4000h E000:3BF3 8E DB                     mov   ds, bx              ; point to seg 4000h (microcode decompression rsult) E000:3BF5                           assume ds:nothing E000:3BF5 33 DB                     xor   bx, bx              ; init bx E000:3BF7                         next_microcode:             ; CODE XREF: init_microcode+47 E000:3BF7 3B 47 0C                  cmp   ax, [bx+0Ch] E000:3BFA 75 05                     jnz   short microcode_not_match E000:3BFC E8 24 00                  call  get_cpu_microcode_ver E000:3BFF 74 08                     jz    short update_microcode E000:3C01                         microcode_not_match:        ; CODE XREF: init_microcode+3C E000:3C01 81 C3 00 08               add   bx, 800h            ; length of one microcode E000:3C05 E2 F0                     loop  next_microcode E000:3C07 EB 19                     jmp   short exit E000:3C09                         ; --------------------------------------------------------------------------- E000:3C09                         update_microcode:           ; CODE XREF: init_microcode+1D

E000:3C09                                                     ; init_microcode+41

E000:3C09 66 B9 79 00 00 00         mov   ecx, 79h ; 'y'

E000:3C0F 66 33 C0                  xor   eax, eax

E000:3C12 66 33 D2                  xor   edx, edx            ; microcode update sign

E000:3C15 8C D8                     mov   ax, ds

E000:3C17 66 C1 E0 04               shl   eax, 4

E000:3C1B 83 C3 30                  add   bx, 30h ; '0'

E000:3C1E 8B C3                     mov   ax, bx              ; eax = linear addr of the microcode

E000:3C20 0F 30                     wrmsr                     ; start microcode update E000:3C22                         exit:                       ; CODE XREF: init_microcode+3 E000:3C22                                                     ; init_microcode+29 ... E000:3C22 C3                        retn E000:3C22                         init_microcode endp

8. Rants and Raves

Hey, if you've read this article this far, you must be curious (^__^). Let's take a break a bit. I want to say some facts that are ridiculous. Well, at least to me. BIOS code is meant to be "twisted" so that code digger like us will find a hard time to figure it out. But, yeah here we are, understanding the big picture. Code digger rules my man! Nothing more dangerous than curiousity.

It's funny to see that the core LZH decompression routine that is used by award bioses (at least v4.51 that's dissected here) is just a complete "copy and paste" from Haruhiko Okumura's LZH code that anyone can find in the web. It's just the language that's different, Okumura's is in C while award's is in x86 assembly, the subroutines were exactly the same!

Another fact is, Phoenix code is very similar to award's code. I don't know who is "stealing" who, or perhaps there' s another source where both of them steal from (^__^). Oh no... I guess they are just doing reverse engineering like me with their tons of money. Unfortunately I don't have those tons of money he..he..he..

Well, let me stop ranting and continue my work on AMI BIOS. I guess we all waiting for it, right? It'll take sometime coz I'm very busy. If anyone of you who read this article have done it and had something to say or want to share your work with the world, I really keen to know. Why don't we join forces, right? my mail address is in the end of this article. I know some of us have done it.. we just haven't been in contact or perhaps it's better if we find our own way. Time will tell (-__-)

Greetz go to: Kris Kaspersky, Petr Soucek, Polaris, Havok, Zero, Mike Tedder a.k.a bpoint, apple_rom, Ilfak Guilfanov and many others who share their knowledge with the world.

9. Closing

What I've explained above possibly far too premature to be ended here. But, I consider this article finished here as the Beta 6 version. If you follow this article from beginning to end, you'll be able to understand the "BIG Picture" of how the Award BIOS works. I think all of the issue dissected here is enough to do any type of modification you wish to do with award bios. If you find any mistake(s) within this article or have any suggestion, please contact me. Goodluck with your BIOS reverse engineering journey, I hope you enjoy it as much as I do (^__^) .

Copyright © Darmawan M S a.k.a Pinczakko