0% found this document useful (0 votes)
20 views14 pages

Chapter 8

Chapter 8 discusses the implementation of BIOS extensions for embedded systems, focusing on routines that capture interrupts and support power failure detection. It details the necessary hardware modifications and provides code snippets for setting up BIOS extensions, emphasizing the importance of handling potential crashes during boot. The chapter also explains the memory layout and segment register adjustments required for proper execution of COM files in this context.

Uploaded by

foof faaf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views14 pages

Chapter 8

Chapter 8 discusses the implementation of BIOS extensions for embedded systems, focusing on routines that capture interrupts and support power failure detection. It details the necessary hardware modifications and provides code snippets for setting up BIOS extensions, emphasizing the importance of handling potential crashes during boot. The chapter also explains the memory layout and segment register adjustments required for proper execution of COM files in this context.

Uploaded by

foof faaf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

Embedded PCs ISA Bus.book : Chapter 8.

fm Page 133 Tuesday, July 1, 1997 7:32 AM

8 Ticks, Pops, and Restarts


Traditional embedded systems start up at the flip of a switch. Even the PC’s built-
in ROM BIOS lights up immediately. However, as we saw in the previous chapter,
loading code from diskette can take quite a while and may conflict with your
watchdog timer. There’s no quick-and-dirty cure for that, but converting your code
into a BIOS extension can help.

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.

The Key to the Code


Schematic 1 shows the new hardware you’ll need: a pushbutton switch with a
pullup resistor driving bit 9 of the input port at 031C. This may be barely worth
warming your soldering iron, but, every now and then, we need an easy one.

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.

So much for the hardware. Now, on to the code!

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

The Embedded PC’s ISA Bus

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 part of FDBExt.ASM shown in Listing 1 sets up the 55 AA signature, length,


and checksum bytes required by the BIOS extension scan. Recall that the checksum
byte in the source code must be zero, because our diskette boot loader computes the
actual checksum as it copies the extension into the Firmware Development Board.

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…”

The Case of the Missing PSP


  , written in Borland’s Turbo Assembler  memory model, produces an
ordinary COM file. As far as TASM and TLINK can tell, the program will run under
DOS with all the usual DOS assumptions and restrictions. Of course, if we actually
do run it under DOS, it won’t work very well at all.

134
Embedded PCs ISA Bus.book : Chapter 8.fm Page 135 Tuesday, July 1, 1997 7:32 AM

Chapter 8: Ticks, Pops, and Restarts

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.

 
  !"#$%%%

!& '(
' ) *+!& 
 ,, -
  
 . -,.

/0+ '1&)  23456

  7

33352855-

)7! 9  77

33362 2-5

#
0&: ;' < )
; ;
' ;+*'1* &
/ = '1&) ! >56

0&: ; &  23386


0&: ;? < )
&* ; ;

)  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

The Embedded PC’s ISA Bus

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-3


0&: ;? < )
&* ; ;
0&: ;!'
'* ; -5
+*'1 ;
+*'1 &' '
)  !' +27

'#
0&: ;,@ 2? 58
! ?? '2

+*'1 ' 67--


+*'1 '
+*'1 '
+*'1 ('

0&: ;!' 6 '5!'


0&: ' ;
0&: ' ;

0&: ; '6#


0&: ' ;

0&: ;A ('6A#


0&: (' ;

! ?? &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

Chapter 8: Ticks, Pops, and Restarts

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.

In our situation, the branch instruction at C800:0003 resides at physical address


C8003. We can also call that address C7F0:0103, because C7F00 + 00103 =
C8003. Thus, if we reload the segment registers with C7F0, rather than C800, and
change + from 0003 to 0103, all our offsets become correct and we can use COM
files for BIOS extensions.

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

The Embedded PC’s ISA Bus

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

! B!'# 8!C  8!$.2


! ?? !) 0 2

+&+ ;
+&+ ;

/0+ '0 ??B!'#& 5% 9&) C

 + 81

138
Embedded PCs ISA Bus.book : Chapter 8.fm Page 139 Tuesday, July 1, 1997 7:32 AM

Chapter 8: Ticks, Pops, and Restarts

timer tick. Measuring the bit’s active time shows that the interrupt handler requires
about 30 µs on a 33 MHz ’386.

As I mentioned in Chapter 7, it’s generally a Bad Idea to toggle a watchdog from a


timer interrupt, because the main routine can crash while the timer tick continues
running. However, this trick can keep the watchdog at bay while loading a big
program from diskette. The mainline code can always capture the timer tick and
implement my favored method after it starts running.

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.

I like that sound… even in real mode!

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

The Embedded PC’s ISA Bus

comparator and generate a Non-Maskable Interrupt. By the time the CPU


responds to the NMI and checks the -PFO status bit, however, the glitch has long
since Gone Away, even though the voltage continues to fall.

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.

It should be obvious that this a situation that’s best avoided.

In Schematic 2 in the previous chapter, C4, a 1 nF capacitor on the PFI trimpot’s


wiper, filters the glitches with a 10 µs time constant that made the trimpot
teaseproof. You should, of course, evaluate this trick in your system to verify that it
does not delay the interrupt too much during a real power failure.

An alternative approach, described in the MAX691A data sheet, applies hysteresis


around the power failure comparator. Because -PFO switches low as PFI drops, a
high value resistor between those two pins will yank PFI down and prevent the end
of the glitch from restoring -PFO. Assuming, of course, that the comparator’s
propagation time doesn’t glitch it the other way!

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

Chapter 8: Ticks, Pops, and Restarts

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

! B!'# 0 !C  0

! ?? !) 0 2

 !&* < 0 2D

