Chapter 8
Chapter 8
In this chapter, I’ll explore BIOS extensions in more detail with a set of routines
that capture interrupts, support the Firmware Development Board’s power failure
detection hardware, and record information in nonvolatile storage. You can use
either the EEPROM or the battery backed RAM circuitry, as long as the firmware
can enable and disable the memory chip’s -WE line.
Holding that button can become awkward at times, particularly when you’re also
maneuvering a scope probe or two, so I rewired the front panel keyboard lock
switch in parallel with the button. Because we have yet to use the keyboard, I
figured the keyboard lock might be superfluous.
Incidentally, should you ever come up against a “locked” PC clone, just whip out
your Swiss Army knife’s Phillips blade, unscrew the clone’s case, yank (or slice) the
lock switch wires, and fire that sucker up. I trust I’m not compromising the security
of what was once the Free World by letting that trick out of the bag. The Original
IBM PC AT had a lock that disabled the keyboard and secured the metal cover to
prevent just such an assault.
Extension Essentials
The Original PC BIOS didn’t permit any extensions, which led to some truly
remarkable kludges as each vendor devised different and mutually incompatible
ways to glue new functions into old PCs. The method we’ll use dates back to a PC
BIOS revision slightly before the XT. That means, for all intents and purposes,
every IBM PC now handles BIOS extensions the same way.
133
Embedded PCs ISA Bus.book : Chapter 8.fm Page 134 Tuesday, July 1, 1997 7:32 AM
Schematic 1
This simple hardware, a button and a resistor, allows you to skip over a malfunctioning
BIOS extension during a boot and prevent a system crash. The supporting circuitry
appeared in Chapter 7 as part of the battery backed RAM and watchdog controls.
The code at forms an escape hatch I suggest you build into all your
extensions, at least while debugging them. With the external button pressed (or the
key lock switch ON) while booting, simply updates the LEDs and returns
to the BIOS. This can save your bacon when your new extension crashes the BIOS
boot sequence. Trust me. It can happen to you, too.
With the switch released or the lock switch OFF, the code shown in Listing 2
makes the whole software development process I’m using in this chapter work
correctly. As Steve Ciarcia often puts it, “Let me explain…”
134
Embedded PCs ISA Bus.book : Chapter 8.fm Page 135 Tuesday, July 1, 1997 7:32 AM
Listing 1
This code fragment resides in the battery backed RAM at C800:0000 on the Firmware
Development Board. The BIOS passes control to the instruction at offset 0003 (just after
the length byte) during the power-on sequence. This code first checks the pushbutton
switch input bit; if the button is pressed, the code immediately returns to the BIOS.
!"#$%%%
!& '(
' ) *+!&
,, -
. -,.
7
33352855-
33362 2-5
#
0&: ;' < )
; ;
' ;+*'1* &
/ = '1&) ! >56
) 5 &'
!#
As you know by now, COM files contain an exact binary image of the program’s code
and data. They date back to the days of CP/M and 8080 CPUs, when 64 KB of
RAM was all even a big spender could have. The CP/M operating system reserved
the first 256 bytes of RAM to control the 8080’s reset and interrupt vectors and
provide some entry points. Therefore, it loaded your COM programs at absolute
RAM address 0100.
MS-DOS adopted much the same memory layout, except that 64 KB of RAM
suddenly seemed not quite so much after all. A COM file fit neatly into one 64 KB
segment atop the reserved 256 bytes, which, still filled with operating system stuff,
became known as the Program Segment Prefix. DOS handles EXE files somewhat
differently, their PSPs live in a different segment, and we’ll get to them later, but,
135
Embedded PCs ISA Bus.book : Chapter 8.fm Page 136 Tuesday, July 1, 1997 7:32 AM
Listing 2
With the switch open, the next step adjusts the segment registers. The RETF instruction
loads the new values into CS and IP from the stack. Note that this code uses the FS and
GS segment registers found in ’386 and higher CPUs and will not run on earlier CPUs.
3334!' '55!&0
2-
!"#!@#245-5
'-!'7256
'#
0&: ;,@ 2? 58
! ?? '2
! ?? &6) 0 2
! B'#)!C
for now, the key point remains that COM files start at 0100 for simple, historical,
largely arbitrary reasons.
Although all of the code and data addresses within a COM file assume that it’s
loaded at offset 0100, the actual disk file does not include those first 256 bytes. The
instruction at offset 0100 within the program’s code segment resides at offset 0000
relative to the start of the file. DOS must set up the segment registers so that the
offsets become correct within the segment where the program will be loaded.
The diskette boot loader in Chapter 1 simulates this process. It loads your program
from diskette into RAM at address 1000:0100 and places nothing at all in the first
136
Embedded PCs ISA Bus.book : Chapter 8.fm Page 137 Tuesday, July 1, 1997 7:32 AM
256 bytes of the segment at 1000:0000. Although there’s no PSP, that trick let us
use standard COM files without invoking a specialized linker. As long as our
program doesn’t expect anything in the PSP, having a blank (or random) one is OK.
The PC’s BIOS, on the other hand, knows nothing of this. When it finds our
Firmware Development Board extension, it passes control to the branch instruction
with !'# + set to C800:0003. Because our extension will set interrupt vectors as
well as change data, it must somehow adjust the segment registers on the fly.
The solution involves simple subtraction, because a given physical address can be
represented by many different segment and offset values. The CPU hardware shifts
the segment register left by four bits, adds the offset, and takes the result as the
physical address. At least that’s the case in real mode, which is all we require now;
protected mode programming is entirely different.
The easiest way to reload both !' and + is by yank them from the stack with a
) ()) instruction. Listing 2 shows the trick in all its glory… not
particularly impressive, hmmm? Just try to figure out what it does without some
commentary, though. Notice that the +*'1 instruction operates on the value of
&' ' as defined in the COM file, not ' ’s absolute offset
within segment C800.
COM programs generally assume that all four of the !', ', ', and '' registers
have the same value, but !' and '' hold the essential values for our code. I load '
and ' from the adjusted !' value. '' cannot point into the nonvolatile memory,
because that RAM has hardware write protection.
Fortunately, as long as we only +*'1, +&+, ! ??, and ) from the stack, whatever
values the BIOS put in '' and '+ will work fine. I have not investigated how deep
the default BIOS stack may be. Should your program need lots of stack space for
some peculiar reason, you should certainly create a stack somewhere else.
Although I didn’t find this written down anywhere, the BIOS in my system
requires that you restore at least ' and ' before the extension returns. As usual,
the final ) restores !' from the stack. I save and restore all the segment
registers, even though the requirements surely depend on which BIOS you’re using.
137
Embedded PCs ISA Bus.book : Chapter 8.fm Page 138 Tuesday, July 1, 1997 7:32 AM
FDBExt also marks a departure from the code you’ve seen so far: notice that I’m now
using the ' and (' segment registers that appear only in ’386 and higher CPUs.
As a result, this code will not run on 8088 or 80286 systems. I don’t include any
tests for the CPU type, as I assume we’re all responsible folks around here. Don’t try
this on your old clunker PC just to see what happens… it won’t work!
Yes, we could rewrite FDBExt to work on any 80x86 CPU, but it’s long past time to
start using hardware that’s been around since 1985. OK?
Capturing Interrupts
The remainder of FDBExt’s initialization code captures the BIOS timer and Non-
Maskable Interrupt vectors. You’ve seen similar routines in Chapters 5 and 7, so
refer back to those source files if you need a refresher.
Listing 3 shows the timer interrupt handler. The Firmware Development Board’s
RAM has its write protection hardware active, forcing each handler to enable
writes before updating its variables. The !' segment stored in the interrupt vector
equals the ' value set up in Listing 2, allowing the ! instruction to reach
8! using !' without saving, loading, using, and restoring '.
Because the RAM write enable bit shares the same port as the watchdog timer bit,
I made the &6) 0 and !) 0 routines toggle the watchdog on each BIOS
Listing 3
The BIOS extension captures the BIOS timer interrupt to count the ticks since the most
recent reset. The value of CS stored in the interrupt vector allows access to the
extension’s variables in nonvolatile memory. Because this code fragment was assembled
in ’386 mode, the INC instruction increments a 32-bit counter in one shot and the final
JMP uses the SMALL keyword to specify that OldTimer holds a seg:off address.
+)&! 81
+*'1 ;
+*'1 ;
! ?? &6) 0 2
+&+ ;
+&+ ;
+ 81
138
Embedded PCs ISA Bus.book : Chapter 8.fm Page 139 Tuesday, July 1, 1997 7:32 AM
timer tick. Measuring the bit’s active time shows that the interrupt handler requires
about 30 µs on a 33 MHz ’386.
Assuming it gets that far, of course! If something goes wrong during the disk boot
or program setup process, we’ve just defeated the entire purpose of the watchdog
timer. Think about it, then decide just how paranoid you should be. Which is
worse: a failure that you don’t detect or a system that doesn’t start up correctly?
Enabling ’386 assembly mode has some interesting side effects. Even though the
8! variable occupies four bytes, the assembler updates it with one 32-bit !
instruction. The /0+ at the end of the routine chains to the previous interrupt
handler as usual, but you must specify '0 ?? to indicate a 16-bit -# value,
rather than a 32-bit ? )( offset in the current segment.
Failing Power
Our BIOS extension responds to power failures by write protecting the RAM and
spinning in a safe shutdown loop. While writing this code, though, I uncovered a
nasty bug: NMI glitches. While they shouldn’t pose a problem in most systems, it’s
worth thinking about them if you’re using a power monitor.
The Firmware Development Board includes a trimpot that adjusts the voltage on
the MAX691’s PFI pin. The correct setting activates -PFO when the supply
voltage falls near the system’s lower tolerance limit; say -5% on a ±10% system. The
remaining 5% gives you enough time to shut the system down before the voltage
goes completely out of tolerance.
In small microcontroller systems, the MAX691 may be the only source of Non-
Maskable Interrupts. In our situation, though, many parts of a PC can contribute
to the NMI signal. Our handler must examine the board’s power failure status and
either chain to the previous NMI handler if it finds -PFO high (inactive) or shut
down the system when it sees a low -PFO input.
Here’s the problem: if the supply voltage falls slowly enough, a small supply glitch
that would normally be well within tolerance can trigger the power failure
139
Embedded PCs ISA Bus.book : Chapter 8.fm Page 140 Tuesday, July 1, 1997 7:32 AM
You can simulate this by very slowly adjusting the PFI trimpot. Tease it until the
system shuts down, then leave the trimpot unchanged. The system will probably
shut down sporadically every time you reboot it.
With none of the NMI sources active, the interrupt handler chain eventually passes
control to the default BIOS handler. Guess what? On my system, the default
handler disables further NMIs caused by the ISA -IOCHCK signal. Thus, when the
power really does fail after the glitch, the NMI handler never gets control.
If the MAX691 presents the only -IOCHCK interrupts in your system, your
interrupt handler can check the status bit in I/O port 61h to verify that the NMI
actually came from the bus. Because the system board latches that status bit when
-IOCHCK goes active, the status will not go away even when the glitch vanishes.
However, if you have several I/O boards that can produce -IOCHCK interrupts, the
situation becomes somewhat messier. Your handler must test the board’s status and
chain to other handlers if it finds no problem. An NMI glitch from your hardware
will set the -IOCHCK latch before vanishing. Your handler will find nothing wrong
with the PC’s power, then incorrectly pass control to other handlers that will also
find nothing wrong on their boards.
You can also add a digital latch that preserves the -PFO glitch, much as the system
board latches -IOCHCK. Remember to include hardware that clears the latch on
each hardware reset to prevent a hot NMI.
140
Embedded PCs ISA Bus.book : Chapter 8.fm Page 141 Tuesday, July 1, 1997 7:32 AM
In any event, one of the conditional assembly options shown in Listing 4 inserts a
timing loop that starts on the first NMI. It displays the loop count on the LEDs
until the next NMI, at which time it locks up the system. You can use ExtTest.BIN to
see how this problem looks on your system.
The CPU automatically disables all interrupts within the NMI handler, preventing
watchdog timer updates during the final lockup loop. The MAX691 times out and
resets the system shortly after the second NMI occurs.
Listing 4
This NMI handler normally shuts down the system in response to a power failure. If the
supply voltage falls very slowly or if you tease the trimpot controlling PFI, you can get a
glitch on NMI that vanishes by the time this routine gets control. The code shown here
includes an optional section that displays the elapsed time from the first NMI to the next,
then locks up the system. If these glitches pose a problem in your system, the code can
also lock up in response to any NMI caused by the ISA -IOCHCK input.
+)&! 0 1
+*'1 ;
+*'1 ;
! ?? &6) 0 2
! ?? !) 0 2
EE9#
0&: ;? < ) 26
0&: ;!;
& ;
&* ; ;
*'< &!1!F
?'
141
Embedded PCs ISA Bus.book : Chapter 8.fm Page 142 Tuesday, July 1, 1997 7:32 AM
! !;
/0+ EE9
?'
*'< &!1!F
?'
+&+ ;
+&+ ;
EE?86#
0&: ;"" 56
! ?? '2
+ 0 1
142
Embedded PCs ISA Bus.book : Chapter 8.fm Page 143 Tuesday, July 1, 1997 7:32 AM
However, the BIOS normally clears the system RAM and runs power-on
diagnostics immediately after a hardware reset, which was not quite what they
wanted. The engineers reserved a byte at address 0F in the Real-Time Clock’s
battery backed CMOS RAM to indicate the reason for the shutdown.
Before the BIOS gets too far along in its reset sequence, it asks the keyboard
controller what caused the reset to occur. If the controller reports that it executed a
) command, as opposed to participating in a power-on or manual reset, the
BIOS reads the shutdown reason code from the clock’s RAM. If that byte indicates
a transition from protected to real mode, the BIOS branches directly back to the
mode switch routine.
The only reason you think it’s a kludge is that you didn’t design it. It’s really a clean,
general, and useful way around an otherwise insurmountable hardware limitation.
Remember the Consulting Engineer’s First Principle: you don’t get paid if the
system doesn’t work.
Intel got the message loud and clear. Starting with the 80386, all their CPUs enter
and exit protected mode at the flip of a bit (well, all right, you need a little setup
and takedown code on either side of the bit-flipping instruction, too). By that time,
however, a considerable body of software used the Officially Approved ’286
method. You can even buy hyperthyroid keyboard controllers with special fast-path
hardware logic to recognize and speed up the ) command. I kid you not.
The shutdown reason code in the clock’s RAM can select one of several different
routines after a reset. Most are ill-suited for civilian use, but one may come in
handy in certain desperate situations. I’ll show how to use it and you figure out
when the trick might be appropriate. Fair enough?
If the shutdown reason code is 0A, the BIOS vectors through the pointer stored at
address 0040:0067 to the restart code. That code is normally within the BIOS, but
you can redirect the BIOS to your own routine. Because the DRAM refresh
hardware continues to run during the shutdown, you regain control immediately
after a hardware reset with nearly everything intact. Of course, all the CPU
registers except !'# + lose their values, so you must consider a few, ah, minor
details that I’ll leave as an exercise.
143
Embedded PCs ISA Bus.book : Chapter 8.fm Page 144 Tuesday, July 1, 1997 7:32 AM
Listing 5a
This code in FDBExt.ASM gains control after the keyboard controller blips the CPU’s
Reset line. The BIOS checks the shutdown reason code at address 0F in the Real-Time
Clock’s CMOS RAM; if that value is 0A it vectors through the address stored at
0040:0067, which ExtTest aims at this routine.
+)&! '61
! ?? &6) 0 2
! B!'#'6!C 6
! ?? !) 0 2
+ '61
Listing 5b shows the code from ExtTest that transfers the vector from our
nonvolatile RAM to address 0040:0067 and sets the shutdown reason code in the
clock’s RAM. Later, in response to a keyboard command, ExtTest executes this
instruction to reset the system using the keyboard controller’s reset command:
The BIOS then executes the code in Listing 5a, goes through a second reset with all
the normal power-on tests, and reloads ExtTest from diskette. That’s all there is to
it. Easy, once you know the secret, isn’t it?
Now, if anybody asks you about the Worst Hack in PC-dom, you can say you’ve
been there and done that. Tell me if you put it to good use and what you did with
the honorary T shirt.
144
Embedded PCs ISA Bus.book : Chapter 8.fm Page 145 Tuesday, July 1, 1997 7:32 AM
Listing 5b
This code from ExtTest.C loads the vector and sets the shutdown reason code into the
Real-Time Clock’s CMOS RAM. Note that the BIOS restart vector cannot be not in the
interrupt table because neither a hardware nor software interrupt invokes it.
Release Notes
The files for this chapter include the modified LoadExt boot sector loader that
stuffs a BIOS extension into the FDB’s nonvolatile storage. You also get FDBExt
and ExtTest to show you how the whole process works. The ReadMe.txt file and
comments in the source code explain how to load and run the code.
You may find that the interactions between the NMI hardware, the BIOS power-on
resets, and the BIOS extensions on your target system don’t behave quite as I’ve
described. If the sample programs deliver strange results, add some trace outputs
and monitor the code’s progress through the various stages. A few LEDs that go
ON in the right order (or don’t go ON at all!) can quickly reveal how your system
works.
As always, patience and careful sleuthing will give you far more understanding than
applying an In-Circuit Emulator…
145
Embedded PCs ISA Bus.book : Chapter 8.fm Page 146 Tuesday, July 1, 1997 7:32 AM