0&: !; 6

EE9#
0&: ;? < ) 26
0&: ;!;
& ;
&* ; ;

 *'< &!1!F

?''<! ?' 8 &!1!F-


' ? &!1!F
EEF6# / = '1&) EEF6 862--

?'

0&: ;' < ) 862


; ;
' ;+9)<(&&
EEF6# /= '1&) EEF6 862-2

 

Listing continues on next page

141
Embedded PCs ISA Bus.book : Chapter 8.fm Page 142 Tuesday, July 1, 1997 7:32 AM

The Embedded PC’s ISA Bus

Listing continued from previous page

! !;
/0+ EE9

?'

0&: ;, 2G5H


0&: ;? < )
& ;
&* ; ;

 *'< &!1!F

?''<! ?' 8 &!1!F-


' ? &!1!F
/ = '1&) EE?86 862--

?'

0&: ;' < ) 862


; ;
' ;+9)<(&&
/= '1&) EE?86 >I62 & -%%%

 

 

+&+ ;
+&+ ;

/0+ '0 ??B!'#& 0 % 9&) C

EE?86#
0&: ;"" 56
! ?? '2

EE'# /0+ EE'

 + 0 1

Resets and the Worst Hack


The Original IBM PC AT’s design engineers faced a serious problem. They needed
a way to get their shiny new 80286 CPU back into real mode, even though the chip
had no way to shut off its Protected Mode Enable bit. The 80826 emerged from
hardware reset in real mode, but once the program entered protected mode, the
80286 architecture provided no way back. Their solution stands as a monument to
engineering ingenuity.

The AT included an 8042 microcontroller managing a variety of tasks implemented


with discrete logic in the Original PC. The designers simply added a command to
the 8042’s repertoire that toggled the 80286 CPU’s Reset line.

142
Embedded PCs ISA Bus.book : Chapter 8.fm Page 143 Tuesday, July 1, 1997 7:32 AM

Chapter 8: Ticks, Pops, and Restarts

Blam… back to real mode!

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.

Listing 5a shows FDBExt’s rudimentary restart routine, which simply increments a


counter and sends a second ) command to the keyboard controller. The BIOS
clears the shutdown reason code before branching to the routine and the hardware
treats the second reset as a complete, normal, power-on reset.

143
Embedded PCs ISA Bus.book : Chapter 8.fm Page 144 Tuesday, July 1, 1997 7:32 AM

The Embedded PC’s ISA Bus

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

0&: ;," 2%? 


! ?? '2

0&: ? 8


&* F<!0  ? %%%6
EE'# /0+ EE'

 + '61

Although our restart handler lives in the Firmware Development Board’s


nonvolatile RAM, because it must exist whenever the CPU uses it, FDBExt cannot
write its address into the vector at 0040:0067. At least on my systems here, that
restart vector changes after FDBExt exits, although the shutdown reason code does
not. That means FDBExt must put the address in a spot that ExtTest knows about,
but somewhere that the BIOS won’t wipe out during its startup processing.

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:

6G JA H

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

Chapter 8: Ticks, Pops, and Restarts

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.

6GK!--675LA #LA LA #LA %%%MK


682G A JNH682G A J@H
682G; '(' ) *+<'(H682G; '(' ) *+<&HH
682G A J@682G; '(' ) *+<&HH
682G A JN682G; '(' ) *+<'(HH
6GK-2%%%MKH
GH
6G) !< ) H OP52 PO
6G) !<   H OP%%%7-A#J@ PO
GH
6GKMKH

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

You might also like