Home of the original IBM PC emulator for browsers.
The following document is from the Microsoft Programmer’s Library 1.3 CD-ROM.
The New Peter Norton Programmer's Guide to the IBM(R) PC and PS/2(R)
════════════════════════════════════════════════════════════════════════════
The New Peter Norton Programmer's Guide to the IBM(R) PC and PS/2(R)
By Peter Norton and Richard Wilton
════════════════════════════════════════════════════════════════════════════
PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717
First edition copyright (C) 1985 by Peter Norton
Second edition revisions copyright (C) 1988 by Microsoft Press
All rights reserved. No part of the contents of this book may be reproduced
or transmitted in any form or by any means without the written permission of
the publisher.
Library of Congress Cataloging in Publication Data
Norton, Peter, 1943- .
The new Peter Norton programmer's guide to the IBM PC and PS/2 :
the ultimate reference guide to the entire family of IBM Personal
Computers / Peter Norton and Richard Wilton.
p. cm.
Includes index.
1. IBM microcomputers--Programming. 2. IBM Personal Computer.
3. IBM Personal System/2 (Computer system) I. Wilton, Richard, 1953- .
II. Title. III. Title: Programmer's guide to the IBM Personal Computers.
QA76.8.I1015N67 1988 88-21104
005.265--dc19 CIP
ISBN 1-55615-131-4
Printed and bound in the United States of America.
1 2 3 4 5 6 7 8 9 FGFG 3 2 1 0 9 8
Distributed to the book trade in the United States by Harper & Row.
Distributed to the book trade in Canada by General Publishing Company, Ltd.
Distributed to the book trade outside the United States and Canada by
Penguin Books Ltd.
Penguin Books Ltd., Harmondsworth, Middlesex, England
Penguin Books Australia Ltd., Ringwood, Victoria, Australia
Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand
British Cataloging in Publication Data available
Microsoft(R), Flight Simulator(R), and GW-BASIC(R) are registered trademarks
of Microsoft Corporation.
IBM(R), PC/AT(R), Personal System/2(R), and PS/2(R) are registered
trademarks, and Micro Channel(TM), PCjr(TM), and PC/XT(TM) are trademarks of
International Business Machines Corporation.
Norton Utilities(TM) is a trademark of Peter Norton.
────────────────────────────────────────────────────────────────────────────
Project Editor: Megan E. Sheppard
Technical Editors: Bob Combs and Jim Johnson
────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────
Contents
Introduction
1 Anatomy of the PCs and PS/2s
2 The Ins and Outs
3 The ROM Software
4 Video Basics
5 Disk Basics
6 Keyboard Basics
7 Clocks, Timers, and Sound Generation
8 ROM BIOS Basics
9 ROM BIOS Video Services
10 ROM BIOS Disk Services
11 ROM BIOS Keyboard Services
12 Miscellaneous Services
13 ROM BIOS Services Summary
14 DOS Basics
15 DOS Interrupts
16 DOS Functions: Version 1
17 DOS Functions: Versions 2.0 and Later
18 DOS Functions Summary
19 Program Building
20 Programming Languages
Appendix A: Installable Device Drivers
Appendix B: Hexadecimal Arithmetic
Appendix C: About Characters
Appendix D: DOS Version 4
Index
────────────────────────────────────────────────────────────────────────────
Introduction
The world of personal computers has come a long way in the few years since
the original edition of this book appeared, yet the goal of this book
remains a simple but ambitious one: to help you master the principles of
programming the IBM personal computer family. From the time that the first
IBM Personal Computer (known simply as "the PC") was introduced in the
fall of 1981, it was clear that it was going to be a very important
computer. Later, as PC sales zoomed beyond the expectations of everyone
(IBM included) and as the original model was joined by a sibling or two,
the PC became recognized as the standard for serious desktop computers.
From the original PC, a whole family of computers──a family with many
branches──has evolved. And at the same time the importance of the PC
family has also grown.
The success and significance of the PC family has made the development of
programs for it very important. However, the fact that each member of the
family differs in details and characteristics from its relatives has also
made the development of programs for the family increasingly complex.
This book is about the knowledge, skills, and concepts that are needed to
create programs for the PC family──not only for one member of the family
(though you might perhaps cater to the peculiarities and quirks of one
member) but for the family as a whole──in a way that is universal enough
that your programs should work not only on all the present family members,
but on future members as well.
This book is for anyone involved in the development of programs for the PC
family. It is for programmers, but not only for programmers. It is for
anyone who is involved in or needs to understand the technical details and
working ideas that are the basis for PC program development, including
anyone who manages programmers, anyone who plans or designs PC programs,
and anyone who uses PC programs and wants to understand the details behind
them.
Some Comments on Philosophy
One of the most important elements of this book is the discussion of
programming philosophy. You will find throughout this book explanations of
the ideas underlying IBM's design of the PC family and of the principles
of sound PC programming, viewed from experience.
If this book were to provide you with only facts──tabulations of technical
information──it would not serve you well. That's why we've interwoven with
the technical discussion an explanation of what the PC family is all
about, of the principles that tie the various family members together, and
of the techniques and methods that can help you produce programs that will
endure and prosper along with the PC family.
How to Use This Book
This book is both a reading book and a reference book, and you can
approach it in at least two ways. You may want to read it as you would any
other book, from front to back, digging in where the discussion is useful
to you and quickly glancing through the material you don't yet need. This
approach provides a grand overview of the workings (and the ideas behind
the workings) of PC programs. You can also use this book as a pure
reference, and dip into specific chapters for specific information. We've
provided a detailed table of contents at the beginning of each chapter and
an extensive index to help you find what you need.
When you use this book as a random-access reference to the details of PC
programming, you'll find that much of the material is intricately
interrelated. To help you understand the interrelationships, we have
repeated some details when it was practical to do so and have referred you
to other sections when such repetition was less practical.
What's New in This Edition
As you might guess, this edition of the Programmer's Guide has been
brought up to date for the new generation of IBM personal computers: the
Personal System/2 computers, or PS/2s.
In some ways this book is more complex and more detailed than the
original. There's a good reason for this: The newer members of the PC and
PS/2 family are more complicated computers, and the later versions of DOS
are more complicated and have more features than their predecessors. It
was inevitable that this revised version of the Programmer's Guide would
reflect this greater complexity in the hardware, the ROM BIOS, and DOS.
Still, you'll find that a few members of the extended PC family aren't
covered in this book. The PCjr, the XT/286, and the PC Convertible are
used relatively infrequently, and the PS/2 Model 70 was released too
recently to be included. Nevertheless, each of these machines is similar
to one of the PCs or PS/2s whose innards we will examine in detail, so
this book should be a useful guide even if you are programming a Model 70
or one of the less widely used PCs.
Here are some of the changes you'll find in this new edition:
New video subsystems. Since the original edition appeared, IBM's Enhanced
Graphics Adapter (EGA) became a de facto hardware standard for PC
programmers and users. Then the PS/2s introduced two new video subsystems,
the Multi-Color Graphics Array (MCGA) and the Video Graphics Array (VGA).
These new video subsystems receive extensive coverage in Chapters 4 and
9.
New keyboards. IBM supports a new, extended keyboard with later versions
of the PC/AT and with all PS/2s. Chapters 6 and 11 have been expanded to
cover the new hardware.
A new focus on C programming. For better or worse, the most recent
versions of DOS have been strongly influenced by the C programming
language. This influence is even more apparent in such operating
environments as Microsoft Windows, UNIX, and OS/2──all of which were
designed by C programmers. For this reason you'll find new examples of C
programming in several different chapters. Of course, we haven't abandoned
Pascal and BASIC──in fact, Chapter 20 examines each of these programming
languages.
A new perspective on DOS. DOS has evolved into a mature operating system
whose design can now be viewed with the clarity of hindsight. The past
several years of working with DOS have helped us view this immensely
popular operating system with a practical perspective born of experience.
Our discussions of DOS emphasize which of its features are obsolescent and
which are pointers to the future.
Despite these changes, the direction and philosophy of this book remain
the same. When you write a program for a PC or PS/2, you can actually
program for an entire family of computers. Each member of the family──the
PC, the PC/XT, the PC/AT, and all PS/2s──has hardware and software
components that are identical or similar to those in other members of the
family. When you keep this big picture in mind, you'll be able to write
programs that take advantage of the capabilities of the different PC and
PS/2 models without sacrificing portability.
Other Resources
One book, of course, can't provide you with all the knowledge that you
might possibly need. We've made this book as rich and complete as we
reasonably can, but there will always be a need for other kinds of
information. Here are some of the places you might look for material to
supplement what you find here.
For detailed technical information about the PC family, the ultimate
source is IBM's series of technical reference manuals. Specific technical
reference manuals exist for the original PC, for the XT, for the AT, and
for PS/2 models 30, 50, 60, and 80. In addition, the detailed IBM BIOS
Interface Technical Reference Manual summarizes the capabilities of the
Basic Input/Output System in all members of the extended PC family. You
should know a few things about using these model-specific manuals:
■ Information specific to one model is not differentiated from general
information for the whole PC family. To be sure of the differences, you
should use common sense, compare the different manuals, and consult
this book.
■ Remember that each new model in the PC family adds new features. If you
turn to the manual for a later model, you will find information on a
wide variety of features; if you turn to the manual for an earlier
model, you'll avoid being distracted by features that do not apply to
all models in the family.
There is also an IBM Options and Adapters Technical Reference Manual for
the various options and adapters used by the PC family, such as different
disk drives or display screens. Technical information about this kind of
equipment is gathered into this manual, which is updated periodically.
(The updates are available by subscription.) Little of the information in
this technical reference manual is of use to programmers, but you might
find some parts of interest.
IBM also publishes technical reference manuals for special extensions to
the PC, such as PC Network.
Perhaps the most important of the IBM technical reference manuals is the
series for DOS. These manuals contain a wealth of detailed technical
information which we have summarized in this book.
A number of other sources can provide information to supplement the IBM
manuals:
■ For a somewhat broader perspective on the IBM Personal Computer──one
that is not focused on programming──see Peter Norton's Inside the IBM
Personal Computer, published by Robert J. Brady Company.
■ For a broader perspective on DOS, see the third edition of Van
Wolverton's Running MS-DOS, and The MS-DOS Encyclopedia, both published
by Microsoft Press.
Because this book covers the subject of PC programming in a broad fashion,
it can provide you with only a few key details about individual
programming languages. For details on particular programming languages and
the many specific compilers for those languages, you will need more books
than we could begin to list or recommend.
With these introductory remarks completed, it's time to plunge into the
task of mastering the principles of programming the PC family!
────────────────────────────────────────────────────────────────────────────
Chapter 1 Anatomy of the PCs and PS/2s
The Microprocessor
The 8088 Microprocessor
The 8086 Microprocessor
The 80286 Microprocessor
The 80386 Microprocessor
The Math Coprocessor
The Support Chips
The Programmable Interrupt Controller
The DMA Controller
The Clock Generator
The Programmable Interval Timer
Video Controllers
Input/Output Controllers
Linking the Parts: The Bus
The Address Bus
The Data Bus
Micro Channel Architecture
Memory
CPU Address Space
The System Memory Map
Design Philosophy
From the programmer's point of view, all members of the PC family consist
of a processor, memory chips, and several smart, or programmable, circuit
chips. All the main circuit components that make the computer work are
located on the system board; other important parts are located on
expansion boards, which can be plugged into the system board.
The system board (Figures 1-1 through 1-3) contains the microprocessor,
which is tied to at least 64 KB of memory; some built-in ROM programs,
such as BASIC and the ROM BIOS; and several very important support chips.
Some of these chips control external devices, such as the disk drive or
the display screen, and others help the microprocessor perform its tasks.
In this section, we discuss each major chip and give a few important
technical specifications. These chips are frequently known by more than
one name. For example, some peripheral input/output hardware is supervised
by a chip known as the 8255. This chip is also referred to as the 8255A
and the 8255A-5. The suffixes A and 5 refer to revision numbers and to
parts rated for operation at different speeds. For programming purposes,
any Intel chip part number that starts with 8255 is identical to any other
chip whose part number starts with 8255, regardless of the suffix.
However, when you replace one of these chips on a circuit board, note the
suffix. If the suffixes are different, the part may not operate at the
proper speed.
The Microprocessor
In all PCs, the microprocessor is the chip that runs programs. The
microprocessor, or central processing unit (CPU), carries out a variety of
computations, numeric comparisons, and data transfers in response to
programs stored in memory.
The CPU controls the computer's basic operation by sending and receiving
control signals, memory addresses, and data from one part of the computer
to another along a group of interconnecting electronic pathways called a
bus. Located along the bus are input and output (I/O) ports that connect
the various memory and support chips to the bus. Data passes through these
I/O ports while it travels to and from the CPU and the other parts of the
computer.
In the IBM PCs and PS/2s, the CPU always belongs to the Intel 8086 family
of microprocessors. (See Figure 1-4.) We'll point out the similarities
and differences between the different microprocessors as we describe them.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 1-1 can be found on p.3 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 1-1. The IBM PC system board.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 1-2 can be found on p.4 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 1-2. The PC/AT system board.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 1-3 can be found on p.5 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 1-3. The PS/2 Model 60 system board.
Model Microprocessor
──────────────────────────────────────────────────────────────────────────
PC 8088
PC/XT 8088
PC/AT 80286
PS/2 Models 25, 30 8086
PS/2 Models 50, 60 80286
PS/2 Model 80 80386
──────────────────────────────────────────────────────────────────────────
Figure 1-4. Microprocessors used in IBM PCs and PS/2s.
The 8088 Microprocessor
The 8088 is the 16-bit microprocessor that controls the standard IBM
personal computers, including the original PC, the PC/XT, the Portable PC,
and the PCjr. Almost every bit of data that enters or leaves the computer
passes through the CPU to be processed.
Inside the 8088, 14 registers provide a working area for data transfer and
processing. These internal registers, forming an area 28 bytes in size,
are able to temporarily store data, memory addresses, instruction
pointers, and status and control flags. Through these registers, the 8088
can access 1 MB (megabyte), or more than one million bytes, of memory.
The 8086 Microprocessor
The 8086 is used in the PS/2 models 25 and 30 (and also in many IBM PC
clones). The 8086 differs from the 8088 in only one minor respect: It uses
a full 16-bit data bus instead of the 8-bit bus that the 8088 uses. (The
difference between 8-bit and 16-bit buses is discussed on page 12.)
Virtually anything that you read about the 8086 also applies to the 8088;
for programming purposes, consider them identical.
The 80286 Microprocessor
The 80286 is used in the PC/AT and in the PS/2 models 50 and 60. Although
fully compatible with the 8086, the 80286 supports extra programming
features that let it execute programs much more quickly than the 8086.
Perhaps the most important enhancement to the 80286 is its support for
multitasking.
Multitasking is the ability of a CPU to perform several tasks at a time──
such as printing a document and calculating a spreadsheet──by quickly
switching its attention among the controlling programs.
The 8088 used in a PC or PC/XT can support multitasking with the help of
sophisticated control software. However, an 80286 can do a much better job
of multitasking because it executes programs more quickly and addresses
much more memory than the 8088. Moreover, the 80286 was designed to
prevent tasks from interfering with each other.
The 80286 can run in either of two operating modes: real mode or protected
mode. In real mode, the 80286 is programmed exactly like an 8086. It can
access the same 1 MB range of memory addresses as the 8086. In protected
mode, however, the 80286 reserves a predetermined amount of memory for an
executing program, preventing that memory from being used by any other
program. This means that several programs can execute concurrently without
the risk of one program accidentally changing the contents of another
program's memory area. An operating system using 80286 protected mode can
allocate memory among several different tasks much more effectively than
can an 8086-based operating system.
The 80386 Microprocessor
The PS/2 Model 80 uses the 80386, a faster, more powerful microprocessor
than the 80286. The 80386 supports the same basic functions as the 8086
and offers the same protected-mode memory management as the 80286.
However, the 80386 offers two important advantages over its predecessors:
■ The 80386 is a 32-bit microprocessor with 32-bit registers. It can
perform computations and address memory 32 bits at a time instead of 16
bits at a time.
■ The 80386 offers more flexible memory management than the 80286 and
8086.
We'll say more about the 80386 in Chapter 2.
The Math Coprocessor
The 8086, 80286, and 80386 can work only with integers. To perform
floating-point computations on an 8086-family microprocessor, you must
represent floating-point values in memory and manipulate them using only
integer operations. During compilation, the language translator represents
each floating-point computation as a long, slow series of integer
operations. Thus, "number-crunching" programs can run very slowly──a
problem if you have a large number of calculations to perform.
A good solution to this problem is to use a separate math coprocessor that
performs floating-point calculations. Each of the 8086-family
microprocessors has an accompanying math coprocessor: The 8087 math
coprocessor is used with an 8086 or 8088; the 80287 math coprocessor is
used with an 80286; and the 80387 math coprocessor is used with an 80386.
(See Figure 1-5.) Each PC and PS/2 is built with an empty socket on its
motherboard into which you can plug a math coprocessor chip.
From a programmer's point of view, the 8087, 80287, and 80387 math
coprocessors are fundamentally the same: They all perform arithmetic with
a higher degree of precision and with much greater speed than is usually
achieved with integer software emulation. In particular, programs that use
math coprocessors to perform trigonometric and logarithmic operations can
run up to 10 times faster than their counterparts that use integer
emulation.
Programming these math coprocessors in assembly language can be an
exacting process. Most programmers rely on high-level language translators
or commercial subroutine libraries when they write programs to run with
the math coprocessors. The techniques of programming the math coprocessors
directly are too specialized to cover in this book.
╓┌─┌────────────────────┌────────────────────┌────────────────────┌─────────┌►
Approximate Range Si
Data Type (from) (to) Bits (d
───────────────────────────────────────────────────────────────────────────
Word integer -32,768 +32,767 16
Short integer -2 x 10E9 +2 x 10E9 32
Long integer -9 x 10E18 +9 x 10E18 64
Packed decimal -99...99 +99...99 80
Short real 8.43 x 10E-37 3.37 x 10E38 32
Long real 4.19 x 10E-307 1.67 x 10E308 64 15
Temporary real 3.4 x 10E-4932 1.2 x 10E4932 80
───────────────────────────────────────────────────────────────────────────
Figure 1-5. The range of numeric data types supported by the 8087,
80287, and 80387 math coprocessors.
The Support Chips
The microprocessor cannot control the entire computer without some
help──nor should it. By delegating certain control functions to other
chips, the CPU is free to attend to its own work. These support chips
can be responsible for such processes as controlling the flow of
information throughout the internal circuitry (as the interrupt
controller and the DMA controller are) and controlling the flow of
information to or from a particular device (such as a video display or
disk drive) attached to the computer. These so-called device controllers
are often mounted on a separate board that plugs into one of the PC's
expansion slots.
Many support chips in the PCs and PS/2s are programmable, which means
they can be manipulated to perform specialized tasks. Although direct
programming of these chips is generally not a good idea, the following
descriptions will point out which chips are safe to program directly and
which aren't. Because this book does not cover direct hardware control,
you should look in the IBM technical manuals as well as in the chip
manufacturers' technical literature for details about programming
individual chips.
The Programmable Interrupt Controller
In a PC or PS/2, one of the CPU's essential tasks is to respond to
hardware interrupts. A hardware interrupt is a signal generated by a
component of the computer, indicating that component's need for CPU
attention. For example, the system timer, the keyboard, and the disk
drive controllers all generate hardware interrupts at various times. The
CPU responds to each interrupt by carrying out an appropriate
hardware-specific activity, such as incrementing a time-of-day counter
or processing a keystroke.
Each PC and PS/2 has a programmable interrupt controller (PIC) circuit
that monitors interrupts and presents them one at a time to the CPU. The
CPU responds to these interrupts by executing a special software routine
called an interrupt handler. Because each hardware interrupt has its own
interrupt handler in the ROM BIOS or in DOS, the CPU can recognize and
respond specifically to the hardware that generates each interrupt. In
the PC, PC/XT, and PS/2 models 25 and 30, the PIC can handle 8 different
hardware interrupts. In the PC/AT and PS/2 models 50, 60, and 80, two
PICs are chained together to allow a total of 15 different hardware
interrupts to be processed.
Although the programmable interrupt controller is indeed programmable,
hardware interrupt management is not a concern in most programs. The ROM
BIOS and DOS provide nearly all of the services you'll need for managing
hardware interrupts. If you do plan to work directly with the PIC, we
suggest you examine the ROM BIOS listings in the IBM technical reference
manuals for samples of actual PIC programming.
The DMA Controller
Some parts of the computer are able to transfer data to and from the
computer's memory without passing through the CPU. This operation is
called direct memory access, or DMA, and it is handled by a chip known
as the DMA controller. The main purpose of the DMA controller is to
allow disk drives to read or write data without involving the
microprocessor. Because disk I/O is relatively slow compared to CPU
speeds, DMA speeds up the computer's overall performance quite a bit.
The Clock Generator
The clock generator supplies the multiphase clock signals that
coordinate the microprocessor and the peripherals. The clock generator
produces a high-frequency oscillating signal. For example, in the
original IBM PC, this frequency was 14.31818 megahertz (MHz, or million
cycles per second); in the newer machines, the frequency is higher.
Other chips that require a regular timing signal obtain it from the
system clock generator by dividing the base frequency by a constant to
obtain the frequency they need to accomplish their tasks. For example,
the IBM PC's 8088 is driven at 4.77 MHz, one-third of the base
frequency. The PC's internal bus and the programmable interval timer
(discussed shortly) use a frequency of 1.193 MHz, running at a quarter
of the 8088 rate and one-twelfth of the base rate.
The Programmable Interval Timer
The programmable interval timer generates timing signals at regular
intervals controlled by software. The chip can generate timing signals
on three different channels at once (four channels in the PS/2 models
50, 60, and 80).
The timer's signals are used for various system tasks. One essential
timer function is to generate a clock-tick signal that keeps track of
the current time of day. Another of the timer's output signals can be
used to control the frequency of tones produced with the computer's
speaker. See Chapter 7 for more information about programming the
system timer.
Video Controllers
The many video subsystems available with the PCs and PS/2s present a
variety of programmable control interfaces to the video hardware. For
example, all PC and PS/2 video subsystems have a cathode ray tube (CRT)
controller circuit to coordinate the timing signals that control the
video display.
Although the video control circuits can be programmed in application
software, all video subsystems have different programming interfaces.
Fortunately, all PCs and PS/2s are equipped with basic video control
routines in the ROM BIOS. We'll describe these routines in Chapter 9.
Input/Output Controllers
PCs and PS/2s have several input/output subsystems with specialized
control circuitry that provides an interface between the CPU and the
actual I/O hardware. For example, the keyboard has a dedicated
controller chip that transforms the electrical signals generated by
keystrokes into 8-bit codes that represent the individual keys. All disk
drives have separate controller circuitry that directly controls the
drive; the CPU communicates with the controller through a consistent
interface. The serial and parallel communications ports also have
dedicated input/output controllers.
You rarely need to worry about programming these hardware controllers
directly because the ROM BIOS and DOS provide services that take care of
these low-level functions. If you need to know the details of the
interface between the CPU and a hardware I/O controller, see the IBM
technical reference manuals and examine the ROM BIOS listings in the PC
and PC/AT manuals.
Linking the Parts: The Bus
As we mentioned, the PC family of computers links all internal control
circuitry by means of a circuit design known as a bus. A bus is simply a
shared path on the main circuit board to which all the controlling parts
of the computer are attached. When data is passed from one component to
another, it travels along this common path to reach its destination.
Every microprocessor, every control chip, and every byte of memory in
the computer is connected directly or indirectly to the bus. When a new
adapter is plugged into one of the expansion slots, it is actually
plugged directly into the bus, making it an equal partner in the
operation of the entire unit.
Any information that enters or leaves a computer system is temporarily
stored in at least one of several locations along the bus. Data is
usually placed in main memory, which in the PC family consists of
thousands or millions of 8-bit memory cells (bytes). But some data may
end up in a port or register for a short time while it waits for the CPU
to send it to its proper location. Generally, ports and registers hold
only 1 or 2 bytes of information at a time and are usually used as
stopover sites for data being sent from one place to another. (Ports and
registers are described in Chapter 2.)
Whenever a memory cell or port is used as a storage site, its location
is known by an address that uniquely identifies it. When data is ready
to be transferred, its destination address is first transmitted along
the address bus; the data follows along behind on the data bus. So the
bus carries more than data: It carries power and control information,
such as timing signals (from the system clock) and interrupt signals, as
well as the addresses of the thousands or millions of memory cells and
the many devices attached to the bus. To accommodate these four
different functions, the bus is divided into four parts: the power
lines, the control bus, the address bus, and the data bus. We're going
to discuss the subjects of address and data buses in greater detail
because they move information in a way that helps to explain some of the
unique properties of the PC family.
The Address Bus
The address bus in the PC, PC/XT, and PS/2 models 25 and 30 uses 20
signal lines to transmit the addresses of the memory cells and devices
attached to the bus. (Memory addressing is discussed more fully on page
13 and in Chapter 3.) Because two possible values (either 1 or 0) can
travel along each of the 20 address lines, these computers can specify
2^20 addresses──the limit of the addressing capability of the 8088 and
8086 microprocessors. This amounts to more than a million possible
addresses.
The 80286 used in the PC/AT can address 2^24 bytes of memory, so the AT
has a 24-line address bus. The bus in the 80286-based PS/2 models 50 and
60 also supports 24-bit memory addressing; in the 80386-based PS/2 Model
80, the bus has 32-bit addressing capability.
The Data Bus
The data bus works with the address bus to carry data throughout the
computer. The PC's 8088-based system uses a data bus that has 8 signal
lines, each of which carries a single binary digit (bit); data is
transmitted across this 8-line bus in 8-bit (1-byte) units. The 80286
microprocessor of the AT uses a 16-bit data bus and therefore passes
data in 16-bit (1-word) units.
The 8088, being a 16-bit microprocessor, can work with 16 bits of data
at a time, exactly like its relative the 80286. Although the 8088 can
work with 16-bit numbers internally, the size of its data bus allows the
8088 to pass data only 8 bits at a time. This has led some people to
comment that the 8088 is not a true 16-bit microprocessor. Rest assured
that it is, even though it is less powerful than the 80286. The 16-bit
data bus of the 80286 does help it move data around more efficiently
than the 8088, but the real difference in speed between the 8088 and the
AT comes from the AT's faster clock rate and its more powerful internal
organization.
There is an important practical reason why so many computers, including
the older members of the PC family, use the 8088 with its 8-bit data
bus, rather than the 8086 with its 16-bit bus. The reason is simple
economics. A variety of 8-bit circuitry elements are available in large
quantities at low prices. When the PC was being designed, 16-bit
circuitry was more expensive and was less readily available. The use of
the 8088, rather than the 8086, was important not only to hold down the
cost of the PC, but also to avoid a shortage of parts. The price of
16-bit circuitry elements has decreased significantly since then,
however, and it has become economically feasible to use the more
efficient 80286 with its 16-bit bus. Furthermore, the 80286 is able to
use a mixture of 8-bit parts and 16-bit parts, thereby maintaining
compatibility within the PC family.
Micro Channel Architecture
The PS/2 models 50, 60, and 80 introduced a new bus hardware design that
IBM calls Micro Channel architecture. Both the Micro Channel bus in the
PS/2s and the earlier PC and PC/AT bus accomplish the same task of
communicating addresses and data to plug-in adapters. The Micro Channel
bus hardware is designed to run at higher speeds than its predecessors
as well as to allow for more flexible adapter hardware designs. The
Micro Channel differs from the PC and PC/AT bus design both in its
physical layout and in its signal specifications, so an adapter that can
be used with one bus is incompatible with the other.
The differences between the original PC bus, the PC/AT bus, and the
Micro Channel bus are important in operating system software but not in
applications programs. Although all programs rely implicitly on the
proper functioning of the address and data buses, very few programs are
actually concerned with programming the bus directly. We'll come back to
the Micro Channel architecture only when we describe PS/2 ROM BIOS
services that work specifically with it.
Memory
So far, we've discussed the CPU, the support chips, and the bus, but
we've only touched on memory. We've saved our discussion of memory for
the end of this chapter because memory chips, unlike the other chips
we've discussed, don't control or direct the flow of information through
a computer system; they merely store information until it is needed.
The number and storage capacity of memory chips that exist inside the
computer determine the amount of memory we can use for programs and
data. Although this may vary from one computer to another, all PCs and
PS/2s come with at least 40 KB of read-only memory (ROM)──with space for
more──and between 64 KB and 2 MB of random-access memory (RAM). Both ROM
and RAM capacities can be augmented by installing additional memory
chips in empty sockets on the motherboard as well as by installing a
memory adapter in one of the system expansion slots. But this is only
the physical view of memory. A program sees memory not as a set of
individual chips, but as a set of thousands or millions of 8-bit
(1-byte) storage cells, each with a unique address.
Programmers must also think of memory in this way──not in terms of how
much physical memory there is, but in terms of how much addressable
memory there is. The 8088 and 8086 can address up to 1 MB (1024 KB, or
exactly 1,048,576 bytes) of memory. In other words, that's the maximum
number of addresses, and therefore the maximum number of individual
bytes of information, the processors can refer to. Memory addressing is
discussed in more detail in Chapter 2.
CPU Address Space
Each byte is referred to by a 20-bit numeric address. In the 8086 memory
scheme, the addresses are 20 bits "wide" because they must travel along
the 20-bit address bus. This gives the 8086 an address space with
address values that range from 00000H through FFFFFH (0 through
1,048,576 in decimal notation). If you have trouble understanding hex
notation, you might want to take a quick look at Appendix B.
Similarly, the 80286's 24-bit addressing scheme lets it use extended
address values in the range 000000H through FFFFFFH, or 16 MB. The 80386
can use extended 32-bit addresses, so its maximum address value is
FFFFFFFFH; that is, the 80386 can directly address up to 4,294,967,296
bytes, or four gigabytes (GB), of memory. This is enough memory for most
practical purposes, even for the most prolific programmer.
Although the 80286 and 80386 can address more than 1 MB of memory, any
program compatible with the 8086 and with DOS must limit itself to
addresses that lie in the 1 MB range available to the 8086. When the IBM
PC first appeared in 1981, 1 MB seemed like a lot of memory, but large
business-applications programs, memory-resident utility programs, and
system software required for communications and networking can easily
fill up the entire 8086 address space.
One way to work around the 1 MB limit is with the LIM
(Lotus-Intel-Microsoft) Expanded Memory Specification (EMS). The EMS is
based on special hardware and software that map additional RAM into the
8086 address space in 16 KB blocks. The EMS hardware can map a number of
different 16 KB blocks into the same 16 KB range of 8086 addresses.
Although the blocks must be accessed separately, the EMS lets up to 2048
different 16 KB blocks map to the same range of 8086 addresses. That's
up to 32 MB of expanded memory.
────────────────────────────────────────────────────────────────────────
NOTE:
Don't confuse EMS "expanded" memory with the "extended" memory located
above the first megabyte of 80286 or 80386 memory. Although many
memory expansion adapters can be configured to serve as either
expanded or extended memory (or both), these two memory configurations
are very different from both a hardware and software point of view.
────────────────────────────────────────────────────────────────────────
The System Memory Map
On the original IBM PC, the 1 MB address space of the 8088 was split
into several functional areas. (See Figure 1-6.) This memory map has
been carried forward for compatibility in all subsequent PC and PS/2
models.
┌────────────────────────────────────┐
│ PC/AT and PS/2 extended memory │
└────────────────────────────────────┘
100000H ────►┌────────────────────────────────────┐
│ Reserved for ROM BIOS │
E0000H ────►├────────────────────────────────────┤
│ Reserved for installable ROM │
C0000H ────►├────────────────────────────────────┤
│ Video buffers │
A0000H ────►└────────────────────────────────────┘
┌────────────────────────────────────┐──┐
│ Transient portion of DOS │ │
├────────────────────────────────────┤ │
│ │ │
│ Transient Program Area │ │
│ (user programs and data) │ │
├────────────────────────────────────┤ │
│ Resident portion of DOS │ ├── System
├────────────────────────────────────┤ │ RAM
│ Data area for ROM BIOS and BASIC │ │
00500H ────►├────────────────────────────────────┤ │
│ Data area for ROM BIOS │ │
00400H ────►├────────────────────────────────────┤ │
│ Interrupt vectors │ │
00000H ────►└────────────────────────────────────┘──┘
Figure 1-6. An outline of memory usage in PCs and PS/2s.
Some of the layout of the PC and PS/2 memory map is a consequence of the
design of the 8086 microprocessor. For example, the 8086 always maintains
a list of interrupt vectors (addresses of interrupt handling routines) in
the first 1024 bytes of RAM. Similarly, all 8086-based microcomputers have
ROM memory at the high end of the 1 MB address space, because the 8086,
when first powered up, executes the program that starts at address FFFF0H.
The rest of the memory map follows this general division between RAM at
the bottom of the address space and ROM at the top. A maximum of 640 KB of
RAM can exist between addresses 00000H and 9FFFFH. (This is the memory
area described by the DOS CHKDSK program.) Subsequent memory blocks are
reserved for video RAM (A0000H through BFFFFH), installable ROM modules
(C0000H through DFFFFH), and permanent ROM (E0000H through FFFFFH). We'll
explore each of these memory areas in greater detail in the chapters that
follow.
Design Philosophy
Before leaping into the following chapters, we should discuss the design
philosophy behind the PC family. This will help you understand what is
(and what isn't) important or useful to you.
Part of the design philosophy of the IBM personal computer family centers
around a set of ROM BIOS service routines (see Chapters 8 through 13)
that provide essentially all the control functions and operations that IBM
considers necessary. The basic philosophy of the PC family is: Let the ROM
BIOS do it; don't mess with direct control. In our judgment, this is a
sound idea that has several beneficial results. Using the ROM BIOS
routines encourages good programming practices, and it avoids some of the
kludgy tricks that have been the curse of many other computers. It also
increases the chances of your programs working on every member of the PC
family. In addition, it gives IBM more flexibility in making improvements
and additions to the line of PC computers. However, it would be naive for
us to simply say to you, "Don't mess with direct control of the hardware."
For good reasons or bad, you may want or may need to have your programs
work as directly with the computer hardware as possible, doing what is
colorfully called "programming down to the bare metal."
Still, as the PC family has evolved, programmers have had the opportunity
to work with increasingly powerful hardware and system software. The newer
members of the PC family provide faster hardware and better system
software, so direct programming of the hardware does not necessarily
result in significantly faster programs. For example, with an IBM PC
running DOS, the fastest way to display text on the video display is to
use assembly-language routines that bypass DOS and directly program the
video hardware. Video screen output is many times slower if you route it
through DOS. Contrast this with a PC/AT or PS/2 running OS/2, where the
best way to put text on the screen is to use the operating system output
functions. The faster hardware and the efficient video output services in
OS/2 make direct programming unnecessary.
As you read the programming details we present in this book, keep in mind
that you can often obtain a result or accomplish a programming task
through several means, including direct hardware programming, calling the
ROM BIOS, or using a DOS service. You must always balance portability,
convenience, and performance as you weigh the alternatives. The more you
know about what the hardware, the ROM BIOS, and the operating system can
do, the better your programs can use them.
────────────────────────────────────────────────────────────────────────────
Chapter 2 The Ins and Outs
How the 8086 Communicates
The 8086 Data Formats
How the 8086 Addresses Memory
Segmented Addresses
80286 and 80386 Protected-Mode Addresses
Address Compatibility
The 8086 Registers
The Scratch-Pad Registers
The Segment Registers
The Offset Registers
The Flags Register
Addressing Memory Through Registers
Rules for Using Registers
How the 8086 Uses I/O Ports
How the 8086 Uses Interrupts
Software Interrupts
Hardware Interrupts
Generally speaking, the more you know about how your computer works, the
more effective you'll be at writing programs for it. High-level
programming languages, such as BASIC and C, are not designed to include
every possible function that you might need while programming──though
admittedly, some are better than others. At some point, you will want to
go deeper into your system and use some of the routines the languages
themselves use, or perhaps go even deeper and program at the hardware
level.
Although some languages provide limited means to talk directly to memory
(as with PEEK and POKE in BASIC) or even to some of the chips (as with
BASIC's INP and OUT statements), most programmers eventually resort to
assembly language, the basic language from which all other languages and
operating systems are built. The 8086 assembly language, like all other
assembly languages, is composed of a set of symbolic instructions, as
shown in Figure 2-1. An assembler translates these instructions and the
data associated with them into a binary form, called machine language,
that can reside in memory and be processed by the 8086 to accomplish
specific tasks.
╓┌─┌──────────────────┌──────────────────────────────────────────────────────╖
Mnemonic Full Name
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
Instructions recognized by all 8086-family microprocessors
AAA ASCII Adjust After Addition
AAD ASCII Adjust After Division
AAM ASCII Adjust After Multiplication
AAS ASCII Adjust After Subtraction
ADC ADd with Carry
ADD ADD
AND AND
CALL CALL
CBW Convert Byte to Word
CLC CLear Carry flag
CLD CLear Direction flag
CLI CLear Interrupt flag
CMC CoMplement Carry flag
CMP CoMPare
CMPS CoMPare String
CMPSB CoMPare String (Bytes)
CMPSW CoMPare String (Words)
CWD Convert Word to Doubleword
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
CWD Convert Word to Doubleword
DAA Decimal Adjust After Addition
DAS Decimal Adjust After Subtraction
DEC DECrement
DIV Unsigned DIVide
ESC ESCape
HLT HaLT
IDIV Integer DIVide
IMUL Integer MULtiply
IN INput from I/O port
INC INCrement
INT INTerrupt
INTO INTerrupt on Overflow
IRET Interrupt RETurn
JA Jump if Above
JAE Jump if Above or Equal
JB Jump if Below
JBE Jump if Below or Equal
JC Jump if Carry
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
JC Jump if Carry
JCXZ Jump if CX Zero
JE Jump if Equal
JG Jump if Greater than
JGE Jump if Greater than or Equal
JL Jump if Less than
JLE Jump if Less than or Equal
JMP JuMP
JNA Jump if Not Above
JNAE Jump if Not Above or Equal
JNB Jump if Not Below
JNBE Jump if Not Below or Equal
JNC Jump if No Carry
JNE Jump if Not Equal
JNG Jump if Not Greater than
JNGE Jump if Not Greater than or Equal
JNL Jump if Not Less than
JNLE Jump if Not Less than or Equal
JNO Jump if Not Overflow
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
JNO Jump if Not Overflow
JNP Jump if Not Parity
JNS Jump if Not Sign
JNZ Jump if Not Zero
JO Jump if Overflow
JP Jump if Parity
JPE Jump if Parity Even
JPO Jump if Parity Odd
JS Jump if Sign
JZ Jump if Zero
LAHF Load AH with Flags
LDS Load pointer using DS
LEA Load Effective Address
LES Load pointer using ES
LOCK LOCK bus
LODS LOaD String
LODSB LOaD String (Bytes)
LODSW LOaD String (Words)
LOOP LOOP
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
LOOP LOOP
LOOPE LOOP while Equal
LOOPNE LOOP while Not Equal
LOOPNZ LOOP while Not Zero
LOOPZ LOOP while Zero
MOV MOVe data
MOVS MOVe String
MOVSB MOVe String (Bytes)
MOVSW MOVe String (Words)
MUL MULtiply
NEG NEGate
NOP No OPeration
NOT NOT
OR OR
OUT OUTput to I/O port
POP POP
POPF POP Flags
PUSH PUSH
PUSHF PUSH Flags
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
PUSHF PUSH Flags
RCL Rotate through Carry Left
RCR Rotate through Carry Right
REP REPeat
REPE REPeat while Equal
REPNE REPeat while Not Equal
REPNZ REPeat while Not Zero
REPZ REPeat while Zero
RET RETurn
ROL ROtate Left
ROR ROtate Right
SAHF Store AH into Flags
SAL Shift Arithmetic Left
SAR Shift Arithmetic Right
SBB SuBtract with Borrow
SCAS SCAn String
SCASB SCAn String (Bytes)
SCASW SCAn String (Words)
SHL SHift Left
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
SHL SHift Left
SHR SHift Right
STC SeT Carry flag
STD SeT Direction flag
STI SeT Interrupt flag
STOS STOre String
STOSB STOre String (Bytes)
STOSW STOre String (Words)
SUB SUBtract
TEST TEST
WAIT WAIT
XCHG eXCHanGe
XLAT transLATe
XOR eXclusive OR
Instructions recognized by the 80286 and 80386 only:
ARPL Adjust RPL field of selector
BOUND Check array index against BOUNDs
CLTS CLear Task-Switched flag
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
CLTS CLear Task-Switched flag
ENTER Establish stack frame
INS INput String from I/O port
LAR Load Access Rights
LEAVE Discard stack frame
LGDT Load Global Descriptor Table register
LIDT Load Interrupt Descriptor Table register
LLDT Load Local Descriptor Table register
LMSW Load Machine Status Word
LSL Load Segment Limit
LTR Load Task Register
OUTS OUTput String to I/O port
POPA POP All general registers
PUSHA PUSH All general registers
SGDT Store Global Descriptor Table register
SIDT Store Interrupt Descriptor Table register
SLDT Store Local Descriptor Table register
SMSW Store Machine Status Word
STR Store Task Register
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
STR Store Task Register
VERR VERify a segment selector for Reading
VERW VERify a segment selector for Writing
Instructions recognized by the 80386 only:
BSF Bit Scan Forward
BSR Bit Scan Reverse
BT Bit Test
BTC Bit Test and Complement
BTR Bit Test and Reset
BTS Bit Test and Set
CDQ Convert Doubleword to Quadword
CMPSD CoMPare String (Doublewords)
CWDE Convert Word to Doubleword in EAX
LFS Load pointer using FS
LGS Load pointer using GS
LSS Load pointer using SS
LODSD LOaD String (Doublewords)
MOVSD MOVe String (Doublewords)
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
MOVSD MOVe String (Doublewords)
MOVSX MOVe with Sign-eXtend
MOVZX MOVe with Zero-eXtend
SCASD SCAn String (Doublewords)
SETA SET byte if Above
SETAE SET byte if Above or Equal
SETB SET byte if Below
SETBE SET byte if Below or Equal
SETC SET byte if Carry
SETE SET byte if Equal
SETG SET byte if Greater
SETGE SET byte if Greater or Equal
SETL SET byte if Less
SETLE SET byte if Less or Equal
SETNA SET byte if Not Above
SETNAE SET byte if Not Above or Equal
SETNB SET byte if Not Below
SETNBE SET byte if Not Below or Equal
SETNC SET byte if No Carry
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
SETNC SET byte if No Carry
SETNE SET byte if Not Equal
SETNG SET byte if Not Greater
SETNGE SET byte if Not Greater or Equal
SETNL SET byte if Not Less
SETNLE SET byte if Not Less or Equal
SETNO SET byte if Not Overflow
SETNP SET byte if Not Parity
SETNS SET byte if Not Sign
SETNZ SET byte if Not Zero
SETO SET byte if Overflow
SETP SET byte if Parity
SETPE SET byte if Parity Even
SETPO SET byte if Parity Odd
SETS SET byte if Sign
SETZ SET byte if Zero
SHLD SHift Left (Doubleword)
SHRD SHift Right (Doubleword)
STOSD STOre String (Doublewords)
Mnemonic Full Name
──────────────────────────────────────────────────────────────────────────
STOSD STOre String (Doublewords)
──────────────────────────────────────────────────────────────────────────
Figure 2-1. The instruction set used with the 8086, 80286, and 80386.
──────────────────────────────────────────────────────────────────────────
NOTE:
Although this chapter discusses the details of 8086 programming,
remember that we're implicitly talking about the 8088, 80286, and 80386
as well. Information pertaining exclusively to the 80286 or 80386 will
be noted.
──────────────────────────────────────────────────────────────────────────
The operations that the 8086 instructions can perform break down into only
a few categories. They can do simple, four-function integer arithmetic.
They can move data around. They can, using only slightly clumsy methods,
manipulate individual bits. They can test values and take logical action
based on the results. And last but not least, they can interact with the
circuitry around them. The size of each instruction varies, but generally
the most basic and often-used instructions are the shortest.
Assembly-language programming can be carried out on one of two levels: to
create interface routines that will tie high-level programs to the
lower-level DOS and ROM-BIOS routines; or to create full-fledged
assembly-language programs that are faster and smaller than equivalent
high-level programs, or that perform exotic tasks at the hardware level,
perhaps accomplishing a feat that is accomplished nowhere else. Either
way, to understand how to use assembly language, you must understand how
8086-family microprocessors process information and how they work with the
rest of the computer. The rest of this chapter describes how the
microprocessor and the computer's other parts communicate.
How the 8086 Communicates
The 8086, 80286, and 80386 interact with the circuitry around them in
three ways: through direct and indirect memory access, through
input/output (I/O) ports, and with signals called interrupts.
The microprocessor uses memory by reading or writing values at memory
locations that are identified with numeric addresses. The memory locations
can be accessed in two ways: through the direct memory access (DMA)
controller or through the microprocessor's internal registers. The disk
drives and the serial communications ports can directly access memory
through the DMA controller. All other devices transfer data to and from
memory by way of the microprocessor's registers.
Input/Output ports are the microprocessor's general means of communicating
with any computer circuitry other than memory. Like memory locations, I/O
ports are identified by number, and data can be read from or written to
any port. I/O port assignment is unique to the design of any particular
computer. Generally, all members of the IBM PC family use the same port
specifications, with just a few variations among the different models.
(See page 37.)
Interrupts are the means by which the circuitry outside the microprocessor
reports that something (such as a keystroke) has happened and requests
that some action be taken. Although interrupts are essential to the
microprocessor's interaction with the hardware around it, the concept of
an interrupt is useful for other purposes as well. For example, a program
can use the INT instruction to generate a software interrupt that requests
a service from DOS or from the system ROM BIOS. Interrupts are quite
important when programming the PC family, so we'll devote a special
section to them at the end of this chapter.
The 8086 Data Formats
Numeric data. The 8086 and 80386 are able to work with only four simple
numeric data formats, all of which are integer values. The formats are
founded on two building blocks: the 8-bit byte and the 16-bit (2-byte)
word. Both of these basic units are related to the 16-bit processing
capacity of the 8086. The byte is the more fundamental unit; and when the
8086 and 80286 address memory, bytes are the basic unit addressed. In a
single byte, these microprocessors can work with unsigned positive numbers
ranging in value from 0 through 255 (that is, 2^8 possibilities). If the
number is a signed value, one of the 8 bits represents the sign, so only 7
bits represent the value. Thus a signed byte can represent values ranging
from -128 through +127. (See Figure 2-2.)
The 8086 and 80286 can also operate on 16-bit signed and unsigned values,
or words. Words are stored in memory in two adjacent bytes, with the low-
order byte preceding the high-order byte. (See the discussion of
"back-words storage" on page 24.)
Range
Size Signed? Dec Hex
──────────────────────────────────────────────────────────────────────────
8 No 0 through 255 00H through FFH
8 Yes -128 through 0 through 80H through 00H through
+127 7FH
16 No 0 through 65,535 0000H through FFFFH
16 Yes -32,768 through 0 8000H through 0000H
through +32,767 through 7FFFH
32 No 0 through 4,294,967,295 00000000H through
FFFFFFFFH
32 Yes -2,147,483,648 through 00000000H through
+2,147,483,647 00000000H through
7FFFFFFFH
──────────────────────────────────────────────────────────────────────────
Figure 2-2. The six data formats used in the 8086 family. (Only the 80386
supports 32-bit formats.)
A word interpreted as an unsigned, positive number can have 2^16 different
values ranging from 0 through 65,535. As a signed number, the value can
range from -32,768 through +32,767.
The 80386 differs from its predecessors in that it can also work with
32-bit integer values, or doublewords. A doubleword represents a signed or
unsigned 4-byte integer with any of 2^32 (or 4,294,967,295) different
values.
Character data. Character data is stored in the standard ASCII format,
with each character occupying 1 byte. The 8086 family knows nothing about
ASCII characters and treats them as arbitrary bytes, with one exception:
The instruction set accommodates decimal addition and subtraction
performed on binary coded decimal (BCD) characters. The actual arithmetic
is done in binary, but the combination of the AF flag (see page 33) and a
few special instructions makes it practical to work on decimal characters
and get decimal results, which can easily be converted to ASCII.
──────────────────────────────────────────────────────────────────────────
Back-Words Storage
While the PC's memory is addressed in units of individual 8-bit bytes,
many operations involve 16-bit words. In memory, a 16-bit word is stored
in any two adjacent 8-bit bytes. The least-significant byte of the word
is stored in the lower memory location, and the most significant byte is
stored in the higher memory location. From some points of view, storing
a word this way is the opposite of what you might expect. Due to the
backward appearance of this storage scheme, it is sometimes whimsically
called "back-words" storage.
─── Higher addresses ───────►
┌───────┬───────┐
│ 9C │ E6 │ Value of word is E69CH
└───────┴───────┘
─── Higher addresses ───────►
┌───────┬───────┬───────┬───────┐
│ 4A │ 5B │ 00 │ 12 │ Value of doubleword is 12005B4AH
└───────┴───────┴───────┴───────┘
If you are working with bytes and words in memory, you should take care
not to be confused by back-words storage. The source of the confusion
has mostly to do with how you write data. For example, if you are
writing a word value in hex, you write it like this: ABCD. The order of
significance is the same as if you are writing a decimal number: The
most significant digit is written first. But a word is stored in memory
with the lowest address location first. So, in memory, the number ABCD
appears as CDAB, with the bytes switched.
──────────────────────────────────────────────────────────────────────────
See Appendix C for more information on ASCII and the PC family's extended
ASCII character set.
How the 8086 Addresses Memory
The 8086 is a 16-bit microprocessor and cannot therefore work directly
with numbers larger than 16 bits. Theoretically, this means that the 8086
should be able to access only 64 KB of memory. But, as we noted in the
previous chapter, it can in fact access much more than that──1024 KB to be
exact. This is possible because of the 20-bit addressing scheme used with
the 8086, which expands the full range of memory locations that the 8086
can work with from 2^16 (65,536) to 2^20 (1,048,576). But the 8086 is
still limited by its 16-bit processing capacity. To access the 20-bit
addresses, it must use an addressing method that fits into the 16-bit
format.
Segmented Addresses
The 8086 divides the addressable memory space into segments, each of which
contains 64 KB of memory. Each segment begins at a paragraph address──that
is, a byte location that is evenly divisible by 16. To access individual
bytes or words, you use an offset that points to an exact byte location
within a particular segment. Because offsets are always measured relative
to the beginning of a segment, they are also called relative addresses or
relative offsets.
Together, a segment and an offset form a segmented address that can
designate any byte in the 8086's 1 MB address space. The 8086 converts a
given 32-bit segmented address into a 20-bit physical address by using the
segment value as a paragraph number and adding the offset value to it. In
effect, the 8086 shifts the segment value left by 4 bits and then adds the
offset value to create a 20-bit address.
Figure 2-3 shows how this is done for a segment value of 1234H and an
offset of 4321H. The segmented address is written as 1234:4321, with
4-digit hexadecimal values and with a colon separating the segment and
offset.
1234:4321
shift│ │
left│ │
│ │
▼ │
12340 │
+4321◄──┘
─────
16661
Figure 2-3. Decoding an 8086 segmented address. The segment value 1234H
is shifted left 4 bits (one hex digit) and added to the offset 4321H to
give the 20-bit physical address 16661H.
On the 8086, there's obviously a great deal of overlap in the range of
values that can be expressed as segmented addresses. Any given physical
address can be represented by up to 2^12 different segmented addresses.
For example, the physical address 16661H could be represented not only as
1234:4321, but also as 1666:0001, 1665:0011, 1664:0021, and so on.
80286 and 80386 Protected-Mode Addresses
The 80286 also uses segmented addresses, but when the 80286 runs in
protected mode, the addresses are decoded differently than on an 8086 or
in 80286 real mode. The 80286 decodes protected-mode segmented addresses
through a table of segment descriptors. The "segment" part of a segmented
address is not a paragraph value, but a "selector" that represents an
index into a segment descriptor table (Figure 2-4). Each descriptor in
the table contains a 24-bit base address that indicates the actual start
of a segment in memory. The resulting address is the sum of the 24-bit
base address and the 16-bit offset specified in the segmented address.
Thus, in protected mode the 80286 can access up to 2^24 bytes of memory;
that is, physical addresses are 24 bits in size.
This table-driven addressing scheme gives the 80286 a great deal of
control over memory usage. In addition to a 24-bit base address, each
segment descriptor specifies a segment's attributes (executable code,
program data, read-only, and so on), as well as a privilege level that
lets an operating system restrict access to the segment. This ability to
specify segment attributes and access privileges is of great use to a
multitasking operating system like OS/2.
The 80386 supports both 8086 and 80286 protected-mode addressing. The
80386 enhances the protected-mode addressing scheme by allowing 32-bit
segment base addresses and 32-bit offsets. Thus a single segmented
address, consisting of a 16-bit selector and a 32-bit offset, can specify
any of 2^32 different physical addresses.
0038:4321
│ │
│ │ │ │
├────────────┤ │ │
│ │ 28 │ │
├────────────┤ │ │
│ │ 30 │ │
├────────────┤ │ │
┌─┤ 012340 │ 38 ◄────┘ │
│ ├────────────┤ │
│ │ │ 40 │
│ ├────────────┤ │
│ │ │ │
│ │
└──────────────────► 012340 │
+ 4321 ◄─┘
──────
016661
Figure 2-4. Decoding an 80286 protected-mode segmented address. The
segment selector 38H indicates an entry in a segment descriptor table. The
segment descriptor contains a 24-bit segment base address which is added
to the offset 4321H to give the 24-bit physical address 016661H.
The 80386 also provides a "virtual 8086" addressing mode, in which
addressing is the same as the usual 8086 16-bit addressing, but with the
physical addresses corresponding to the 1 MB 8086 address space mapped
anywhere in the 4 gigabyte (GB) 80386 address space. This lets an
operating system execute several different 8086 programs, each in its own
1 MB, 8086-compatible address space.
Address Compatibility
The different addressing schemes used by the 80286 and 80386 are generally
compatible (except, of course, for 32-bit addressing on the 80386).
However, if you are writing an 8086 program that you intend to convert for
use in protected mode, be careful to use segments in an orderly fashion.
Although it's possible to specify a physical 8086 address with many
different segment-offset combinations, you will find it easier to convert
8086 programs to 80286 protected-mode addressing if you keep your segment
values as constant as possible.
For example, imagine that your program needs to access an array of
160-byte strings of characters, starting at physical address B8000H. A
poor way to access each string would be to exploit the fact that the
strings are each 10 paragraphs long by using a different segment value to
locate the start of each string:
B800:0000H (physical address B8000H)
B80A:0000H (physical address B80A0H)
B814:0000H (physical address B8140H)
B81E:0000H (physical address B81E0H)
A better way to accomplish the same addressing would be to keep a constant
segment value and change the offset value:
B800:0000H (physical address B8000H)
B800:00A0H (physical address B80A0H)
B800:0140H (physical address B8140H)
B800:01E0H (physical address B81E0H)
Although the result is the same on an 8086 and in real mode on an 80286,
you'll find that the second method is much better suited to 80286
protected mode, where each different segment selector designates a
different segment descriptor.
The 8086 Registers
The 8086 was designed to execute instructions and perform arithmetic and
logical operations as well as receive instructions and pass data to and
from memory. To do this, it uses a variety of 16-bit registers.
There are fourteen registers in all, each with a special use. Four
scratch-pad registers are used by programs to temporarily hold the
intermediate results and operands of arithmetic and logical operations.
Four segment registers hold segment values. Five pointer and index
registers hold the offsets that are used with the values in the segment
registers to locate data in memory. Finally, one flags register contains
nine 1-bit flags that are used to record 8086 status information and
control 8086 operations. (See Figure 2-5.)
Scratch-pad registers
7 0 7 0
╓──┬──┬──┬──┬──┬──┬──┬──╥──┬──┬──┬──┬──┬──┬──┬──╖
AX (accumulator) ║ AH ║ AL ║
╟──┼──┼──┼──┼──┼──┼──┼──╫──┼──┼──┼──┼──┼──┼──┼──╢
BX (base) ║ BH ║ BL ║
╟──┼──┼──┼──┼──┼──┼──┼──╫──┼──┼──┼──┼──┼──┼──┼──╢
CX (count) ║ CH ║ CL ║
╟──┼──┼──┼──┼──┼──┼──┼──╫──┼──┼──┼──┼──┼──┼──┼──╢
DX (data) ║ DH ║ DL ║
╙──┴──┴──┴──┴──┴──┴──┴──╨──┴──┴──┴──┴──┴──┴──┴──╜
Segment registers
15 0
╓──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──╖
CS (code segment) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
DS (data segment) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
SS (stack segment) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
ES (extra segment) ║ ║
╙──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──╜
Offset registers
15 0
╓──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──╖
IP (instruction pointer)║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
SP (stack pointer) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
BP (base pointer) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
SI (source index) ║ ║
╟──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──╢
DI (destination index) ║ ║
╙──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──╜
Flags register
15 0
╓──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──╖
Flags ║ │OF│DF│IF│TF│SF│ZF│ │AF│ │PF│ │CF║
╙──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──╜
Figure 2-5. The 8086 registers and flags.
The Scratch-Pad Registers
When a computer is processing data, a great deal of the microprocessor's
time is spent transferring data to and from memory. This access time can
be greatly reduced by keeping frequently used operands and results inside
the 8086. Four 16-bit registers, usually called the scratch-pad or data
registers, are designed for this purpose.
The scratch-pad registers are known as AX, BX, CX, and DX. Each of them
can also be subdivided and separately used as two 8-bit registers. The
high-order 8-bit registers are known as AH, BH, CH, and DH, and the low-
order 8-bit registers are known as AL, BL, CL, and DL.
The scratch-pad registers are used mostly as convenient temporary working
areas, particularly for arithmetic operations. Addition and subtraction
can be done in memory without using the registers, but the registers are
faster.
Although these registers are available for any kind of scratch-pad work,
each also has some special uses:
■ The AX (accumulator) register is the main register used to perform
arithmetic operations. (Although addition and subtraction can be
performed in any of the scratch-pad or offset registers, multiplication
and division must be done in AX or AL.)
■ The BX (base) register can be used to point to the beginning of a
translation table in memory. It can also be used to hold the offset
part of a segmented address.
■ The CX (count) register is used as a repetition counter for loop
control and repeated data moves. For example, the LOOP instruction in
assembly language uses CX to count the number of loop iterations.
■ The DX register is used to store data for general purposes, although
it, too, has certain specialized functions. For example, DX contains
the remainder of division operations performed in AX.
The Segment Registers
As we discussed earlier, the complete address of a memory location
consists of a 16-bit segment value and a 16-bit offset within the segment.
Four registers, called CS, DS, ES, and SS, are used to identify four
specific segments of memory. Five offset registers, which we'll discuss
shortly, can be used to store the relative offsets of the data within each
of the four segments.
Each segment register is used for a specific type of addressing:
■ The CS register identifies the code segment, which contains the program
that is being executed.
■ The DS and ES registers identify data segments where data used in a
program is stored.
■ The SS register identifies the stack segment. (See page 32 for more
information about stacks.)
Programs rarely use four separate segments to address four different 64 KB
areas of memory. Instead, the four segments specified in CS, DS, ES, and
SS usually refer to overlapping or identical areas in memory. In effect,
the different segment registers identify areas of memory used for
different purposes.
For example, Figure 2-6 shows how the values in the segment registers
correspond to the memory used in a hypothetical DOS program. The values in
the segment registers are chosen to correspond to the start of each
logically different area of memory, even though the 64 KB areas of memory
identified by each segment overlap each other. (See Chapter 20 for more
about segments and the memory layout of DOS programs.)
All 8086 instructions that use memory have an implied use of a particular
segment register for the operation being performed. For example, the MOV
instruction, because it acts on data, uses the DS register. The JMP
instruction, which affects the flow of a program, automatically uses the
CS register.
This means that you can address any 64 KB segment in memory by placing its
paragraph address in the appropriate segment register. For example, to
access data in the video buffer used by IBM's Color Graphics Adapter, you
place the paragraph address of the start of the buffer in a segment
register and then use the MOV instruction to transfer data to or from the
buffer.
┌─────────────────────┐ ◄───┐
SS=2919H │ Stack │ 29190H ├─ 2 KB
├─────────────────────┤ ◄───┤
│ │ │
DS=2419H │ Program data │ ├─ 20KB
│ │ 24190H │
├─────────────────────┤ ◄───┤
│ │ │
CS=2019H │ Executable code │ ├─ 16 KB
│ │ 20190H │
└─────────────────────┘ ◄───┘
Segment registers Physical addresses
Figure 2-6. Segment usage in a typical DOS program. Each segment register
contains the starting paragraph of a different area of memory.
mov ax,0B800h ; move the segment value into DS
mov ds,ax
mov al,[0000] ; copy the byte at B800:0000
; into AL
In interpreted BASIC you can use this method with the DEF SEG statement:
DEF SEG = &HB800 ' move the segment value into DS
X = PEEK(0000) ' copy the byte at B800:0000 into X
The Offset Registers
Five offset registers are used with the segment registers to contain
segmented addresses. One register, called the instruction pointer (IP),
contains the offset of the current instruction in the code segment; two
registers, called the stack registers, are intimately tied to the stack;
and the remaining two registers, called the index registers, are used to
address strings of data.
The instruction pointer (IP), also called the program counter (PC),
contains the offset within the code segment where the current program is
executing. It is used with the CS register to track the location of the
next instruction to be executed.
Programs do not have direct access to the IP register, but a number of
instructions, such as JMP and CALL, change the IP value implicitly.
The stack registers, called the stack pointer (SP) and the base pointer
(BP), provide offsets into the stack segment. SP gives the location of the
current top of the stack. Programs rarely change the value in SP directly.
Instead, they rely on PUSH and POP instructions to update SP implicitly.
BP is the register generally used to access the stack segment directly.
You'll see BP used quite often in the assembly-language examples that
appear in Chapters 8 through 20.
The index registers, called the source index (SI) and the destination
index (DI), can be used for general-purpose addressing of data. Also, all
string move and comparison instructions use SI and DI to address data
strings.
The Flags Register
The fourteenth and last register, called the flags register, is really a
collection of individual status and control bits called flags. The flags
are maintained in a register, so they can be either saved and restored as
a coordinated set or inspected as ordinary data. Normally, however, the
flags are set and tested as independent items──not as a set.
There are nine 1-bit flags in the 8086's 16-bit flags register, leaving 7
bits unused. (The 80286 and 80386 use some of the unused flags to support
protected-mode operation.) The flags can be logically divided into two
groups: six status flags, which record processor status information
(usually indicating what happened with a comparison or arithmetic
operation), and three control flags, which direct some of the 8086
instructions. Be prepared to see a variety of notations for the flags,
including distinct names for whether they are set (1) or clear (0). The
terms used in Figures 2-7 and 2-8 are the most common.
──────────────────────────────────────────────────────────────────────────
The Stack
The stack is a built-in feature of the 8086. It provides programs with a
place to store and keep track of work in progress. The most important
use of the stack is to keep a record of where subroutines were invoked
from and what parameters were passed to them. The stack can also be used
for temporary working storage, although this is less fundamental and
less common.
The stack gets its name from an analogy to a spring-loaded stack of
plates in a cafeteria: New data is "pushed" onto the top of the stack
and old data is "popped" off. A stack always operates in
last-in-first-out (LIFO) order. This means that when the stack is used
to keep track of where to return to a program, the most recent calling
program is returned to first. This way, a stack maintains the orderly
workings of programs, subroutines, and interrupt handlers, no matter how
complex their operation.
A stack is used from the bottom (highest address) to the top (lowest
address) so that when data is pushed onto the top of the stack, it is
stored at the memory addresses just below the current top of the stack.
The stack grows downward so that as data is added, the location of the
top of the stack moves to lower and lower addresses, decreasing the
value of SP each time. You need to keep this in mind when you access the
stack, which you are likely to do in assembly-language interface
routines.
Any part of any program can create a new stack space at any time, but
this is not usually done. Normally, when a program is run, a single
stack is created for it and used throughout the operation of the
program.
There is no simple way to estimate the size of stack that a program
might need, and the 8086's design does not provide any automatic way of
detecting when stack space is in short supply or exhausted. This can
make programmers nervous about the amount of space that should be set
aside for a stack. A conservative estimate of how much stack space to
maintain is about 2 KB (2048 bytes), the default amount allocated by
many high-level language compilers.
┌──────┐◄─── Bottom of stack
:1008 │ 5E00 │
┌──────┐◄─── Bottom of stack ├──────┤
:1008 │ 5E00 │ :1006 │ 4D00 │
├──────┤ ├──────┤
:1006 │ 4D00 │ :1004 │ 3C00 │◄─── Old top of stack
├──────┤ ├──────┤
:1004 │ 3C00 │◄─── Top of stack :1002 │ 2B00 │◄─── Top of stack
├──────┤ (SP=1004) ├──────┤ (SP=1002)
:1002 │ │ :1000 │ │
├──────┤ └────┘
:1000 │ │ ║ ║
└──────┘ PUSH
a. Stack before a PUSH b. Stack after a PUSH
────────────────────── ─────────────────────
┌──────┐◄─── Bottom of stack
:1008 │ 5E00 │
├──────┤
:1006 │ 4D00 │
├──────┤
:1004 │ 3C00 │◄─── Top of stack
├──────┤ (SP=1004)
:1002 │ │
├──────┤
:1000 │ │
└─║──║─┘
▼ ▼
POP
c. Stack after a POP
────────────────────
──────────────────────────────────────────────────────────────────────────
Code Name Use
──────────────────────────────────────────────────────────────────────────
CF Carry flag Indicates an arithmetic carry
OF Overflow flag Indicates signed arithmetic overflow
ZF Zero flag Indicates zero result, or equal
comparison
SF Sign flag Indicates negative result/comparison
PF Parity flag Indicates even number of 1 bits
AF Auxiliary carry flag Indicates adjustment needed in
binary-coded decimal (BCD) arithmetic
operations
──────────────────────────────────────────────────────────────────────────
Figure 2-7. The six status flags in the 8086's flags register.
Code Name Use
──────────────────────────────────────────────────────────────────────────
DF Direction flag Controls increment direction in
string operations (CMPS, LODS, MOVS,
SCAS, STOS)
IF Interrupt flag Controls whether interrupts are
enabled
TF Trap flag Controls single-step operation (used
by DEBUG) by generating an interrupt
at the end of every instruction
──────────────────────────────────────────────────────────────────────────
Figure 2-8. The three control flags in the 8086's flags register.
Addressing Memory Through Registers
We've seen that memory is always addressed by a combination of a segment
value and a relative offset. The segment value always comes from one of
the four segment registers.
In contrast, the relative offset can be specified in many different ways.
(See Figure 2-9.) For each machine instruction that accesses memory, the
8086 computes an effective address by combining one, two, or three of the
following:
■ The value in BX or BP
■ The value in SI or DI
■ A relative-offset value, called a displacement, that is part of the
instruction itself
╓┌─┌──────────────────┌───────────────────────────┌──────────────────┌───────►
Name Effective Address Example Comments
───────────────────────────────────────────────────────────────────────────
Immediate Value "addressed" is part mov ax,1234h Stores 12
of the 8086 instruction
Direct Specified as part of the mov ax,[1234h] Copies th
8086 instruction into AX.
register
Register indirect Contained in BX, SI, DI, or mov ax,[bx] Copies th
BP offset co
AX. The d
register
Name Effective Address Example Comments
───────────────────────────────────────────────────────────────────────────
register
[DI] is D
default i
Based The sum of a displacement mov ax,[bx+2] Copies th
(part of the instruction) or mov ax,2[bx] past the
and the value in BX or BP BX into A
segment r
DS; for [
SS.
Indexed The sum of a displacement mov ax,[si+2] Copies th
and the value in SI or DI or mov ax,2[si] past the
SI into A
segment r
Based indexed The sum of a displacement, mov ax,[bp+si+2] The offse
the value in SI or DI, and or mov ax,2[bp+si] values in
the value in BX or BP or When BX i
mov ax,2[bp][si] segment r
Name Effective Address Example Comments
───────────────────────────────────────────────────────────────────────────
mov ax,2[bp][si] segment r
BP is use
SS.
String addressing Source string: register movsb Copies th
indirect using SI memory at
Destination string: ES:[DI].
register indirect using DI
───────────────────────────────────────────────────────────────────────────
Figure 2-9. 8086 Addressing Modes. In assembly language, some
instructions can be specified in several different ways.
Each of the various ways of forming an effective address has its uses. You
can use the Immediate and Direct methods when you know the offset of a
particular memory location in advance. You must use one of the remaining
methods when you can't tell what an address will be until your program
executes. In the chapters ahead, you'll see examples of most of the
different 8086 addressing modes.
The notation used in specifying 8086 addresses is straightforward.
Brackets, [ ], are used to indicate that the enclosed item specifies a
relative offset. This is a key element of memory addressing: Without
brackets, the actual value stored in the register is used in whatever
operation is specified.
Rules for Using Registers
It is important to know that various rules apply to the use of registers,
and it is essential to be aware of these rules when writing assembly-
language interface routines. Because the rules and conventions of usage
vary by circumstance and by programming language, exact guidelines are not
always available, but the general rules that follow will apply in most
cases. (You will find additional guidance, and working models to copy, in
the examples in Chapters 8 through 20.) Keep in mind, though, that the
following rules are general, not absolute.
Probably the most useful rule for using the registers is simply to use
them for what they are designed for. The idea that each of the 8086
registers has certain special uses may seem somewhat quirky, particularly
to a programmer who is accustomed to working with a CPU that has a less
specialized set of registers (such as the 68000, for example). On the
8086, using the registers for their natural functions leads to cleaner,
more efficient source code and ultimately to more reliable programs.
For example, the segment registers are designed to contain segment values,
so don't use them for anything else. (In 80286 protected mode you can't
use them for anything else anyway without generating an error condition.)
The BP register is intended for stack addressing; if you use it for
anything else, you'll have to do some fancy footwork when you need to
address values in the stack segment.
Particular rules apply to the four segment registers (CS, DS, ES, and SS).
The CS register should be changed only through intersegment jumps and
subroutine calls.
Most programmers use the DS register to point to a default data segment
that contains the data most frequently used in a program. This means that
the value in the DS register is usually initialized at the beginning of a
program and then left alone. Should it be necessary to use DS to address a
different segment, its original value is saved, the new segment is
accessed, and then the original value is restored. In contrast, most
people use the ES register as needed to access arbitrary segments in
memory.
The stack segment (SS) and stack pointer (SP) registers should usually be
updated implicitly, either by PUSH and POP instructions or by CALL and RET
instructions that save subroutine return addresses on the stack. When DOS
loads a program into memory to be executed, it initializes SS and SP to
usable values. In .COM programs, SS:SP points to the end of the program's
default segment; in .EXE programs, SS:SP is determined explicitly by the
size and location of the program's stack segment. In either case, it's
rare that you need to change SS or SP explicitly.
If you need to discard a number of values from the stack or reserve
temporary storage space on top of the stack, you can increment or
decrement SP directly:
add sp,8 ; discard four words (8 bytes)
; from stack
sub sp,6 ; add three empty words (6 bytes)
; to top of stack
If you need to move the stack to a different location in memory, you must
generally update both SS and SP at the same time:
cli ; disable interrupts
mov ss,NewStackSeg ; update SS from a memory variable
mov sp,NewStackPtr ; update SP from a memory variable
sti ; re-enable interrupts
Be careful when you change SS and SP explicitly. If you modify SS but fail
to update SP, SS will be specifying a new stack segment while SP will be
pointing somewhere inside another stack segment──and that's asking for
trouble the next time you use the stack.
It's hard to be explicit about the use of the other registers. In general,
most programmers try to minimize memory accesses by keeping the
intermediate results of lengthy computations in registers. This is because
it takes longer to perform a computation on a value stored in memory than
on a value stored in a register. Of course, the 8086 has only so many
registers to work with, so you may find yourself running out of registers
before you run out of variables.
How the 8086 Uses I/O Ports
The 8086-family microprocessors communicate with and control many parts of
the computer through the use of input and output (I/O) ports. The I/O
ports are doorways through which information passes as it travels to or
from an I/O device, such as a keyboard or a printer. Most of the support
chips we described in Chapter 1 are accessed through I/O ports; in fact,
each chip may use several port addresses for different purposes.
Each port is identified by a 16-bit port number, which can range from 00H
through FFFFH (65,535). The CPU identifies a particular port by the port's
number.
As it does when accessing memory, the CPU uses the data and address buses
as conduits for communication with the ports. To access a port, the CPU
first sends a signal on the system bus to notify all I/O devices that the
address on the bus is that of a port. The CPU then sends the port address.
The device with the matching port address responds.
The port number addresses a memory location that is associated with an I/O
device but is not part of main memory. In other words, an I/O port number
is not the same as a memory address. For example, I/O port 3D8H has
nothing to do with memory address 003D8H. To access an I/O port, you don't
use data-transfer instructions like MOV and STOS. Instead, you use the
instructions IN and OUT, which are reserved for I/O port access.
──────────────────────────────────────────────────────────────────────────
NOTE:
Many high-level programming languages provide functions that access I/O
ports. The BASIC functions INP and OUT, and the C functions inp and
outp, are typical examples.
──────────────────────────────────────────────────────────────────────────
The uses of specific I/O ports are determined by the hardware designers.
Programs that make use of I/O ports need to be aware of the port numbers,
as well as their use and meaning. Port number assignments differ slightly
among the PC family members, but, in general, IBM has reserved the same
ranges of I/O port numbers for the same input/output devices in all PCs
and PS/2s. (See Figure 2-10.) For details on how each I/O port is used,
see the descriptions of the various input/output devices in the IBM
technical reference manuals.
╓┌─┌──────────────────────────────────────────────┌───────────────┌──────────►
Description I/O Port Comment
Numbers
───────────────────────────────────────────────────────────────────────────
Programmable Interrupt Controller (master) 20H─3FH
System timer 40H─5FH
Keyboard controller 60H─6FH On PS/2 Mode
are reserved
control and
System control port B 61H PS/2 models
Real-time clock, NMI mask 70H─7FH On PC, PC/XT
30, NMI mask
System control port A 92H PS/2 models
Description I/O Port Comment
Numbers
───────────────────────────────────────────────────────────────────────────
System control port A 92H PS/2 models
Programmable Interrupt Controller (slave) A0H─BFH On PS/2 Mode
Real-time clock B0H─BFH, PS/2 Model 3
E0H─EFH
Clear math coprocessor busy F0H
Reset math coprocessor F1H
Math coprocessor F8H─FFH
Fixed-disk controller 1F0H─1F8H
Game control adapter 200H─207H
Parallel printer 3 278H─27BH
Description I/O Port Comment
Numbers
───────────────────────────────────────────────────────────────────────────
Parallel printer 3 278H─27BH
Serial communications 2 2F8H─2FFH
Fixed-disk controller 320H─32FH PC/XT and PS
PC network 360H─363H,
368H─36BH
Parallel printer 2 378H─37BH
Monochrome Display Adapter 3B0H─3BBH Also used by
monochrome v
Parallel printer 1 3BCH─3BFH
Enhanced Graphics Adapter (EGA), 3C0H─3CFH
Video Graphics Array (VGA)
Description I/O Port Comment
Numbers
───────────────────────────────────────────────────────────────────────────
Video Graphics Array (VGA)
Color Graphics Adapter (CGA), 3D0H─3DFH Also used by
Multi-Color Graphics Array (MCGA) color video
Diskette controller 3F0H─3F7H
Serial communications 1 3F8H─3FFH
───────────────────────────────────────────────────────────────────────────
Figure 2-10. PC and PS/2 input/output port assignments. This table lists
the most frequently used I/O ports. For a complete list, see the IBM
Technical Reference manuals.
How the 8086 Uses Interrupts
An interrupt is an indication to the microprocessor that its immediate
attention is needed. The 8086-family microprocessors can respond to
interrupts from either hardware or software. A hardware device can
generate an interrupt signal that is processed by the programmable
interrupt controller (PIC) and passed to the microprocessor; in software,
the INT instruction generates an interrupt. In both cases, the
microprocessor stops processing and executes a memory-resident subroutine
called an interrupt handler. After the interrupt handler has performed its
task, the microprocessor resumes processing at the point the interrupt
occurred.
The 8086 supports 256 different interrupts, each identified by a number
between 00H and FFH (decimal 255). The segmented addresses of the 256
interrupt handlers are stored in an interrupt vector table that starts at
0000:0000H (that is, at the very beginning of available memory). Each
interrupt vector is 4 bytes in size, so you can locate the address of any
interrupt handler by multiplying the interrupt number by 4. You can also
replace an existing interrupt handler with a new one by storing the new
handler's segmented address in the appropriate interrupt vector.
Software Interrupts
Probably the most familiar type of interrupts are generated by the INT
instruction. Consider what happens when the CPU executes the following
instruction:
INT 12H
The CPU pushes the current contents of the flags register, the CS (code
segment) register, and the IP (instruction pointer) register onto the
stack. Then it transfers control to the interrupt handler corresponding to
interrupt number 12H, using the segmented address stored at 0000:0048H.
The CPU then executes the interrupt 12H handler, which responds
appropriately to interrupt 12H. The interrupt handler terminates with an
IRET instruction that pops CS:IP and the flags back into the registers,
thus transferring control back to the interrupted program.
Hardware Interrupts
The microprocessor responds to a hardware interrupt in much the same way
it responds to a software interrupt: by transferring control to an
interrupt handler. The important difference lies in the way the interrupt
is signalled.
Devices such as the system timer, the hard disk, the keyboard, and the
serial communications ports can generate interrupt signals on a set of
reserved interrupt request (IRQ) lines. These lines are monitored by the
PIC circuit, which assigns interrupt numbers to them. When a particular
hardware interrupt occurs, the PIC places the corresponding interrupt
number on the system data bus where the microprocessor can find it.
The PIC also assigns priorities to the various interrupt requests. For
example, the highest-priority PIC interrupt in all PCs and PS/2s is the
timer-tick interrupt, which is signalled on interrupt request line 0
(IRQ0) and is assigned interrupt 08H by the PIC. When a system timer
generates a timer- tick interrupt, it does so by signalling on IRQ0; the
PIC responds by signalling the CPU to execute interrupt 08H. If a
lower-priority hardware interrupt request occurs while the timer-tick
interrupt is being processed, the PIC delays the lower-priority interrupt
until the timer interrupt handler signals that it has finished its
processing.
When you coldboot the computer, the system start-up routines assign
interrupt numbers and priorities to the hardware interrupts by
initializing the PIC. In 8088- and 8086-based machines (PCs, PC/XTs, PS/2
models 25 and 30), interrupt numbers 08H through 0FH are assigned to
interrupt request levels 0 through 7 (IRQ0 through IRQ7). In PC/ATs and
PS/2 models 50, 60, and 80, an additional eight interrupt lines (IRQ8
through IRQ15) are assigned interrupt numbers 70H through 77H.
One hardware interrupt bypasses the PIC altogether. This is the
non-maskable interrupt (NMI), which is assigned interrupt number 02H in
the 8086 family. The NMI is used by devices that require absolute,
"now-or-never" priority over all other CPU functions. In particular, when
a hardware memory error occurs, the computer's RAM subsystem generates an
NMI. This causes the CPU to pass control to an interrupt 02H handler; the
default handler in the PC family resides in ROM and issues the "PARITY
CHECK" message you see when a memory error occurs.
When you debug a program on any member of the PC family, remember that
hardware interrupts are occurring all the time. For example, the system
timer-tick interrupt (interrupt 08H) occurs roughly 18.2 times per second.
The keyboard and disk-drive controllers also generate interrupts. Each
time these hardware interrupts occur, the 8086 uses the current stack to
save CS:IP and the flags register. If your stack is too small, or if you
are manipulating SS and SP when a hardware interrupt occurs, the 8086 may
damage valuable data when it saves CS:IP and the flags.
If you look back at our example of updating SS and SP on page 36, you'll
see that we explicitly disable hardware interrupts by executing the CLI
instruction prior to updating SS. This prevents a hardware interrupt from
occurring between the two MOV instructions while SS:SP is pointing
nowhere. (Actually, this is a problem only in very early releases of the
8088; the chip was later redesigned to prevent this problem by disabling
interrupts during the instruction that follows a data move into SS.)
We'll talk in more detail about how PCs and PS/2s use interrupts in
Chapters 3 and 8.
────────────────────────────────────────────────────────────────────────────
Chapter 3 The ROM Software
The Start-Up ROM
The ROM BIOS
Interrupt Vectors
Key Low-Memory Addresses
The ROM Version and Machine-ID Markers
The ROM BASIC
The ROM Extensions
Comments
It takes software to make a computer go. And getting a computer going and
keeping it going is much easier if some of that software is permanently
built into the computer. That's what the ROM programs are all about. ROM
stands for read-only memory──memory permanently recorded in the circuitry
of the computer's ROM chips, that can't be changed, erased, or lost.
PCs and PS/2s come with a substantial amount of ROM that contains the
programs and data needed to start and operate the computer and its
peripheral devices. The advantage of having a computer's fundamental
programs stored in ROM is that they are right there──built into the
computer──and there is no need to load them into memory from disk the way
that DOS must be loaded. Because they are permanent, the ROM programs are
very often the foundation upon which other programs (including DOS) are
built.
There are four elements to the ROM in IBM's PC family: the start-up
routines, which do the work of getting the computer started; the ROM
BIOS──an acronym for Basic Input/Output System──which is a collection of
machine-language routines that provide support services for the continuing
operation of the computer; the ROM BASIC, which provides the core of the
BASIC programming language; and the ROM extensions, which are programs
that are added to the main ROM when certain optional equipment is added to
the computer. We'll be examining each of these four major elements
throughout the rest of this chapter.
The ROM programs occupy addresses F000:0000H through F000:FFFFH in the
PC/XT/AT family and the PS/2 models 25 and 30, and E000:0000H through
F000:FFFFH in the other PS/2s. However, the routines themselves are not
located at any specific addresses in ROM as they are in other computers.
The address of a particular ROM routine varies among the different members
of the PC/XT/AT and PS/2 families.
Although the exact addresses of the ROM routines can vary, IBM provides a
consistent interface to the ROM software by using interrupts. Later in
this book we'll show you exactly how to use interrupts to execute the ROM
routines.
The Start-Up ROM
The first job the ROM programs have is to supervise the start-up of the
computer. Unlike other aspects of the ROM, the start-up routines have
little to do with programming the PC family──but it is still worthwhile to
understand what they do.
The start-up routines perform several tasks:
■ They run a quick reliability test of the computer (and the ROM
programs) to ensure everything is in working order.
■ They initialize the chips and the standard equipment attached to the
computer.
■ They set up the interrupt-vector table.
■ They check to see what optional equipment is attached.
■ They load the operating system from disk.
The following paragraphs discuss these tasks in greater detail.
The reliability test, part of a process known as the Power On Self Test
(POST), is an important first step in making sure the computer is ready.
All POST routines are quite brief except for the memory tests, which can
be annoyingly lengthy in computers that contain a large amount of memory.
The initialization process is slightly more complex. One routine sets the
default values for interrupt vectors. These default values either point to
the standard interrupt handlers located inside the ROM BIOS, or they point
to do-nothing routines in the ROM BIOS that may later be superseded by the
operating system or by your own interrupt handlers. Another initialization
routine determines what equipment is attached to the computer and then
places a record of it at standard locations in low memory. (We'll be
discussing this equipment list in more detail later in the chapter.) How
this information is acquired varies from model to model──for example, in
the PC it is taken mostly from the settings of two banks of switches
located on the computer's system board; in the PC/AT and the PS/2s, the
ROM BIOS reads configuration information from a special nonvolatile memory
area whose contents are initialized by special setup programs supplied by
IBM. The POST routines learn about the computer's hardware by a logical
inspection and test. In effect, the initialization program shouts to each
possible option, "Are you there?", and listens for a response.
No matter how it is acquired, the status information is recorded and
stored in the same way for every model so that your programs can examine
it. The initialization routines also check for new equipment and
extensions to ROM. If they find any, they momentarily turn control over to
the ROM extensions so that they can initialize themselves. The
initialization routines then continue executing the remaining start-up
routines (more on this later in the chapter).
The final part of the start-up procedure, after the POST tests, the
initialization process, and the incorporation of ROM extensions, is called
the bootstrap loader. It's a short routine that loads a program from disk.
In essence, the ROM bootstrap loader attempts to read a disk boot program
from a disk. If the boot program is successfully read into memory, the ROM
loader passes control of the computer to it. The disk boot program is
responsible for loading another, larger disk program, which is usually a
disk operating system such as DOS, but can be a self-contained and
self-loading program, such as Microsoft Flight Simulator. If the ROM
bootstrap loader cannot read a disk's boot program, it either activates
the built-in ROM BASIC or displays an error message if the disk boot
program contains an error. As soon as either of these two events occurs,
the system start-up procedure is finished and the other programs take
over.
The ROM BIOS
The ROM BIOS is the part of ROM that is in active use whenever the
computer is at work. The role of the ROM BIOS is to provide the
fundamental services that are needed for the operation of the computer.
For the most part, the ROM BIOS controls the computer's peripheral
devices, such as the display screen, keyboard, and disk drives. When we
use the term BIOS in its narrowest sense, we are referring to the device
control programs──the programs that translate a simple command, such as
read-something-from-the-disk, into all the steps needed to actually
perform the command, including error detection and correction. In the
broadest sense, the BIOS includes not only routines needed to control the
PC's devices, but also routines that contain information or perform tasks
that are fundamental to other aspects of the computer's operation, such as
keeping track of the time of day.
Conceptually, the ROM BIOS programs lie between programs that are
executing in RAM (including DOS) and the hardware. In effect, this means
that the BIOS works in two directions in a two-sided process. One side
receives requests from programs to perform the standard ROM BIOS
input/output services. A program invokes these services with a combination
of an interrupt number (which indicates the subject of the service
request, such as printer services) and a service number (which indicates
the specific service to be performed). The other side of the ROM BIOS
communicates with the computer's hardware devices (display screen, disk
drives, and so on), using whatever detailed command codes each device
requires. This side of the ROM BIOS also handles any hardware interrupts
that a device generates to get attention. For example, whenever you press
a key, the keyboard generates an interrupt to let the ROM BIOS know.
Of all the ROM software, the BIOS services are probably the most
interesting and useful to programmers──as a matter of fact, we have
devoted six chapters to the BIOS services in Chapters 8 through 13. Since
we deal with them so thoroughly later on, we'll skip any specific
discussion of what the BIOS services do and instead focus on how the BIOS
as a whole keeps track of the computer's input and output processes.
Interrupt Vectors
The IBM PC family, like all computers based on the Intel 8086 family of
microprocessors, is controlled largely through the use of interrupts,
which can be generated by hardware or software. The BIOS service routines
are no exception; each is assigned an interrupt number that you must call
when you want to use the service.
When an interrupt occurs, control of the computer is turned over to an
interrupt-handling subroutine that is often stored in the system's ROM (a
BIOS service routine is nothing more than an interrupt handler). The
interrupt handler is called by loading its segment and offset addresses
into registers that control program flow: the CS (code segment) register
and the IP (instruction pointer) register──together known as the CS:IP
register pair. Segment addresses that locate interrupt handlers are called
interrupt vectors.
During the system start-up process, the BIOS sets the interrupt vectors to
point to the interrupt handlers in ROM. The interrupt vector table starts
at the beginning of RAM, at address 0000:0000H. (See Chapter 2 for more
about interrupts and interrupt vectors.) Each entry in the table is stored
as a pair of words, with the offset portion first and the segment portion
second. The interrupt vectors can be changed to point to a new interrupt
handler simply by locating the vector and changing its value.
As a general rule, PC-family interrupts can be divided into six
categories: microprocessor, hardware, software, DOS, BASIC, and general
use.
Microprocessor interrupts, often called logical interrupts, are designed
into the microprocessor. Four of them (interrupts 00H, 01H, 03H, and 04H)
are generated by the microprocessor itself, and another (interrupt 02H,
the nonmaskable interrupt) is activated by a signal generated by certain
hardware devices, such as the 8087 math coprocessor.
Hardware interrupts are built into the PC hardware. In PCs, XTs, and PS/2
models 25 and 30, interrupt numbers 08H through 0FH are used for hardware
interrupts; in ATs and PS/2 models 50, 60, and 80, interrupt numbers 08H
through 0FH and 70H through 77H are reserved for hardware interrupts. (See
Chapter 2 for more about hardware interrupts.)
──────────────────────────────────────────────────────────────────────────
The Part DOS Plays
The ROM bootstrap loader's only function is to read a bootstrap program
from a disk and transfer control to it. On a bootable DOS disk, the disk
bootstrap program verifies that DOS is stored on the disk by looking for
two hidden files named IBMBIO.COM and IBMDOS.COM. If it finds them, it
loads them into memory along with the DOS command interpreter,
COMMAND.COM. During this loading process, optional parts of DOS, such as
installable device drivers, may also be loaded.
The IBMBIO.COM file contains extensions to the ROM BIOS. These
extensions can be changes or additions to the basic I/O operations and
often include corrections to the existing ROM BIOS, new routines for new
equipment, or customized changes to the standard ROM BIOS routines.
Because they are part of disk software, the IBMBIO.COM routines provide
a convenient way to modify the ROM BIOS. All that is necessary, besides
the new routine, is that the interrupt vectors for the previous ROM BIOS
routines be changed to point to the location in memory where the new
disk BIOS routines are placed. Whenever new devices are added to the
computer, their support programs can be included in the IBMBIO.COM file
or as installable device drivers, eliminating the need to replace ROM
chips. See Appendix A for more on device drivers.
You can think of the ROM BIOS routines as the lowest-level system
software available, performing the most fundamental and primitive I/O
operations. The IBMBIO.COM routines, being extensions of the ROM BIOS,
are essentially on the same low level, also providing basic functions.
By comparison, the IBMDOS.COM routines are more sophisticated; think of
them as occupying the next level up, with applications programs on top.
The IBMDOS.COM file contains the DOS service routines. The DOS services,
like the BIOS services, can be called by programs through a set of
interrupts whose vectors are placed in the interrupt-vector table in low
memory. One of the DOS interrupts, interrupt 21H (decimal 33), is
particularly important because when invoked, it gives you access to a
rather large group of DOS functions. The DOS functions provide more
sophisticated and efficient control over the I/O operations than the
BIOS routines do, especially with regard to disk file operations. All
standard disk processes──formatting diskettes; reading and writing data;
opening, closing, and deleting files; performing directory searches──are
included in the DOS functions and provide the foundation for many
higher-level DOS programs, such as FORMAT, COPY, and DIR. Your programs
can use the DOS services when they need more control of I/O operations
than programming languages allow, and when you are reluctant to dig all
the way down to the BIOS level. The DOS services are a very important
part of this book, and we have devoted five chapters to them. (See
Chapters 14 through 18.)
The COMMAND.COM file is the third and most important part of DOS, at
least from a utilitarian standpoint. This file contains the routines
that interpret the commands you type in through the keyboard in the DOS
command mode. By comparing your input to a table of command names, the
COMMAND.COM program can differentiate between internal commands that are
part of the COMMAND.COM file, such as RENAME or ERASE, and external
commands, such as the DOS utility programs (like DEBUG) or one of your
own programs. The command interpreter acts by executing the required
routines for internal commands or by searching for the requested
programs on disk and loading them into memory. The whole subject of the
COMMAND.COM file and how it works is intriguing and well worth
investigating──as are the other DOS programs. We recommend you read the
DOS Technical Reference Manual or Inside the IBM PC for additional
information.
──────────────────────────────────────────────────────────────────────────
Software interrupts incorporated into the PC design are part of the ROM
BIOS programs. ROM BIOS routines invoked by these interrupts cannot be
changed, but the vectors that point to them can be changed to point to
different routines. Reserved interrupt numbers are 10H through 1FH
(decimal 16 through 31) and 40H through 5FH (decimal 64 through 95).
DOS interrupts are always available when DOS is in use. Many programs and
programming languages use the services provided by DOS through the DOS
interrupts to handle basic operations, especially disk I/O. DOS interrupt
numbers are 20H through 3FH (decimal 32 through 63).
BASIC interrupts are assigned by BASIC itself and are always available
when BASIC is in use. The reserved interrupt numbers are 80H through F0H
(decimal 128 through 240).
General-use interrupts are available for temporary use in your programs.
The reserved interrupt numbers are 60H through 66H (decimal 96 through
102).
Most of the interrupt vectors used by the ROM BIOS, DOS, and BASIC contain
the addresses of interrupt handlers. A few interrupt vectors, however,
point to tables of useful information. For example, interrupt 1EH contains
the address of a table of diskette drive initialization parameters; the
interrupt 1FH vector points to a table of bit patterns used by the ROM
BIOS to display text characters; and interrupts 41H and 46H point to
tables of fixed-disk parameters. These interrupt vectors are used for
convenience, not for interrupts. If you tried to execute interrupt 1EH,
for instance, you'd probably crash the system because the interrupt 1EH
vector points to data, not to executable code.
The interrupt vectors are stored at the lowest memory locations; the very
first location in memory contains the vector for interrupt number 00H, and
so on. Because each vector is two words in length, you can find a
particular interrupt's location in memory by multiplying its interrupt
number by 4. For example, the vector for interrupt 05H, the print-screen
service interrupt, would be at byte offset 20 (5 x 4 = 20); that is, at
address 0000:0014H. You can examine the interrupt vectors by using DEBUG.
For example, you could examine the interrupt 05H vector with DEBUG in the
following way:
DEBUG
D 0000:0014 L 4
DEBUG will show 4 bytes, in hex, like this:
54 FF 00 F0
Converted to a segment and offset address and allowing for "back-words"
storage, the interrupt vector for the entry point in ROM of the
print-screen service routine (interrupt 05H) is F000:FF54H. (Of course,
this address may be different in different members of the PC and PS/2
families.) The same DEBUG instruction finds any other interrupt vector
just as easily.
Figure 3-1 lists the main interrupts and their vector locations. These
are the interrupts that programmers will probably find most useful.
Details are available for most of these interrupts in Chapters 8 through
18. Interrupts that are not mentioned in this list are, for the most part,
reserved for future development by IBM.
╓┌─┌───────────┌────────────┌────────────────────────────────────────────────╖
Interrupt Offset in Use
Hex Dec Segment
0000
──────────────────────────────────────────────────────────────────────────
00H 0 0000 Generated by CPU when division by zero is
attempted
01H 1 0004 Used to single-step through programs (as with
DEBUG)
02H 2 0008 Nonmaskable interrupt (NMI)
03H 3 000C Used to set break-points in programs (as with
DEBUG)
04H 4 0010 Generated when arithmetic result overflows
05H 5 0014 Invokes print-screen service routine in ROM BIOS
08H 8 0020 Generated by hardware clock tick
Interrupt Offset in Use
Hex Dec Segment
0000
──────────────────────────────────────────────────────────────────────────
08H 8 0020 Generated by hardware clock tick
09H 9 0024 Generated by keyboard action
0EH 14 0038 Signals diskette attention (e.g. to signal
completion)
0FH 15 003C Used in printer control
10H 16 0040 Invokes video display services in ROM BIOS
11H 17 0044 Invokes equipment-list service in ROM BIOS
12H 18 0048 Invokes memory-size service in ROM BIOS
13H 19 004C Invokes disk services in ROM BIOS
14H 20 0050 Invokes communications services in ROM BIOS
15H 21 0054 Invokes system services in ROM BIOS
16H 22 0058 Invokes standard keyboard services in ROM BIOS
17H 23 005C Invokes printer services in ROM BIOS
18H 24 0060 Activates ROM BASIC language
19H 25 0064 Invokes bootstrap start-up routine in ROM BIOS
1AH 26 0068 Invokes time and date services in ROM BIOS
1BH 27 006C Interrupt by ROM BIOS for Ctrl-Break
Interrupt Offset in Use
Hex Dec Segment
0000
──────────────────────────────────────────────────────────────────────────
1BH 27 006C Interrupt by ROM BIOS for Ctrl-Break
1CH 28 0070 Interrupt generated at each clock tick
1DH 29 0074 Points to table of video control parameters
1EH 30 0078 Points to diskette drive parameter table
1FH 31 007C Points to CGA video graphics characters
20H 32 0080 Invokes program-terminate service in DOS
21H 33 0084 Invokes all function-call services in DOS
22H 34 0088 Address of DOS program-terminate routine
23H 35 008C Address of DOS keyboard-break handler
24H 36 0090 Address of DOS critical-error handler
25H 37 0094 Invokes absolute disk-read service in DOS
26H 38 0098 Invokes absolute disk-write service in DOS
27H 39 009C Ends program, but keeps it in memory under DOS
2FH 47 00BC DOS Multiplex interrupt
41H 65 0104 Points to fixed-disk drive parameter table
43H 67 010C Points to video graphics characters (EGA, PS/2s)
67H 103 019CH Invokes LIM Expanded Memory Manager
Interrupt Offset in Use
Hex Dec Segment
0000
──────────────────────────────────────────────────────────────────────────
67H 103 019CH Invokes LIM Expanded Memory Manager
──────────────────────────────────────────────────────────────────────────
Figure 3-1. Important interrupts used in the IBM personal computer
family.
Changing Interrupt Vectors
The main programming interest in interrupt vectors is not to read them but
to change them to point to a new interrupt-handling routine. To do this,
you must write a routine that performs a different function than the
standard ROM BIOS or DOS interrupt handlers perform, store the routine in
RAM, and then assign the routine's address to an existing interrupt in the
table.
A vector can be changed byte by byte on an assembly-language level, or by
using a programming-language instruction like the POKE statement in BASIC.
In some cases, there may be a danger of an interrupt occurring in the
middle of a change to the vector. If you are not concerned about this, go
ahead and use the POKE method. Otherwise, there are two ways to change
a vector while minimizing the likelihood of interrupts: by suspending
interrupts during the process, or by using a DOS interrupt specially
designed to change vectors.
The first method requires that you use assembly language to suspend
interrupts while you change the interrupt vector. You can use the clear
interrupts instruction (CLI), which suspends all interrupts until a
subsequent STI (set interrupts) instruction is executed. By temporarily
disabling interrupts with CLI you ensure that no interrupts can occur
while you update an interrupt vector.
──────────────────────────────────────────────────────────────────────────
NOTE:
CLI does not disable the nonmaskable interrupt (NMI). If your
application is one of the rare ones that needs to supply its own NMI
handler, the program should temporarily disable the NMI while changing
the NMI interrupt vector. (See PC or PS/2 technical reference manuals
for details.)
──────────────────────────────────────────────────────────────────────────
The following example demonstrates how to update an interrupt vector with
interrupts temporarily disabled. This example uses two MOV instructions to
copy the segment and offset address of an interrupt handler from DS:DX
into interrupt vector 60H:
xor ax,ax ; zero segment register ES
mov es,ax
cli ; disable interrupts
mov word ptr es:[180h],dx ; update vector offset
mov word ptr es:[182h],ds ; update vector segment
sti ; enable interrupts
The second method of updating an interrupt vector is to let DOS do it for
you using DOS interrupt 21H, service 25H (decimal 37), which was designed
for this purpose. There are two very important advantages to letting DOS
set interrupts for you. One advantage is that DOS takes on the task of
putting the vector into place in the safest possible way. The other
advantage is more far-reaching. When you use DOS service 25H to change an
interrupt vector, you allow DOS to track changes to any interrupt vectors
it may itself be using. This is particularly important for programs that
might run in the DOS "compatibility box" in OS/2. Using a DOS service to
set an interrupt vector instead of setting it yourself is only one of many
ways that you can reduce the risk that a program will be incompatible with
new machines or new operating-system environments.
The following example demonstrates how to use interrupt 21H, service 25H
to update the vector for interrupt 60H from values stored in a memory
variable:
mov dx,seg Int60Handler ; copy new segment to DS
mov ds,dx
mov dx,offset Int60Handler ; store offset address in DX
mov al,60h ; interrupt number
mov ah,25h ; DOS set-interrupt function number
int 21h ; DOS function-call interrupt
This example shows, in the simplest possible way, how to use the DOS
service. However, it glosses over an important and subtle difficulty: You
have to load one of the addresses that you're passing to DOS into the DS
(data segment) register──which effectively blocks normal access to data
through the DS register. Getting around that problem requires you to
preserve the contents of the DS register. Here is one way this can be
done. In this example, taken from the Norton Utilities programs, the
interrupt 09H vector is updated with the address of a special interrupt
handler:
push ds ; save current data segment
mov dx,offset PGROUP:XXX ; store handler's offset in DX
push cs ; move handler's code segment...
pop ds ; ...into DS
mov ah,25h ; request set-interrupt function
mov al,9 ; change interrupt number 9
int 21h ; DOS function-call interrupt
pop ds ; restore original data segment
Key Low-Memory Addresses
Much of the operation of the PCs and PS/2s is controlled by data stored in
low-memory locations, particularly in the two adjacent 256-byte areas
beginning at segments 40H and 50H (addresses 0040:0000H and 0050:0000H).
The ROM BIOS uses the 256 bytes from 0040:0000H through 0040:00FFH as a
data area for its keyboard, video, disk, printer, and communications
routines. The 256 bytes between 0050:0000H and 0050:00FFH are used
primarily by BASIC, although a few ROM BIOS status variables are located
there as well.
Data is loaded into these areas by the BIOS during the start-up process.
Although the control data is supposed to be the private reserve of the
BIOS, DOS, and BASIC, your programs are allowed to inspect or even change
it. Even if you do not intend to use the information in these control
areas, it is worth studying because it reveals a great deal about what
makes the PC family tick.
The ROM BIOS Data Area
Some memory locations in the BIOS data area are particularly interesting.
Most of them contain data vital to the operation of various ROM BIOS and
DOS service routines. In many instances, your programs can obtain
information stored in these locations by invoking a ROM BIOS interrupt; in
all cases, they can access the information directly. You can easily check
out the values at these locations on your own computer, using either DEBUG
or BASIC. To use DEBUG, type a command of this form:
DEBUG
D XXXX:YYYY L 1
XXXX represents the segment part of address you want to examine. (This
would be either 0040H or 0050H, depending on the data area that interests
you.) YYYY represents the offset part of the address. The L 1 tells DEBUG
to display one byte. To see two or more bytes, type the number of bytes
(in hex) you want to see after the L instruction. For example, the BIOS
keeps track of the current video mode number in the byte at 0040:0049H. To
inspect this byte with DEBUG, you would type
DEBUG
D 0040:0049 L 1
To display the data with BASIC, use a program of the following form,
making the necessary substitutions for segment (&H0040 or &H0050),
number.of.bytes, and offset (the offset part of the address you want to
inspect):
10 DEF SEG = segment
20 FOR I = 0 TO number.of.bytes - 1
30 VALUE = PEEK(offset + I)
40 IF VALUE < 16 THEN PRINT "0"; ' needed for leading zero
50 PRINT HEX$ (VALUE);" ";
60 NEXT I
The following pages describe useful low-memory addresses.
0040:0010H (a 2-byte word). This word holds the equipment-list data that
is reported by the equipment-list service, interrupt 11H (decimal 17). The
format of this word, shown in Figure 3-2, was established for the PC and
XT; certain parts may appear in a different format in later models.
0040:0013H (a 2-byte word). This word contains the usable memory size in
KB. BIOS interrupt service 12H (decimal 18) is responsible for reporting
the value in this word.
0040:0017H (2 bytes of keyboard status bits). These bytes are actively
used to control the interpretation of keyboard actions by the ROM BIOS
routines. Changing these bytes actually changes the meaning of keystrokes.
You can freely change the first byte, at address 0040:0017H, but it is not
a good idea to change the second byte. See pages 137 and 138 for the bit
settings of these 2 bytes.
╓┌─┌──────────────────┌─────────────────┌────────────────────────────────────╖
Bit
F E D C B A 9 8 7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
X X . . . . . . . . . . . . . . Number of printers installed
. . X . . . . . . . . . . . . . (Reserved)
. . . X . . . . . . . . . . . . 1 if game adapter installed
. . . . X X X . . . . . . . . . Number of RS-232 serial ports
. . . . . . . X . . . . . . . . (Reserved)
. . . . . . . . X X . . . . . . +1 = number of diskette drives:
00 = 1 drive; 01 = 2 drives;
10 = 3 drives;
11 = 4 drives (see bit 0)
. . . . . . . . . . X X . . . . Initial video mode:
01 = 40-column color;
10 = 80-column color,
11 = 80-column monochrome;
Bit
F E D C B A 9 8 7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
11 = 80-column monochrome;
00 = none of the above
. . . . . . . . . . . . X X . . For PC with 64 KB motherboard:
Amount of system board RAM
(11 = 64 KB, 10 = 48 KB,
01 = 32 KB, 00 = 16 KB)
For PC/AT: Not used
For PS/2s: Bit 3: Not used;
Bit 2: 1 = pointing device
installed
. . . . . . . . . . . . . . X . 1 if math coprocessor installed
. . . . . . . . . . . . . . . X 1 if any diskette drives present (if
so, see bits 7 and 6)
──────────────────────────────────────────────────────────────────────────
Figure 3-2. The coding of the equipment-list word at address 0040:0010H.
0040:001AH (a 2-byte word). This word points to the current head of the
BIOS keyboard buffer at 0040:001EH, where keystrokes are stored until they
are used.
0040:001CH (a 2-byte word). This word points to the current tail of the
BIOS keyboard buffer.
0040:001EH (32 bytes, used as sixteen 2-byte entries). This keyboard
buffer holds up to 16 keystrokes until they are read via the BIOS services
through interrupt 16H (decimal 22). As this is a circular queue buffer,
two pointers indicate the head and tail. It is not wise to manipulate this
data.
0040:003EH (1 byte). This byte indicates if a diskette drive needs to be
recalibrated before seeking to a track. Bits 0 through 3 correspond to
drives 0 through 3. If a bit is clear, recalibration is needed. Generally,
you will find that a bit is clear if there was any problem with the most
recent use of a drive. For example, the recalibration bit will be clear if
you try to request a directory (DIR) on a drive with no diskette, and then
type A in response to the following display:
Not ready reading drive A
Abort, Retry, Fail?
0040:003FH (1 byte). This byte returns the diskette motor status. Bits 0
through 3 correspond to drives 0 through 3. If the bit is set, the
diskette motor is running.
0040:0040H (1 byte). This byte is used by the ROM BIOS to ensure that the
diskette drive motor is turned off. The value in this byte is decremented
with every tick of the system clock (that is, about 18.2 times per
second). When the value reaches 0, the BIOS turns off the drive motor.
0040:0041H (1 byte). This byte contains the status code reported by the
ROM BIOS after the most recent diskette operation. (See Figure 3-3.)
0040:0042H (7 bytes). These 7 bytes hold diskette controller status
information.
Beginning at 0040:0049H is a 30-byte area used for video control. This is
the first of two areas in segment 40H that the ROM BIOS uses to track
critical video information.
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H No error
01H Invalid diskette command requested
02H Address mark on diskette not found
03H Write-protect error
04H Sector not found; diskette damaged or not formatted
06H Diskette change line active
08H DMA diskette error
09H Attempt to DMA across 64 KB boundary
0CH Media type not found
10H Cyclical redundancy check (CRC) error in data
20H Diskette controller failed
40H Seek operation failed
80H Diskette timed out (drive not ready)
──────────────────────────────────────────────────────────────────────────
Figure 3-3. Diskette status codes in the ROM BIOS data area at
0040:0041H.
Although programs can safely inspect any of this data, you should modify
the data only when you bypass the ROM BIOS video services and program the
video hardware directly. In such cases, you should update the video
control data to reflect the true status of the video hardware.
0040:0049H (1 byte). The value in this byte specifies the current video
mode. (See Figure 3-4.) This is the same video-mode number used in the
ROM BIOS video services. (See Chapter 9 for more on these services and
page 72 for general information concerning video modes.)
We've already shown how to use DEBUG to determine the current video mode
by inspecting the byte at 0040:0049H. BASIC programs can use the following
instructions to read this byte and determine the video mode:
DEF SEG = &H40 ' set BASIC data segment to 40H
VIDEO.MODE = PEEK(&H49) ' look at location 0040:0049H
0040:004AH (a 2-byte word). This word indicates the number of characters
that can be displayed in each row of text on the screen.
0040:004CH (a 2-byte word). This word indicates the number of bytes
required to represent one screenful of video data.
Number Description
──────────────────────────────────────────────────────────────────────────
00H 40 x 25 16-color text
(CGA composite color burst disabled)
01H 40 x 25 16-color text
02H 80 x 25 16-color text
(CGA composite color burst disabled)
03H 80 x 25 16-color text
04H 320 x 200 4-color graphics
05H 320 x 200 4-color graphics
(CGA composite color burst disabled)
06H 640 x 200 2-color graphics
07H 80 x 25 monochrome text
0DH 320 x 200 16-color graphics
0EH 640 x 200 16-color graphics
0FH 640 x 350 monochrome graphics
10H 640 x 350 16-color graphics
11H 640 x 480 2-color graphics
12H 640 x 480 16-color graphics
13H 320 x 200 256-color graphics
──────────────────────────────────────────────────────────────────────────
Figure 3-4. BIOS video mode numbers stored at address 0040:0049H.
0040:004EH (a 2-byte word). This word contains the starting byte offset
into video display memory of the current display page. In effect, this
address indicates which page is in use by giving the offset to that page.
0040:0050H (eight 2-byte words). These words give the cursor locations for
eight separate display pages, beginning with page 0. The first byte of
each word gives the character column and the second byte gives the row.
0040:0060H (a 2-byte word). These 2 bytes indicate the size of the cursor,
based on the range of cursor scan lines. The first byte gives the ending
scan line, the second byte the starting scan line.
0040:0062H (1 byte). This byte holds the current display page number.
0040:0063H (a 2-byte word). This word stores the port address of the
hardware CRT controller chip.
0040:0065H (1 byte). This byte contains the current setting of the CRT
mode register on the Monochrome Display Adapter and the Color Graphics
Adapter.
0040:0066H (1 byte). This byte contains the current setting of the Color
Graphics Adapter's CRT color register. This byte ends the first block of
ROM BIOS video control data.
0040:0067H (5 bytes). The original IBM PC BIOS used the 5 bytes starting
at 0040:0067H for cassette tape control. In PS/2 models 50, 60, and 80,
which don't support a cassette interface, the 4 bytes at 0040:0067H can
contain the address of a system reset routine that overrides the usual
BIOS startup code. (See the BIOS technical reference manual for details.)
0040:006CH (4 bytes stored as one 4-byte number). This area is used as a
master clock count, which is incremented once for each timer tick. It is
treated as if it began counting from 0 at midnight. When the count reaches
the equivalent of 24 hours, the ROM BIOS resets the count to 0 and sets
the byte at 0040:0070H to 1. DOS or BASIC calculates the current time from
this value and sets the time by putting the appropriate count in this
field.
0040:0070H (1 byte). This byte indicates that a clock rollover has
occurred. When the clock count passes midnight (and is reset to 0), the
ROM BIOS sets this byte to 1, which means that the date should be
incremented.
──────────────────────────────────────────────────────────────────────────
NOTE:
This byte is set to 1 at midnight and is not incremented. There is no
indication if two midnights pass before the clock is read.
──────────────────────────────────────────────────────────────────────────
0040:0071H (1 byte). The ROM BIOS sets bit 7 of this byte to indicate that
the Ctrl-Break key combination was pressed.
0040:0072H (a 2-byte word). This word is set to 1234H after the initial
power-up memory check. When a warm boot is instigated from the keyboard
(via Ctrl-Alt-Del), the memory check will be skipped if this location is
already set to 1234H.
0040:0074H (4 bytes). These 4 bytes are used by various members of the PC
family for diskette and fixed-disk drive control. See the IBM BIOS
Interface Technical Reference Manual for details.
0040:0078H (4 bytes). These bytes control time-out values for the parallel
printers. (In the PS/2, only the first 3 bytes are used for this purpose.)
0040:007CH (4 bytes). These bytes contain time-out values for up to four
RS-232 serial ports.
0040:0080H (a 2-byte word). This word points to the start of the keyboard
buffer area.
0040:0082H (a 2-byte word). This word points to the end of the keyboard
buffer area.
The next 7 bytes are used by the ROM BIOS in the EGA and PS/2s for video
control:
0040:0084H (1 byte). The value of this byte is one less than the number of
character rows displayed on the screen. The BIOS can refer to this value
to determine how many character rows of data to erase when the screen is
cleared or how many rows to print when Shift-PrtSc is pressed.
0040:0085H (2 bytes). This word indicates the height, in scan lines, of
characters on the screen.
0040:0087H (4 bytes). These 4 bytes are used by the BIOS video support
routines to indicate the amount of video RAM available, the initial
settings of the EGA configuration switches, and other miscellaneous video
status information.
0040:008BH (11 bytes). The ROM BIOS uses this data area for control and
status information regarding the diskette and fixed-disk drives.
0040:0098H (9 bytes). This data area is used by the PC/AT and PS/2 BIOS to
control certain functions of the real-time clock.
0040:00A8H (4 bytes). In the EGA and PS/2 BIOS, these bytes contain the
segmented address of a table of video parameters and overrides for default
ROM BIOS video configuration values. The actual contents of the table
vary, depending on which video hardware you are using. The IBM ROM BIOS
Interface Technical Reference Manual describes this table in detail.
0050:0000H (1 byte). This byte is used by the ROM BIOS to indicate the
status of a print-screen operation. Three possible hex values are stored
in this location:
──────────────────────────────────────────────────────────────────────────
00H Indicates OK status
01H Indicates a print-screen operation is currently in progress
FFH Indicates an error occurred during a print-screen operation
──────────────────────────────────────────────────────────────────────────
0050:0004H (1 byte). This byte is used by DOS when a single-diskette
system mimics a two-diskette system. The value indicates whether the one
physical drive is acting as drive A or drive B. These values are used:
──────────────────────────────────────────────────────────────────────────
00H Acting as drive A
01H Acting as drive B
──────────────────────────────────────────────────────────────────────────
0050:0010H (a 2-byte word). This area is used by ROM BASIC to hold its
default data segment (DS) value.
BASIC lets you set your own data segment value with the DEF SEG = value
statement. (The offset into the segment is specified by the PEEK or POKE
function.) You can also reset the data segment to its default setting by
using the DEF SEG statement without a value. Although BASIC does not give
you a simple way to find the default value stored in this location, you
can get it by using this little routine:
DEF SEG = &H50
DATA.SEGMENT = PEEK(&H11) * 256 + PEEK(&H10)
──────────────────────────────────────────────────────────────────────────
NOTE:
BASIC administers its own internal data based on the default data
segment value. Attempting to change this value is likely to sabotage
BASIC's operation.
──────────────────────────────────────────────────────────────────────────
0050:0012H (4 bytes). In some versions of ROM BASIC, these 4 bytes contain
the segment and offset address of BASIC's clock-tick interrupt handler.
──────────────────────────────────────────────────────────────────────────
NOTE:
In order to perform better, BASIC runs the system clock at four times
the standard rate, so BASIC must replace the ROM BIOS clock interrupt
routine with its own. The standard BIOS interrupt routine is invoked by
BASIC at the normal rate; that is, once for every four fast ticks.
There's more about this on page 146.
──────────────────────────────────────────────────────────────────────────
0050:0016H (4 bytes). This area contains the address of ROM BASIC's
break-key handling routine.
0050:001AH (4 bytes). This area contains the address of ROM BASIC's
diskette error-handling routine.
The Intra-Application Communications Area
In the PC/XT/AT family, the 16 bytes starting at 0040:00F0H are reserved
as an intra-application communication area (ICA). This data area provides
an area of RAM at a known address that an application can use for sharing
data among separate program modules. In the PS/2 BIOS, however, the ICA is
no longer documented.
Few applications actually use the ICA because the amount of RAM is so
small and because the data within the ICA can be unexpectedly modified
when more than one program uses it. If you do write a program that uses
the ICA, we recommend that you include a checksum and also a signature so
that you can ensure that the data in the ICA is yours and that it has not
been changed by another program.
──────────────────────────────────────────────────────────────────────────
WARNING:
The ICA is definitely located in the 16 bytes from 0040:00F0H through
0040:00FFH. A typographic error in some editions of the IBM PC Technical
Reference Manual places it at 0050:0000H through 0050:00FFH. This is
incorrect.
──────────────────────────────────────────────────────────────────────────
The BIOS Extended Data Area
The PS/2 ROM BIOS start-up routines allocate an additional area of RAM for
their own use. The BIOS routines use this extended data area for transient
data storage. For example, the BIOS routines that support the
pointing-device (mouse) controller hardware use part of the extended data
area for temporary storage.
You can determine the starting address of the extended data area by using
a system service available through ROM BIOS interrupt 15H. (See Chapter
12.) The first byte in the extended data area contains the size of the
data area in KB.
The ROM Version and Machine-ID Markers
Because the BIOS programs are fixed in memory, they can't be easily
changed when additions or corrections are needed. This means that ROM
programs must be tested very carefully before they are frozen onto memory
chips. Although there is a good chance for serious errors to exist in a
system's ROM programs, IBM has a fine track record; so far, only small and
relatively unimportant errors have been found in the PC family's ROM
programs, and IBM has done well to correct errors by revising the BIOS.
The different versions of ROM software could present a small challenge to
programmers who discover that the differences affect the operating
characteristics of their programs. But an even greater challenge for
programmers is that the PC, XT, AT, and PS/2s each have a slightly
different set of ROM BIOS routines.
To ensure that programs can work with the appropriate ROM programs and the
right computer, IBM has supplied two identifying markers that are
permanently available at the end of memory in the system ROM. One marker
identifies the ROM release date, which can be used to identify the BIOS
version, and the other gives the machine model. These markers are always
present in IBM's own machines and you'll also find them supplied by the
manufacturers of a few PC compatibles. The following paragraphs describe
these markers in detail.
The ROM release date can be found in an 8-byte storage area from
F000:FFF5H to F000:FFFCH (2 bytes before the machine ID byte). It consists
of ASCII characters in the common American date format; for example,
06/01/83 stands for June 1, 1983. This release marker is a common feature
of the IBM personal computers, but is present in only a few IBM
compatibles. For example, the Compaq Portable I does not have it, but the
Panasonic Senior Partner does.
You can look at the release date with DEBUG by using the following
command:
DEBUG
D F000:FFF5 L 8
Or you can let your program look at the bytes using this technique:
10 DEF SEG = &HF000
20 FOR I = 0 TO 7
30 PRINT CHR$(PEEK(&HFFF5 + I));
40 NEXT
50 END
The model ID is a byte located at F000:FFFEH. This byte identifies which
model of PC or PS/2 you are using. (See Figure 3-5.) In addition, a ROM
BIOS service in the PC/AT and PS/2s returns more detailed identification
information, including the submodel byte listed in the figure. (See
Chapter 12.)
╓┌─┌──────────────────┌────────┌────────┌────────┌─────────┌─────────────────╖
Machine Date Model Submodel BIOS Revision Notes
──────────────────────────────────────────────────────────────────────────
PC 04/24/81 FFH ☼ 00
10/19/81 FFH ☼ 01 Some BIOS bugs
fixed
10/27/82 FFH ☼ 02 Upgrade of PC BIOS
to XT level
PC/XT 11/08/82 FEH ☼ 00
01/10/86 FBH 00 01 256/640 KB system
board
05/09/86 FBH 00 02
PC/AT 01/10/84 FCH ☼ 00 6 MHz 80286
06/10/85 FCH 00 01
11/15/85 FCH 01 00 8 MHz 80286
PS/2 Model 25 06/26/87 FAH 01 00
PS/2 Model 30 09/02/86 FAH 00 00
12/12/86 FAH 00 01
PS/2 Model 50 02/13/87 FCH 04 00
PS/2 Model 60 02/13/87 FCH 05 00
Machine Date Model Submodel BIOS Revision Notes
──────────────────────────────────────────────────────────────────────────
PS/2 Model 60 02/13/87 FCH 05 00
PS/2 Model 80 03/30/87 F8H 00 00 16 MHz 80386
PS/2 Model 80 10/07/87 F8H 01 00 20 MHz 80386
PCjr 06/01/83 FDH ☼ 00
PC Convertible 09/13/85 F9H 00 00
PC/XT Model 04/21/86 FCH 02 00
286
──────────────────────────────────────────────────────────────────────────
Figure 3-5. Machine and ROM BIOS version identification.
It is possible that IBM-compatible computers can be identified in the same
way, but we do not know of any reliable published information. You may
need to rely on improvised methods to identify non-IBM compatibles.
You can examine the machine ID byte with DEBUG by using the following
command:
DEBUG
D F000:FFFE L 1
A BASIC program can inspect this byte using techniques such as this:
10 DEF SEG = &HF000
20 MODEL = PEEK(&HFFFE)
30 IF MODEL < &HF8 THEN PRINT "I'm not an IBM computer" : STOP
40 ON (MODEL - &HF7) GOTO 100,110,120,130,140,150,160,170
100 PRINT "I'm a PS/2 Model 80" : STOP
110 PRINT "I'm a PC convertible" : STOP
120 PRINT "I'm a PS/2 Model 30" : STOP
130 PRINT "I'm a PC/XT" : STOP
140 PRINT "I'm an 80286-based machine (PC/AT, PS/2 Model 50 or 60)" :
STOP
150 PRINT "I'm a PCjr" : STOP
160 PRINT "I'm a PC/XT" : STOP
170 PRINT "I'm a PC" : STOP
The ROM BASIC
Now we move on to the third element of ROM: the ROM BASIC. The ROM BASIC
acts in two ways. First, it provides the core of the BASIC language, which
includes most of the commands and the underlying foundation──such as
memory management──that BASIC uses. The disk versions of interpreted
BASIC, which are found in the program files BASIC.COM and BASICA.COM, are
essentially supplements to ROM BASIC, and they rely on ROM BASIC to get
much of their work done. The second role of ROM BASIC is to provide what
IBM calls "cassette" BASIC──the BASIC that is activated when you start up
your computer without a disk.
Whenever you use any of the interpreted, disk-based BASICs, the ROM BASIC
programs are also used──although there's nothing to make you aware of it.
On the other hand, compiled BASIC programs don't make use of the ROM
BASIC.
The ROM Extensions
The fourth element of the ROM has more to do with the PC's design than
with the actual contents of its memory. The PC was designed to allow for
installable extensions to the built-in software in ROM. The additional ROM
is usually located on a plug-in adapter such as the Enhanced Graphics
Adapter or a fixed-disk controller card. Computers in the PC/XT/AT family
also have empty sockets on their system boards to accommodate additional
ROM chips. Because the original ROM BIOS could not include support
programs for future hardware, ROM extensions are obviously a necessary and
helpful addition.
Several memory areas are reserved for ROM extensions. Addresses C000:0000H
through C000:7FFFH are reserved for video adapter ROM. The area between
C800:0000H and D000:FFFFH can be used by nonvideo adapters. (For example,
the IBM XT fixed-disk adapter occupies addresses starting at C800:0000H.)
Finally, ROM extensions on chips placed onto the system board of a PC, XT,
or AT occupy the address range E000:0000H through E000:FFFFH. In the PS/2
models 50, 60, and 80, you cannot add ROM chips to the system board. The
system ROM in these computers occupies the entire address range between
E000:0000H and F000:FFFFH.
Comments
As the PC family has evolved, the amount and complexity of the ROM
software has increased to accommodate the greater sophistication of the
computer hardware. The source code listings in the PC, XT, and AT
technical reference manuals consist of tens of thousands of
assembly-language instructions. Despite the size of the ROM BIOS, a browse
through the source code can be fun and enlightening.
We have made every effort in this book to point out when and how to use
the ROM BIOS routines. We recommend that you read Chapters 8 through 13
before you begin your own exploration of the ROM BIOS.
────────────────────────────────────────────────────────────────────────────
Chapter 4 Video Basics
The Video Subsystems
Memory and the Video Subsystems
Creating the Screen Image
The Video Display Modes
Video Mode Control
Display Resolution
The Use of Color
Color-Suppressed Modes
Color in Text and Graphics Modes
Inside the Display Memory
Display Pages in Text Modes
Display Pages in Graphics Modes
Displaying Characters in Text and Graphics Modes
Controlling the Video Display
Direct Hardware Control
Compatibility Considerations
To many people, the video display is the computer. Programs are often
judged by their display quality and visual design alone. In this chapter,
you'll see what kinds of video display output the IBM PC family can
produce. More importantly, we'll describe how to manipulate the video
displays to get the effects you want.
The Video Subsystems
Every PC and PS/2 has a video subsystem responsible for producing the
image that appears on the screen. At the heart of the video subsystem is
the special-purpose circuitry that must be programmed to generate the
electrical signals that control the video display. Most members of the
PC/XT/AT family require you to install a display adapter, a special video
circuit board that plugs into one of the computer's expansion slots. On
the other hand, all PS/2s are equipped with built-in video circuitry and,
therefore, require no display adapter.
The video circuitry consists of a group of interrelated components that
control signal timing, colors, and the generation of text characters. All
IBM video subsystems have a video buffer, a block of dedicated memory that
holds the text or graphics information displayed on the screen. The video
subsystem performs the unique task of translating the raw data in the
video buffer into the signals that drive the video display.
The various video subsystems used in PCs and PS/2s all evolved from the
two video adapters originally released by IBM for the PC: the Monochrome
Display Adapter (MDA) and the Color Graphics Adapter (CGA). IBM later
released its Enhanced Graphics Adapter (EGA), a more powerful successor to
the MDA and CGA.
When the PS/2s appeared, IBM introduced two more video subsystems: the
Multi-Color Graphics Array (MCGA), built into the PS/2 models 25 and 30,
and the Video Graphics Array (VGA), built into the PS/2 models 50, 60, and
80. At the same time the PS/2s appeared, IBM introduced a VGA adapter that
can be used in the PC/XT/AT family as well as in the PS/2 Model 30.
We'll be discussing all five of these IBM subsystems──MDA, CGA, EGA, MCGA,
and VGA──in this chapter. Although clear differences in hardware design
exist between the various video subsystems, their strong family
resemblance should encourage you to consider what they have in common
before worrying about the differences between them.
Most of the five video subsystems can be programmed into two fundamentally
different modes, called text mode and graphics mode by IBM. (The lone
exception is the MDA, which operates only in text mode.) In text mode you
can display only text characters, though many of these characters are
suitable for producing simple line drawings. (See Appendix C for more on
characters.) Graphics mode is mainly used for complex drawings but you can
also use it to draw text characters in a variety of shapes and sizes.
The CGA can operate in both text and graphics modes to produce drawings
and characters in several formats and colors. By contrast, the MDA can
operate only in text mode, using a stored set of ASCII alphanumeric and
graphics characters and displaying them in only one color. The MDA works
only with the IBM Monochrome Monitor (or its equivalent) while the CGA
must be connected to either a direct-drive or a composite color monitor.
(See page 74 for more on monitors.) Many business and professional users
prefer a monochrome display to a color display because a monochrome screen
is easier on the eyes and less expensive than an equivalent color display.
But in choosing monochrome, they sacrifice color, a valuable asset for any
computer display.
The MDA's most obvious drawback is its inability to display images in
graphics mode. For this reason, PC/XT/AT users who prefer a monochrome
display, yet need to view graphics, must turn to an EGA or to a non-IBM
adapter like the Hercules Graphics Card, which emulates the MDA's text
mode but supports a monochrome graphics mode as well.
Roughly two-thirds of all PCs are equipped with the standard MDA and
therefore have no graphics or color capability. While there are real
advantages to using color and graphics, most PCs get along nicely without
either. Although the clear trend is toward higher-performance video
subsystems that can display graphics as well as text, keep in mind as you
plan computer applications that many PCs display text only.
The best way to understand the video capabilities of the PCs and PS/2s is
to cover the features that their various video subsystems have in common.
As we go along, we'll point out the differences and improvements that
distinguish the newer and more complicated subsystems (EGA, MCGA, and VGA)
from their predecessors (MDA and CGA).
Memory and the Video Subsystems
The video buffer memory is connected directly to the display circuitry so
that the data in the video buffer can be repeatedly read out of the buffer
and displayed. However, the video buffer is also logically (to the CPU) a
part of the computer's main memory address space. A full 128 KB of the
memory address space is set aside for use as video buffers, at addresses
A000:0000H through B000:FFFFH, but the two original display adapters use
only two small parts of this memory area. The Monochrome Display Adapter
(MDA) provides 4 KB of display memory located at segment B000H. The
original CGA provides 16 KB of display memory located at segment B800H.
With the other IBM video subsystems, the address at which video memory is
located isn't fixed──it depends on how the subsystem is configured. For
example, when an EGA is used with a monochrome display, its text-mode
video buffer is placed at B000H, just as with an MDA. When an EGA is
attached to a color display, its video buffer can be addressed at B800H.
And when you use an EGA in non-CGA graphics modes, the starting buffer
address is A000H. Like the EGA, the MCGA and the VGA also support this
chameleon-like method of buffer addressing.
Creating the Screen Image
You can describe the screen display created by IBM video subsystems as a
memory-mapped display, because each address in the display memory
corresponds to a specific location on the screen. (See Figure 4-1.) The
display circuitry repeatedly reads information from memory and places it
on the screen. The information can be changed as quickly as the computer
can write new information from your programs into memory. The display
circuitry translates the stream of bits it receives from memory into
bursts of light at particular locations on the screen.
┌─────────────────────────────────────────┐
│ ┌─────────────────────────────────────┐ │
│ │ •• ••• • •••• •••• ••• Pixels or │ │
│ │ characters │ │
│ │ on screen │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
║ ║
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬
│▓│▓│ │▓│▓│▓│ │ │▓│ │▓│▓│▓│▓│ │▓│▓│▓│▓│ │▓│▓│▓│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴
Successive locations in RAM
Figure 4-1. The memory-mapped display.
These dots of light are called pixels and are produced by an electron beam
striking the phosphorescent surface of the CRT. The electron beam is
produced by an electron gun that scans the screen line by line. As the gun
moves across and down the screen in a fixed path called a raster scan, the
video subsystem generates video control signals that turn the beam on and
off, matching the pattern of the bits in memory.
The video circuitry refreshes the screen between 50 and 70 times a second
(depending on the video mode), making the changing images appear clear and
steady. At the end of each screen-refresh cycle, the electron beam must
move from the bottom right corner to the top left corner of the screen to
begin a new cycle. This movement is called the vertical retrace. During
the retrace, the beam is blanked and no pixels are written to the screen.
The vertical retrace period (about 1.25 milliseconds) is important to
programmers for one main reason, which requires some explanation. The
special dual-ported design of the video memory gives the CPU and the
display-refresh circuitry equal access to the display memory. This allows
the CPU and the display circuitry to access video memory at the same time.
This causes a problem on the Color Graphics Adapter (CGA). If the CPU
happens to read or write to the video buffer at the same time the display
circuitry is copying data out of the buffer to display onscreen, a "snow"
effect may briefly appear on the screen. However, if you instruct the CPU
to access memory only during vertical retrace, when the display circuitry
is not accessing the video buffer, then snow can be eliminated. A program
running on a CGA can test the value of bit 3 in the adapter's I/O port at
3DAH. This bit is set on at the beginning of vertical retrace and then set
off at the end. During this 1.25-millisecond pause, you can have your
programs write as much data as possible to the video display memory. At
the end of the retrace, the display circuitry can write this data to the
screen without snow.
This technique is useful for any application that directly accesses data
in the video buffer in text mode on a CGA. Fortunately, the hardware
design of all other IBM video subsystems avoids this access conflict and
makes this specialized programming technique unnecessary.
The Video Display Modes
Originally, there were eight video modes defined for the IBM personal
computers: seven on the CGA and one on the MDA. The more sophisticated
EGA, MCGA, and VGA introduced several new modes plus variations on the
original eight. As a result, among the five IBM video subsystems are 12
text and graphics modes and, depending how you count them, seven or eight
variations──and that's not counting the extra modes available with non-IBM
video hardware and with defunct IBM systems like the PCjr. There's plenty
of variety when you're working with IBM video subsystems.
Despite the perplexing proliferation of video modes, what is striking
about the different modes is not their differences but their similarities
(Figure 4-2): All video modes are related in resolution and in video
buffer organization to the original MDA and CGA modes.
The MDA's 80-column, 25-row monochrome text mode is supported on the EGA
and VGA. Similarly, the CGA's two text modes (40 x 25 and 80 x 25 16-color
modes) are also supported on the EGA, MCGA, and VGA. Don't let the
redundant mode numbers in Figure 4-2 confuse you: The difference between
mode 0 and mode 1, for example, is that the composite color signal on the
CGA is modified for composite monochrome monitors in mode 0. (See page
74 for more on monitors.) With all other monitors and in all other video
subsystems, modes 0 and 1 are the same, as are modes 2 and 3 and modes 4
and 5.
╓┌─┌────────────┌─────────────┌────────────┌─────────────┌────────────┌──────►
BIOS Mode Number
Hex Dec Type Resolution Colors Video Su
───────────────────────────────────────────────────────────────────────────
00H,01H 0, 1 Text 40 x 25 16 CGA, EGA
02H,03H 2, 3 Text 80 x 25 16 CGA, EGA
04H,05H 4, 5 Graphics 320 x 200 4 CGA, EGA
06H 6 Graphics 640 x 200 2 CGA, EGA
07H 7 Text 80 x 25 Mono MDA, EGA
08H,09H,0AH 8, 9, 10 (PCjr on
0BH,0CH 11, 12 (Used in
BIOS)
0DH 13 Graphics 320 x 200 16 EGA,VGA
BIOS Mode Number
Hex Dec Type Resolution Colors Video Su
───────────────────────────────────────────────────────────────────────────
0DH 13 Graphics 320 x 200 16 EGA,VGA
0EH 14 Graphics 640 x 200 16 EGA,VGA
0FH 15 Graphics 640 x 350 Mono EGA,VGA
10H 16 Graphics 640 x 350 16 EGA,VGA
11H 17 Graphics 640 x 480 2 MCGA,VGA
12H 18 Graphics 640 x 480 16 VGA
13H 19 Graphics 320 x 200 256 MCGA,VGA
───────────────────────────────────────────────────────────────────────────
Figure 4-2. Video modes available on IBM video subsystems.
The evolutionary pattern is the same for graphics modes. The CGA supports
two graphics modes, a 320 x 200 pixel, 4-color mode and a 640 x 200,
2-color mode. These same two modes are supported on the EGA, MCGA, and
VGA. The EGA introduced three new graphics modes with more colors and
better resolution than the original CGA graphics modes: the 320 x 200,
16-color; 640 x 200, 16-color; and 640 x 350, 16-color modes. The EGA also
introduced a 640 x 350 monochrome graphics mode that could be used only
with an MDA-compatible monochrome display.
When the PS/2s appeared, their video subsystems supported the same modes
as did the MDA, CGA, and EGA──but again, a few new graphics modes were
introduced. The MCGA in the PS/2 models 25 and 30 followed the CGA
tradition: It supported all CGA modes, plus new 640 x 480, 2-color and 320
x 200, 256-color graphics modes. The VGA in the other PS/2 models strongly
resembles the EGA. It provides all the EGA's text and graphics modes, the
two new MCGA graphics modes, and one more graphics mode not supported by
the other subsystems──a 640 x 480, 16-color mode.
How do you know which mode to use in a program? Clearly, if broad
compatibility is a concern, the MDA and CGA modes are the least common
denominator. If you need more colors or better graphics resolution than
the CGA modes provide, you can turn to one of the EGA, MCGA, or VGA
graphics modes. Of course, if your program requires an EGA or a VGA to
run, users who have only a CGA will be out of luck.
Many commercial software vendors solve this problem by distributing
installable video output routines along with their products. Before you
can use a package like Microsoft Windows or Lotus 1-2-3, for example, you
must run a special installation program that binds output routines for
your particular video hardware to the software application. This approach
is more work for both the people who write software and the people who use
it, but it is a good way to make applications deliver the best possible
video performance without stumbling over the diversity of video hardware
and video modes.
Video Mode Control
Before we get into the details about resolution and color in video modes,
let's consider how you select which video mode to use. The most efficient
way to set up a video mode is to use assembly language to call the ROM
BIOS. ROM BIOS interrupt 10H (decimal 16), service 00H, provides a way to
select a video mode using the mode numbers listed in Figure 4-2. (See
Chapter 9 for more details on this.)
──────────────────────────────────────────────────────────────────────────
Monitors
The type of video display, or monitor, that might be used has an
important effect on program design. Many monitors cannot produce color
or graphics, and a few produce such a poor quality image that you can
use only the 40-column text display format. The many kinds of monitors
that can be used with the PC family of computers can be broken down into
five basic types.
Direct-drive monochrome monitors. These monitors are designed to work
with the Monochrome Display Adapter (MDA), although you can also use
them with an Enhanced Graphics Adapter (EGA). The green IBM Monochrome
Display is reminiscent of IBM's 3270 series of mainframe computer
terminals; it's no surprise that many business users are comfortable
with the combination of an MDA and a green monochrome display.
Composite monochrome monitors. These monitors are still among the most
widely used and least expensive monitors available. They connect to the
composite video output on the Color Graphics Adapter (CGA) and provide a
fairly clear one-color image (usually green or amber). Don't confuse the
composite monochrome monitor with the direct-drive monochrome monitor.
The composite monochrome monitor can be attached only to the CGA,
whereas the direct-drive monochrome monitor must be used with an MDA or
EGA.
Composite color monitors and TV sets. Composite color monitors use a
single combined signal such as the composite video output of the CGA.
The composite color monitor produces color and graphics but has
limitations: An 80-column display is often unreadable; only certain
color combinations work well; and graphics resolution is low in quality,
so graphics must be kept simple by using low-resolution graphics modes.
Although the standard television set (color or black-and-white) is
technically a composite monitor, it usually produces an even
lower-quality image than the dedicated composite monitor. Text displays
must be in 40-column mode to ensure that the display is readable. TVs
are connected to the composite video output of the CGA, but the
composite signal must be converted by an RF adapter before going into
the TV.
RGB color monitors. The RGB monitors are considered the best of both
worlds. They combine the high-quality text display of the monochrome
monitors with high-resolution graphics and color. RGB stands for
red-green-blue, and RGB monitors are so named because they use separate
red, green, and blue color signals, unlike the composite monitors, which
use only one composite signal. The image and color quality of an RGB
monitor is much better than that available through any screen that
connects to the composite video output.
Variable-frequency monitors. One of the problems created by the
proliferation of different video subsystems is that some subsystems
produce color and timing signals with different frequencies or different
encodings than other subsystems. For example, you cannot use a
PS/2-compatible monitor with a CGA because the color information in the
monitor drive signals is encoded differently by a CGA than it is by a
PS/2 video subsystem (MCGA or VGA).
Monitor manufacturers addressed this problem by designing
variable-frequency RGB monitors that can be used with a wide range of
signal frequencies and with more than one type of color signal encoding.
For example, NEC's MultiSync monitors can adjust to the different signal
frequencies generated by the CGA, the EGA, and the PS/2 video
subsystems. These monitors also have a switch that lets you adapt them
either to the digital color signal encoding used by the CGA and EGA or
to the analog color signals used by the PS/2 subsystems.
Many people use variable-frequency monitors because they anticipate the
need to upgrade their video subsystems at some time in the future, and
they don't want to be stuck with an incompatible monitor.
──────────────────────────────────────────────────────────────────────────
Many programming languages also offer high-level commands that select
video modes for you. For example, BASIC gives you control over the video
modes through the SCREEN statement but refers to them in its own way,
using different mode numbers than the ROM BIOS routines. You can also
control some of the video modes through the DOS MODE command. (See Figure
4-3.)
BIOS Mode Number BASIC Statement to DOS Statement to
Hex Dec Change Mode Change Mode
──────────────────────────────────────────────────────────────────────────
00H 0 SCREEN 0,0: WIDTH 40 MODE BW40
01H 1 SCREEN 0,1:WIDTH 40 MODE CO40
02H 2 SCREEN 0,0:WIDTH 80 MODE BW80
03H 3 SCREEN 0,1:WIDTH 80 MODE CO80
04H 4 SCREEN 1,0 n/a
05H 5 SCREEN 1,1 n/a
06H 6 SCREEN 2 n/a
07H 7 n/a MODE MONO
──────────────────────────────────────────────────────────────────────────
Figure 4-3. The BASIC and DOS commands used to change video modes.
Display Resolution
Video images consist of a large number of closely spaced pixels. The
display resolution is defined by the number of pixel rows, or scan lines,
from top to bottom and the number of pixels from left to right in each
scan line. The horizontal and vertical resolution is limited by the
capabilities of the video monitor as well as the display circuitry inside
the computer. The video modes available on the different subsystems were
carefully designed so that the horizontal and vertical resolution in each
mode is within the limits imposed by the hardware.
The MDA's single text mode has 720 x 350 pixel resolution; that is, the
screen has 350 scan lines, each of which contains 720 pixels. Because 25
rows of 80 characters of text are displayed in this mode, each character
is 9 pixels wide (720 ÷ 80) and 14 pixels high (350 ÷ 25). The CGA's text
modes are a bit lower resolution, because the CGA's pixel resolution is
only 640 x 200. Thus the 25 rows of 80-character text on a CGA consist of
characters that are only 8 pixels wide (640 ÷ 80) and 8 pixels high (200 ÷
25). That's why text looks sharper on an MDA screen than on a CGA.
The trend in the newer IBM video subsystems is to provide better vertical
resolution. For example, the EGA's 80 x 25 text mode has 640 x 350 pixel
resolution, so text characters are 8 x 14 pixels. On the MCGA, the default
80 x 25 text mode has 640 x 400 resolution (8 x 16 characters), and on the
VGA the same text mode has 720 x 400 resolution, so characters are each 9
pixels wide and 16 pixels high. From a program's point of view, the 80 x
25 text mode is the same on the CGA, the MCGA, and the VGA──it's display
mode 3 in all cases──but a user sees much higher resolution when using a
VGA or MCGA than when using one of the older subsystems.
You see the same trend towards better resolution when you examine the
graphics modes available with the newer video subsystems. The VGA's 640 x
480, 16-color mode has more than twice as many pixels on the screen as the
original CGA's 640 x 200 graphics mode. It's ironic that this CGA mode was
known as a "high-resolution" mode when the CGA was new.
The Use of Color
A variety of colors is available in every video mode except of course on a
monochrome display. You may have noticed that among the various modes
there are substantial differences in the number of colors available. In
this section, we will describe the color options for the video modes.
Colors for the video display screens are produced by combinations of four
elements: three color components──red, green, and blue──plus an intensity,
or brightness, component. Text and graphics modes use the same colors and
intensity options, but they combine them in different ways to produce
their colored displays. The text modes, whose basic unit is a character
composed of several pixels, use an entire byte to set the color, the
intensity, and the blinking characteristics of the character and its
background. In graphics modes, each pixel is represented by a group of 1
through 8 bits whose value determines the color and brightness of the
displayed pixel.
In 16-color text and graphics modes, the four basic color and brightness
components can be combined in 16 ways. Colors are specified by a group of
4 bits. Each bit designates whether a particular color component is on or
off. The result is 16 color combinations that correspond to the 16 4-bit
binary numbers. (See Figure 4-4.)
In some video modes, the data in the video buffer consists of 4-bit
attribute values that correspond exactly to the 16 possible color
combinations on the screen. In other video modes, the attribute values do
not directly specify colors. For example, on the EGA, each attribute value
designates one of 16 palette registers, each of which contains a color
value. (See Figure 4-5.) It is the palette color values that determine
the color combinations displayed on the screen.
Intensity Red Green Blue Binary Hex Description
───────────────────────────────────────────────────────────────────────────
0 0 0 0 0000B 00H Black
0 0 0 1 0001B 01H Blue
0 0 1 0 0010B 02H Green
0 0 1 1 0011B 03H Cyan (blue-green
0 1 0 0 0100B 04H Red
0 1 0 1 0101B 05H Magenta
0 1 1 0 0110B 06H Brown (or dark
yellow)
0 1 1 1 0111B 07H Light gray (or
ordinary white)
1 0 0 0 1000B 08H Dark gray (black
many screens)
1 0 0 1 1001B 09H Light blue
1 0 1 0 1010B 0AH Light green
1 0 1 1 1011B 0BH Light cyan
1 1 0 0 1100B 0CH Light red
1 1 0 1 1101B 0DH Light magenta
1 1 1 0 1110B 0EH Yellow (or light
yellow)
1 1 1 1 1111B 0FH Bright white
───────────────────────────────────────────────────────────────────────────
Figure 4-4. Default colors available in 16-color text and graphics modes.
┌──────────────────────────────────── ┌─────────────┐
│ 0110 1010 1010 0111 0101 0101 1101 │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ └─┬┘ ├─────────────┤
│ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ │ ├─────────────┤
│ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ │ ├─────────────┤
│ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ │ ├─────────────┤ ┌──────────
│ └─────────────────►│ Color value ├───►│ ┌────────
│ ├─────────────┤ │ │ ▒
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ │
│ ├─────────────┤ │ │
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ │
├─────────────┤
│▒▒▒▒▒▒▒▒▒▒▒▒▒│
Attribute value in └─────────────┘ Color on
video buffer Pallette registers screen
Figure 4-5. How EGA colors are specified using palette registers. Each
attribute value in the video buffer designates a palette register whose
contents specify a color.
The use of palettes makes it possible to specify one of a broad range of
colors using relatively few bits of data in the video buffer. Each of the
EGA's 16 palette registers, for example, can contain one of 64 different
6-bit color values. In this way, any 2 of 64 different colors can be used
in a 2-color EGA video mode, any 4 out of 64 can be used in a 4-color
mode, and any 16 of 64 can be used in a 16-color mode.
All IBM video subsystems except the MDA can use palettes to display
colors. The CGA has three built-in, 4-color palettes for use in 320 x 200,
4-color mode. The EGA, as we have seen, has a 16-color palette in which
each color can be selected from a set of 64 colors. The MCGA and the VGA,
which can display an even wider range of colors, use a separate
palette-like component, the video digital to analog converter (video DAC),
to send color signals to the screen.
The video DAC contains 256 color registers, each of which contains 6-bit
color values for red, green, and blue. Since there are 64 possible values
for each of the RGB components, each video DAC color register can contain
one of 64 x 64 x 64, or 262,144 different color values. That wide range of
colors can help you display very subtle color shades and contours.
With the MCGA, the video DAC color registers serve much the same purpose
as the palette registers do with the EGA. Attribute values in the video
buffer designate video DAC color registers whose contents specify the
colors that appear on the screen. Unfortunately, only one MCGA video mode
can take full advantage of the video DAC's capabilities: 320 x 200,
256-color mode. Only this video mode uses 8-bit attribute values that can
specify all 256 of the video DAC's color registers. All remaining video
modes use attribute values that have no more than 4 bits, so only the
first 16 video DAC color registers are used.
The VGA gets around this limitation (and complicates matters somewhat) by
using a set of 16 palette registers like the EGA's, as well as a set of
256 video DAC color registers like the MCGA's. An attribute value in the
video buffer selects one of the 16 palette registers, whose contents
select one of the 256 video DAC color registers──whose contents "in turn"
determine the color displayed on the screen. (See Figure 4-6.)
Specifying colors on an EGA, MCGA, or VGA is clearly more complicated than
it is on the CGA. To simplify this process, however, the ROM BIOS loads
the palette registers (on the EGA and VGA) and the video DAC color
registers (on the MCGA and VGA) with color values that exactly match those
available on the CGA. If you use CGA-compatible text and graphics modes on
the newer subsystems and ignore the palette and video DAC registers,
you'll see the same colors you would on a CGA.
┌──────────────────────────────────────
│ 0110 1010 1010 0111 0101 0101 1101
│ └─┬┘ •
│ │ ┌─────────────┐ │ • │
│ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ • │
│ │ ├─────────────┤ ├─────────────┤
│ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ │ ├─────────────┤ ├─────────────┤
│ Attribute value │ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ in video buffer │ ├─────────────┤ ├─────────────┤
│ └──►│ Color ├─┐ │▒▒▒▒▒▒▒▒▒▒▒▒▒│
│ │ register │ │ ├─────────────┤ ┌─────────
│ ├─────────────┤ │ │ RGB color ├──►│ ┌───────
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ └─►│ value │ │ │ ▒
│ ├─────────────┤ ├─────────────┤ │ │
│▒▒▒▒▒▒▒▒▒▒▒▒▒│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ │
├─────────────┤ ├─────────────┤ │ │
│▒▒▒▒▒▒▒▒▒▒▒▒▒│ │▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ │
└─────────────┘ ├─────────────┤
Palette registers │▒▒▒▒▒▒▒▒▒▒▒▒▒│ Color on
├─────────────┤ screen
│ • │
│ • │
•
Video DAC color registers
Figure 4-6. How VGA colors are specified using palette registers and the
video DAC.
For this reason it's usually best to ignore the palette and video DAC
registers when you start developing an application. Once your application
works properly with the CGA-compatible colors, you can add program code
that changes the palette and/or the video DAC colors. The ROM BIOS
provides a complete set of services that let you access the palette and
video DAC registers. Chapter 9 covers these services in detail.
In considering color, read each of the remaining sections, which discuss
important color-related items.
Color-Suppressed Modes
In an effort to make the graphics modes compatible with a wide range of
monitors, both color and monochrome, IBM included a few modes on the Color
Graphics Adapter that do not produce color: color-suppressed modes. There
are three color-suppressed modes: modes 0, 2, and 5. In these modes,
colors are converted into shades of gray, or whatever color the screen
phosphor produces. There are four gray shades in mode 5, and a variety of
shades in modes 0 and 2. CGA's color is suppressed in the composite output
but not in its RGB output. This inconsistency is the result of an
unavoidable technical limitation.
──────────────────────────────────────────────────────────────────────────
NOTE:
For each color-suppressed mode, there is a corresponding color mode, so
modes 0 and 1 correspond to 40-column text, modes 2 and 3 to 80-column
text, and modes 4 and 5 to medium-resolution graphics. The fact that
modes 4 and 5 reverse the pattern of modes 0 and 1 and modes 2 and 3,
where the color-suppressed mode comes first, has led to a complication
in BASIC. The burst parameter of the BASIC SCREEN statement controls
color. The meaning of this parameter is reversed for modes 4 and 5 so
that the statement SCREEN,1 activates color in the text modes (0, 1, 2,
and 3) but suppresses color in the graphics modes (4 and 5). This
inconsistency may have been a programming error at first, but it is now
part of the official definition of the SCREEN statement.
──────────────────────────────────────────────────────────────────────────
Color in Text and Graphics Modes
Text and graphics modes use the same color-decoding circuitry, but differ
in the way they store the color attribute data in the video buffer. In
text modes, no matter what video subsystem you use, the foreground and
background colors of each character are specified by two 4-bit fields in a
single attribute byte. (See Figure 4-7.) Together, the foreground and
background attributes describe all of a character's pixels: All foreground
pixels are displayed with the character's foreground attribute, and all
background pixels assume the background attribute.
Bit
7 6 5 4 3 2 1 0 Use
──────────────────────────────────────────────────────────────────────────
1 . . . . . . . Blinking of foreground character or intensity
component of background color
. 1 . . . . . . Red component of background color
. . 1 . . . . . Green component of background color
. . . 1 . . . . Blue component of background color
. . . . 1 . . . Intensity component of foreground color
. . . . . 1 . . Red component of foreground color
. . . . . . 1 . Green component of foreground color
. . . . . . . 1 Blue component of foreground color
──────────────────────────────────────────────────────────────────────────
Figure 4-7. The coding of the color attribute byte.
In graphics modes, each pixel's attribute is determined by the contents of
a bit field in the video buffer. The size and format of a pixel's bit
field depend on the video mode: The smallest bit fields are only 1 bit
wide (as in 640 x 200, 2-color mode), and the largest bit fields are 8
bits wide (as in 320 x 200, 256-color mode).
The reason for having both text and graphics modes becomes clear if you
think about how much data it takes to describe the pixels on the screen.
In graphics modes, you need between 1 and 8 bits of data in the video
buffer for every pixel you display. In 640 x 350, 16-color mode, for
instance, with 4 bits per pixel, you need 640 x 350 x 4 ÷ 8 (112,000)
bytes to represent one screenful of video data. But if you display 25 rows
of 80 characters in a text mode with the same resolution, you need only 80
x 25 x 2, or 4000, bytes.
The tradeoff is clear: Text modes consume less memory and require less
data manipulation than do graphics modes──but you can manipulate each
pixel independently in graphics modes, as opposed to manipulating entire
characters in text modes.
Setting color in text modes
Let's take a closer look at how you control colors in text modes. (We'll
get back to graphics modes later in this chapter.) In text modes, each
character position on the display screen is controlled by a pair of
adjacent bytes in the video buffer. The first byte contains the ASCII code
for the character that will be displayed. (See Appendix C for a chart of
characters.) The second byte is the character's attribute byte. It
controls how the character will appear, that is, its colors, brightness
(intensity), and blinking.
We've already mentioned two attributes that affect a character's
appearance: color and intensity (brightness). You can assign several other
attributes to text characters, depending on which video subsystem you're
using. With all IBM video subsystems, text characters can blink. On
monochrome-capable subsystems (the MDA, EGA, and VGA), characters can also
be underlined. Also, on some non-IBM subsystems like the Hercules Graphics
Card Plus, characters can have attributes such as overstrike and boldface.
In all cases, you assign these alternate attributes by using the same
4-bit attributes that specify color. A case in point is the blinking
attribute. Character blinking is controlled by setting a bit in a special
register in the video subsystem. (On the CGA, for example, this
enable-blink bit is bit 5 of the 8-bit register mapped at input/output
port 3D8H.) When this bit is set to 1, the high-order bit of each
character's attribute byte is not interpreted as part of the character's
background color specification. Instead, this bit indicates whether the
character should blink.
If you have a CGA, watch what happens when you run the following BASIC
program:
10 DEF SEG = &HB800 ' point to start of video buffer
20 POKE 0,ASC("A") ' store the ASCII code for A in the buffer
30 POKE 1,&H97 ' foreground attribute = 7 (white)
' background attribute = 9 (intense blue)
You'll see a blinking white letter A on a blue background. If you add the
following statement to the program, you'll clear the enable-blink bit and
cause the CGA to interpret the background attribute as intense blue:
40 OUT &H3D8,&H09 ' clear the "enable-blink" bit
The default attribute used by DOS and BASIC is 07H, normal white (7) on
black (0), without blinking, but you can use any combination of 4-bit
foreground and background attributes for each character displayed in a
text mode. If you exchange a character's foreground and background
attributes, the character is displayed in "reverse video." If the
foreground and background attributes are the same, the character is
"invisible."
Setting attributes in the monochrome mode
The monochrome mode (mode 7) used by the Monochrome Display Adapter has a
limited selection of attributes that take the place of color. Like the
CGA, the MDA uses 4-bit foreground and background attributes, but their
values are interpreted differently by the MDA attribute decoding
circuitry.
Only certain combinations of foreground and background attributes are
recognized by the MDA. (See Figure 4-8.) Other useful combinations, like
"invisible" (white-on-white) or a reverse-video/underlined combination,
aren't supported by the hardware.
Like the CGA, the MDA has an enable-blink bit that determines whether the
high-order bit of each character's attribute byte controls blinking or the
intensity of the background attribute. On the MDA, the enable-blink bit is
bit 5 of the register at port 3B8H. As on the CGA, the enable-blink bit is
set by the ROM BIOS when it establishes monochrome text mode 7, so you
must explicitly clear this bit if you want to disable blinking and display
characters with intensified background.
With the EGA, MCGA, and VGA, text-mode attributes work the same as with
the MDA and CGA. Although the enable-blink bit is not in the same hardware
register in the newer subsystems, the ROM BIOS offers a service through
interrupt 10H that toggles the bit on an EGA, MCGA, or VGA. (See Chapter
9, page 178 for more information about this service.)
╓┌─┌──────────┌──────────────────────────────────────────────────────────────╖
Attribute Description
──────────────────────────────────────────────────────────────────────────
00H Nondisplayed
01H Underlined
07H Normal (white on black)
09H High-intensity underlined
0FH High-intensity
70H White background, black foreground ("reverse video")
87H☼ Blinking white on black (if blinking enabled)
Dim background, normal foreground (if blinking
disabled)
Attribute Description
──────────────────────────────────────────────────────────────────────────
disabled)
8FH☼ Blinking high-intensity (if blinking enabled)
Dim background, high-intensity foreground (if blinking
disabled)
F0H Blinking "reverse video" (if blinking enabled)
High-intensity background, black foreground (if blinking
disabled)
──────────────────────────────────────────────────────────────────────────
Figure 4-8. Monochrome text-mode attributes. The appearance of some
attributes depends on the setting of the enable-blink bit at I/O port
3B8H.
Setting color in graphics modes
So far, we've seen how to set color (and the monochrome equivalent of
color) in text modes. Setting color in graphics modes is quite different.
In graphics modes, each pixel is associated with a color. The color is set
the same way attributes are set in text mode, but there are important
differences. First, since each pixel is a discrete dot of color, there is
no foreground and background──each pixel is simply one color or another.
Second, pixel attributes are not always 4 bits in size──we've already
mentioned that pixel attributes can range from 1 to 8 bits, depending on
the video mode being used. These differences give graphics-mode programs a
subtly different "feel" than they have in text modes, both to programmers
and to users.
The most important difference between text-mode and graphics-mode
attributes, however, is this: In graphics modes you can control the color
of each pixel. This lets you use colors much more effectively than you can
in text modes. This isn't so obvious with the CGA and its limited color
capabilities, but with an MCGA or VGA it's quite apparent.
Let's start with the CGA. The CGA's two graphics modes are relatively
limited in terms of color: In 320 x 200, 4-color mode, pixel attributes
are only 2 bits wide, and you can display only four different colors at a
time. In 640 x 200, 2-color mode, you have only 1 bit per pixel, so you
can display only two different colors. Also, the range of colors you can
display in CGA graphics modes is severely limited.
In 320 x 200, 4-color mode, pixels can have value 0, 1, 2, or 3,
corresponding to the 2-bit binary values 00B, 01B, 10B, and 11B. You can
assign any one of the CGA's 16 color combinations to zero-value pixels,
but colors for nonzero pixels are derived from one of three built-in
palettes. (See Figure 4-9.) In 640 x 200, 2-color mode, nonzero pixels
can be assigned any one of the 16 color combinations, but zero-value
pixels are always black. In both modes, you can assign palette colors
using ROM BIOS interrupt 10H services described in Chapter 9.
The EGA, MCGA, and VGA are considerably more flexible in terms of color
management, because you can assign any color combination to any palette or
video DAC color register. Equally important is the fact that you have
larger pixel values and therefore more colors to work with on the screen.
The most frequently used graphics modes on the EGA and VGA are the
16-color modes with pixels that require 4 bits to define the colors. In
most applications, 16 colors are adequate, because you can select those 16
colors from the entire range of color combinations the hardware can
display (64 colors on the EGA and 262,144 colors on the MCGA and VGA).
Again, the ROM BIOS provides services that let you assign arbitrary color
combinations to the palette and video DAC color registers on the EGA,
MCGA, and VGA. See Chapter 9 for details.
Pixel Bits Pixel Value Pixel Color
──────────────────────────────────────────────────────────────────────────
Mode 4, palette 0:
0 1 1 Green
1 0 2 Red
1 1 3 Yellow or brown
Mode 4, palette 1:
0 1 1 Cyan
1 0 2 Magenta
1 1 3 White
Mode 5:
0 1 1 Cyan
1 0 2 Red
1 1 3 White
──────────────────────────────────────────────────────────────────────────
Figure 4-9. Palettes in CGA 320 x 200, 4-color graphics mode.
Inside the Display Memory
Now we come to the inner workings of the video buffer map. In this
section, we'll see how the information in the video memory is related to
the display screen.
Although the video buffer memory map varies according to the video mode
you use, a clear family resemblance exists among the video modes. In text
modes, the video buffer map in all IBM video subsystems is the same. In
graphics modes, there are two general layouts, a linear map based on the
map used with the original CGA graphics modes and a parallel map that was
first used in EGA graphics modes.
Video Mode Starting Memory Used Subsystem
Paragraph (bytes)
Address (hex)
──────────────────────────────────────────────────────────────────────────
00H, 01H B800H 2000 CGA, EGA, MCGA, VGA
02H, 03H B800H 4000 CGA, EGA, MCGA, VGA
04H, 05H B800H 16,000 CGA, EGA, MCGA, VGA
06H B800H 16,000 CGA, EGA, MCGA, VGA
07H B000H 4000 MDA, EGA, VGA
0DH A000H 32,000 EGA, VGA
0EH A000H 64,000 EGA, VGA
0FH A000H 56,000 EGA, VGA
10H A000H 112,000 EGA, VGA
11H A000H 38,400 MCGA, VGA
12H A000H 153,600 VGA
13H A000H 64,000 MCGA, VGA
──────────────────────────────────────────────────────────────────────────
Figure 4-10. Video buffer addresses in IBM video modes.
Before we examine the actual map of the video buffer, let's look at the
addresses where the video buffer is located. (See Figure 4-10.) The
breakdown is straightforward: Color text modes start at paragraph address
B800H, and monochrome text mode starts at B000H. CGA-compatible graphics
modes start at B800H. All other graphics modes start at A000H. The amount
of RAM required to hold a screenful of data varies according to the number
of characters or pixels displayed, and, in the case of graphics modes,
with the number of bits that represent a pixel.
Display Pages in Text Modes
The amount of RAM physically installed in the various video subsystems is
frequently more than enough to contain more than one screen's worth of
video data. In video modes where this is true, all IBM video subsystems
support multiple display pages. When you use display pages, the video
buffer is mapped into two or more areas, and the video hardware is set up
to selectively display any one of these areas in the map.
Because only one page is displayed at any given time, you can write
information into nondisplayed pages as well as directly to the displayed
page. Using this technique you can build a screen on an invisible page
while another page is being displayed and then switch to the new page when
the appropriate time comes. Switching screen images this way makes screen
updates seem instantaneous.
The display pages are numbered 0 through 7, with page 0 starting at the
beginning of the video buffer. Of course, the amount of available RAM may
be insufficient to support eight full display pages; the actual number of
pages you can use (see Figure 4-11) depends on how much video RAM is
available and on how much memory is required for one screenful of data.
Each page begins on an even kilobyte memory boundary. The display page
offset addresses are shown in Figure 4-12.
To select a display page, use ROM BIOS interrupt 10H, service 05H. To
determine which page is actively displayed, use interrupt 10H, service
0FH. (See Chapter 9 for information about these ROM BIOS services.)
In any of these modes, if the pages are not actively used (actually
displayed on the screen), then the unused part of the display memory can
conceivably be used for data besides text or pixels, although this usage
is neither normal nor advisable. Making any other use of this potentially
free memory is asking for trouble in the future.
Number of
Video Mode Subsystem Pages Notes
──────────────────────────────────────────────────────────────────────────
00H, 01H CGA, EGA, MCGA, VGA 8
02H, 03H CGA 4
EGA, MCGA, VGA 8
04H, 05H CGA, MCGA 1
EGA, VGA 2 Not fully supported by
ROM BIOS
06H CGA, EGA, MCGA, VGA 1
07H MDA 1
EGA, VGA 8
0DH EGA, VGA 8
0EH EGA, VGA 4
0FH EGA, VGA 2
10H EGA, VGA 2
11H MCGA, VGA 1
12H VGA 1
13H MCGA, VGA 1
──────────────────────────────────────────────────────────────────────────
Figure 4-11. Display pages available in IBM video subsystems.
Page 40 x 25, 16-color 80 x 25, 16-color 80 x 25 Mono
──────────────────────────────────────────────────────────────────────────
0 B800:0000H B800:0000H B000:0000H
1 B800:0800H B800:1000H B000:1000H☼
2 B800:1000H B800:2000H B000:2000H☼
3 B800:1800H B800:3000H B000:3000H☼
4 B800:2000H B800:4000H☼ B000:4000H☼
5 B800:2800H B800:5000H☼ B000:5000H☼
6 B800:3000H B800:6000H☼ B000:6000H☼
7 B800:3800H B800:7000H☼ B000:7000H☼
──────────────────────────────────────────────────────────────────────────
Figure 4-12. Start addresses for text-mode display pages in IBM video
subsystems.
Display Pages in Graphics Modes
For the EGA, the MCGA, and the VGA, the page concept is as readily
available in graphics modes as in text modes. Obviously there is no reason
not to have graphics pages if the memory is there to support them.
The main benefit of using multiple pages for either graphics or text is to
be able to switch instantly from one display screen to another without
taking the time to build the display information from scratch. In theory,
multiple pages could be used in graphics mode to produce smooth and
fine-grained animation effects, but there aren't enough display pages to
take the animation very far.
Displaying Characters in Text and Graphics Modes
As you have learned, in text modes no character images are stored in video
memory. Instead, each character is represented in the video buffer by a
pair of bytes containing the character's ASCII value and display
attributes. The pixels that make up the character are drawn on the screen
by a character generator that is part of the display circuitry. The Color
Graphics Adapter has a character generator that produces characters in an
8 x 8 pixel block format, while the Monochrome Display Adapter's character
generator uses a 9 x 14 pixel block format. The larger format is one of
the factors that makes the MDA's display output easier to read.
The standard ASCII characters (01H through 7FH [decimal 1 through 127])
represent only half of the ASCII characters available in the text modes.
An additional 128 graphics characters (80H through FFH [decimal 128
through 255]) are available through the same character generator. More
than half of them can be used to make simple line drawings. A complete
list of both the standard ASCII characters and the graphics characters
provided by IBM is given in Appendix C.
The graphics modes can also display characters, but they are produced
quite differently. Graphics-mode characters are drawn, pixel by pixel, by
a ROM BIOS software character generator, instead of by a hardware
character generator. (ROM BIOS interrupt 10H provides this service; see
Chapter 9.) The software character generator refers to a table of bit
patterns to determine which pixels to draw for each character. The ROM of
every PC and PS/2 contains a default table of character bit patterns, but
you can also place a custom bit pattern table in RAM and instruct the BIOS
to use it to display your own character set.
In CGA-compatible graphics modes (640 x 200, 2-color and 320 x 200,
4-color), the bit patterns for the second 128 ASCII characters are always
found at the address stored in the interrupt 1FH vector at 0000:007CH. If
you store a table of bit patterns in a buffer and then store the buffer's
segment and offset at 0000:007CH, the ROM BIOS will use the bit patterns
in the buffer for ASCII characters 80H through FFH (decimal 128 through
255. In other graphics modes on the EGA, MCGA, and VGA, the ROM BIOS
provides a service through interrupt 10H that lets you pass the address of
a RAM-based table of character bit patterns for all 256 characters.
Mapping characters in text modes
In text modes, the memory map begins with the top left corner of the
screen, using 2 bytes per screen position. The memory bytes for succeeding
characters immediately follow in the order you would read them──from left
to right and from top to bottom.
Modes 0 and 1 are text modes with a screen format of 40 columns by 25
rows. Each row occupies 40 x 2 = 80 bytes. A screen occupies only 2 KB in
modes 0 and 1, which means the CGA's 16 KB memory can accommodate eight
display pages. If the rows are numbered 0 through 24 and the columns
numbered 0 through 39, then the offset to any screen character in the
first display page is given by the following BASIC formula:
CHARACTER.OFFSET = (ROW.NUMBER * 80) + (COLUMN.NUMBER * 2)
Since the attribute byte for any character is in the memory location next
to the ASCII character value, you can locate it by simply adding 1 to the
character offset.
Modes 2, 3, and 7 are also text modes, but with 80 columns in each row
instead of 40. The byte layout is the same, but each row requires twice as
many bytes, or 80 x 2 = 160 bytes. Consequently, the 80 x 25 screen format
uses 4 KB, and the 16 KB memory can accommodate four display pages. The
offset to any screen location in the first display page is given by the
following BASIC formula:
CHARACTER.OFFSET = (ROW.NUMBER * 160) + (COLUMN.NUMBER * 2)
The beginning of each text display page traditionally starts at an even
kilobyte boundary. Because each screen page in the text modes actually
uses only 2000 or 4000 bytes, some unused bytes follow each page: either
48 or 96 bytes, depending on the size of the page. So, to locate any
screen position on any page in text mode, use the general formula shown on
the next page.
LOCATION = (SEGMENT.PARAGRAPH * 16)
+ (PAGE.NUMBER * PAGE.SIZE) + (ROW.NUMBER * ROW.WIDTH * 2)
+ (COLUMN.NUMBER * 2) + WHICH
LOCATION is the 20-bit address of the screen information.
SEGMENT.PARAGRAPH is the location of the video display memory
(for example, B000H or B800H).
PAGE.NUMBER is in the range 0 through 3 or 0 through 7.
PAGE.SIZE is 2000 or 4000.
ROW.NUMBER is from 0 through 24.
ROW.WIDTH is 40 or 80.
COLUMN.NUMBER is from 0 through 39 or 0 through 79.
WHICH is 0 for the display character or 1 for the display attribute.
Mapping pixels in graphics modes
When you use a graphics mode, pixels are stored as a series of bit fields,
with a one-to-one correlation between the bit fields in memory and the
pixels on the screen. The actual mapping of bit fields in the video buffer
depends on the video mode.
In CGA-compatible graphics modes, the display is organized into 200 lines,
numbered 0 through 199. Each line of pixels is represented in the video
buffer in 80 bytes of data. In 640 x 200, 2-color mode, each bit
represents one pixel on the screen, while in 320 x 200, 4-color mode, each
pixel is represented by a pair of bits in the buffer. (See Figure 4-13.)
Thus there are eight pixels to each byte in 640 x 200, 2-color mode, and
80 x 8, or 640, pixels per row. Similarly, there are four pixels to each
byte in 320 x 200, 4-color mode, and 80 x 4, or 320, pixels per row.
The storage for the pixel rows is interleaved:
■ Pixels in even-numbered rows are stored in the first half of the video
buffer, starting at B800:0000H.
■ Pixels in odd-numbered rows are stored starting at B800:2000H.
For example, in 640 x 200, 2-color mode, the first pixel in the first row
(in the upper-left corner of the screen) is represented by the leftmost
bit (bit 7) in the byte at B800:0000H. The second pixel in the row is
represented by bit 6 of the same byte. Because of the interleaved buffer
map, however, the pixel immediately below the first pixel is represented
in bit 7 of the byte at B800:2000H.
640 x 200, 2-color mode 320 x 200, 4-color mode
bit bit
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
┌───────────────────┐ ┌───────────────────┐
│ 1 0 0 0 1 1 1 1 │ │ 1 1 0 0 0 1 0 1 │
└──┼─┼─┼─┼─┼─┼─┼─┼──┘ └──┬─┬─┬─┬─┬─┬─┬─┬──┘
│ │ │ │ │ │ │ │ └┬┘ └┬┘ └┬┘ └┬┘
┌───│─│─│─│─│─│─│─│─────── ┌────│───│───│───│────────
│┌──│─│─│─│─│─│─│─│─────── │┌───│───│───│───│────────
││ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ││ ▼ ▼ ▼ ▼
││ ▓ • • • ▓ ▓ ▓ ▓ ││ ▒ • ▓ ▓
││ ││
││ ││
││ ││
Figure 4-13. Pixel mapping in CGA-compatible graphics modes.
In all other graphics modes, the buffer map is linear, as it is in text
modes. Pixels are stored from left to right in each byte, and one row of
pixels immediately follows another in the video buffer. On the MCGA and
VGA, for example, the 1-bit pixels in 640 x 480, 2-color mode and the
8-bit pixels in 320 x 200, 256-color mode are stored starting at
A000:0000H and proceeding linearly through the buffer.
The catch is that pixel bit fields are not always mapped linearly in all
video modes. On the EGA and VGA, the video buffer in 16-color graphics
modes is arranged as a set of four parallel memory maps. In effect, the
video memory is configured to have four 64 KB memory maps spanning the
same range of addresses starting at A000:0000H. The EGA and VGA have
special circuitry that accesses all four memory maps in parallel. Thus in
16-color EGA and VGA graphics modes, each 4-bit pixel is stored with 1 bit
in each memory map. (See Figure 4-14.) Another way to visualize this is
that a 4-bit pixel value is formed by concatenating corresponding bits
from the same address in each memory map.
There is a good reason why the EGA and VGA were designed to use parallel
memory maps in graphics modes. Consider the situation in 640 x 350,
16-color mode: With 4 bits per pixel, you need 640 x 350 x 4 (896,000)
bits to store one screenful of pixels. That comes out to 112,000 bytes,
which is bigger than the 64 KB maximum size of one 8086 segment. If you
organize the pixel data in parallel, however, you only need 112,000 ÷ 4
(28,000) bytes in each memory map.
With this variety of memory maps and pixel sizes, it's fortunate that the
ROM BIOS provides services that let you read and write individual pixels
regardless of the video mode. (Chapter 9 describes these services.)
Unfortunately, these ROM BIOS pixel-manipulation services are pretty slow.
If you're working in graphics modes, you'll probably find that the
graphics drawing functions provided in your programming language (such as
the PSET, LINE, and CIRCLE functions in BASIC) are the best tools for
creating graphics-mode screens.
┌───────────────────┐
Parallel │ 1 0 1 1 0 1 0 1 │ Map 3
memory └──┬─┬─┬─┬─┬─┬─┬─┬──┘
maps ┌──┴─┴─┴─┴─┴─┴─┴─┴──┐
│ 1 0 0 0 0 0 0 0 │ Map 2
└──┬─┬─┬─┬─┬─┬─┬─┬──┘
┌──┴─┴─┴─┴─┴─┴─┴─┴──┐
│ 1 0 1 1 0 1 1 1 │ Map 1
└──┬─┬─┬─┬─┬─┬─┬─┬──┘
┌──┴─┴─┴─┴─┴─┴─┴─┴──┐
│ 1 0 1 1 0 0 0 1 │ Map 0
└──┬─┬─┬─┬─┬─┬─┬─┬──┘
┌─────────┘ │ │┌┘ └┐│ │ └─────────┐
│ ┌──────Pixel values─────┐ │
│ │ ┌───┘│ │└───┐ │ │
┌─┴┐ ┌─┴┐ ┌─┴┐ ┌─┴┐ ┌┴─┐ ┌┴─┐ ┌┴─┐ ┌┴─┐
1111 0000 1011 1011 0000 1010 0010 1011
│ │ └───┐│ │┌───┘ │ │
│ └──────┐ │└┐ ┌┘│ ┌──────┘ │
└─────────┐ │ │ │ │ │ │ ┌─────────┘
┌───│─│─│─│─│─│─│─│───────
│┌──│─│─│─│─│─│─│─│───────
││ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
││ ▒ • ▓ ▓ • ▓ ▓ ▓
││
Figure 4-14. Pixel mapping in 16-color EGA and VGA graphics modes.
Controlling the Video Display
In general, control of the display screen, like most other computer
operations, can be done in four ways:
■ By using the programming-language services (for example, BASIC's SCREEN
statement)
■ By using the DOS services (see Chapters 16 and 17)
■ By using the ROM BIOS video services (see Chapter 9)
■ By direct manipulation of the hardware via memory or I/O ports
The video services available through programming languages, DOS, and the
ROM BIOS automatically place screen output data in the video buffer, with
each type of service offering varying levels of control. The ROM BIOS
services are particularly powerful, providing nearly all the functions
needed to generate display-screen output, control the cursor, and
manipulate screen information. (All video services are fully described in
Chapter 9.) For maximum control over the video display, you also have the
option of bypassing the software services and placing data directly in the
video buffer──when you feel you have good reason to.
──────────────────────────────────────────────────────────────────────────
About the Cursor
A blinking cursor is a feature of the text modes that is used to
indicate the active location on the display screen. The cursor is
actually a group of scan lines that fill the entire width of the
character box. The size of the character-box varies with the video
hardware and video mode: The Monochrome Display Adapter uses a
9-pixels-wide-by-14-scan-lines-high format; the Color Graphics Adapter
uses an 8-pixels-by-8-scan-lines format; the EGA's default text-mode
character box is 8 pixels wide by 14 scan lines high; and the VGA's is 9
by 16. The higher-resolution video subsystems use character boxes with
more scan lines, so their text-mode characters appear sharper and more
detailed, as you'll see in Appendix C.
The default cursor format uses two scan lines near the bottom of the
character box but may be changed to display any number of scan lines
within the character box. Since the blinking cursor used in text modes
is a hardware-created feature, software has only limited control over
it.
You can change the size of the cursor as well as its location on the
screen using the services provided by the ROM BIOS. Interrupt 10H,
service 01H lets you set the size of the cursor, whereas service 02H
lets you move the cursor to any character position on the screen. The
ROM BIOS also provides a service (interrupt 10H, service 03H) that
reports the current size and location of the cursor.
So far, we've been talking about the text-mode cursor. In graphics modes
there is no hardware-generated cursor, but the ROM BIOS routines keep
track of a logical cursor location that tells you the active screen
location. As in text modes, you can use ROM BIOS services 02H and 03H to
keep track of the graphics-mode cursor location.
To create a cursor in graphics modes, many programs, including BASIC,
simulate the block cursor by using a distinctive background color at the
cursor location or by using the ASCII block characters.
──────────────────────────────────────────────────────────────────────────
Before opting for direct video output, you should know that it does
interfere with windowing systems and more advanced multitasking operating
environments. All the same, many important programs for the PC family
generate direct video output──so many, in fact, that this has become a
standard and accepted way of creating output. So, even though in the long
run it's probably not wise to place output directly in the video buffer,
everyone seems to be doing it.
Basically, you can't mix programs that write directly into the display
memory and windowing systems because two programs would be fighting over
the control of the same memory and messing up each other's data. But
because so many programs now generate direct video output, multitasking
operating systems like OS/2 go to great lengths to accommodate programs
that write directly to the display memory. A system like OS/2 can make
this accommodation simply by keeping a separate copy of the program's
display memory; when the program is running, the copy is moved into the
display buffer, and when the program is stopped, a fresh copy of the
display buffer is made. This technique allows OS/2 to run programs that
work directly with the display memory, but at a cost: First, computing and
memory overhead go up; second, the program can't run in the background
simultaneously with other programs; and third, the display information
can't be "windowed"; that is, it can't be moved or adjusted in size.
Programmers are faced with a conflict here: Direct output to the screen
has the benefit of speed and power, but using ROM BIOS or higher-level
services for screen output has the benefit of more flexibility for
adapting to windowing systems, new video hardware, and so on. The best
solution is to use both techniques, trading off portability whenever
maximum performance is an absolute priority.
Direct Hardware Control
Much of the information we've provided in this chapter, particularly
information on internal mapping of display memory, is meant to help you
write video information directly into the display memory. But remember
that direct programming has inherent risks, and you'll find it both safer
and easier to use the highest available means to control the video
display. Lower-level means, particularly direct manipulation, can be very
disruptive.
More important, it's not always easy to write "well-behaved" programs that
access video hardware directly. There are several reasons for this. One is
simply that there is a lot of different video hardware to worry about.
Apart from the five IBM video subsystems we've discussed here, many
non-IBM video adapters and built-in video subsystems exist in non-IBM
computers. If you write a program that programs a particular IBM video
subsystem directly, the program probably won't be portable to a different
IBM subsystem or to non-IBM hardware.
We've already mentioned another reason to avoid direct video hardware
programming: Multitasking or windowing operating systems must work
overtime to accommodate programs that directly access video hardware. Of
course, the designers of newer PC and PS/2 operating environments are well
aware of the need for good video performance, so modern operating systems
generally offer faster and more flexible video output services than do
older systems, such as DOS. Direct hardware programming offers little
advantage if the operating system's video I/O services are fast enough.
Also, direct video hardware control can get you into trouble with the ROM
BIOS if you aren't careful. The ROM BIOS keeps track of the video hardware
status in a set of variables in the data area in segment 40H. (See Chapter
3 for a list of ROM BIOS video status variables.) If you program the
video hardware directly, you must be careful to update the ROM BIOS status
variables accordingly.
For example, the simple routine we presented earlier for resetting the CGA
enable-blink bit bypasses a ROM BIOS status variable. To update the
enable-blink bit without causing the ROM BIOS to lose track of the video
hardware state, you would update the ROM BIOS status variable at
0040:0065H:
10 DEF SEG = &HB800 ' (same as before)
20 POKE 0,ASC("A")
30 POKE 1,&H97
40 DEF SEG = &H0040 ' address the BIOS data area
50 POKE &H0065,(PEEK(&H0065) AND NOT &H20) ' update BIOS status variable
60 OUT &H3D8,PEEK(&H0065) ' update hardware register
If you program carefully, controlling the video hardware directly can be
very rewarding. You can maximize the speed of your video output as well as
take full advantage of hardware capabilities such as smooth,
pixel-by-pixel panning or hardware-generated interrupts. But when you
write such a program, keep the pitfalls in mind.
Compatibility Considerations
If you want your program to run on a wide variety of PCs and PS/2s, you
must design compatibility into the program. As the various IBM video
subsystems have evolved, programmers have developed several approaches to
compatibility. These include
■ Installable programs
■ Self-installing programs
■ Hardware-independent programming environments
We've already mentioned how many software vendors provide video
compatibility by distributing software that has its video output routines
in separate, installable modules: Before the software can be used, the
video routines must be linked to the rest of the application. This lets
you write programs that take full advantage of each video subsystem's
capabilities without sacrificing compatibility.
However, the installation process can be cumbersome, both for a programmer
who must write the installation program and for an end-user who must
install video routines properly. You can eliminate the installation
process if you make your application self-installing. The key to doing
this is to incorporate a routine in your program that identifies which
video subsystem the program is running on. The program can then tailor its
own video output to the capabilities and limitations of the video
hardware.
You can use several different programming techniques to identify the video
subsystem. In PS/2s, ROM BIOS offers a service that reports the video
hardware configuration (see Chapter 9), but in the PC/XT/AT family you
must rely on improvised hardware identification techniques documented in
the hardware technical manuals.
Once a program has determined the video hardware configuration, it can
produce appropriate output. For example, a program running on a Monochrome
Display Adapter can use only one video mode with monochrome attributes. If
the same program were running on a color subsystem, it could run with
color attributes in text modes. If the program needed to produce graphics
output, it could select a graphics mode with the highest possible
resolution based on its identification of the video subsystem.
In the simplest case, your program can use whatever video mode is in use
when the program starts up. ROM BIOS interrupt 10H, service 0FH reports
the current video mode number. If you're not using an assembly-language
interface to the ROM BIOS, however, you may find it easier simply to use
the program on the following page to inspect the ROM BIOS status variable
at 0040:0049H that contains the video mode number.
10 DEF SEG = &H0040
20 VIDEO.MODE = PEEK(&H0049)
You can avoid video hardware dependence in your programs if you use an
operating environment like Digital Research's GEM or Microsoft Windows.
These environments shield your program from the idiosyncrasies of video
hardware by providing a set of consistent, hardware-independent
subroutines to perform video I/O. The problem, of course, is that the
end-user must also have a copy of the operating environment to be able to
run your program.
Whatever approach you take to video compatibility, be sure to consider
several compatibility criteria. These criteria are not completely
consistent with each other, reflecting the internal inconsistency in the
design of the IBM personal computer and the variety of display formats
that can be used. Still, there are overall guidelines for compatibility,
which we'll outline here.
First, text-only display output increases compatibility. Many PCs are
still equipped with Monochrome Display Adapters, which cannot show graphic
output. If you are weighing a text-versus-graphics decision in the design
of a program, there are two factors to consider. On one hand, as many
programs have dramatically demonstrated, you can create very effective
drawings using only standard IBM text characters. On the other hand, it is
more and more common for computers to include graphics capability. So, in
the future, text-only output will probably lose its importance, and you'll
be able to use graphics in your programs without worrying about
compatibility.
Second, the less your programs depend on color, the wider the range of
computers with which they will be compatible. This does not mean that you
need to avoid color for compatibility; it simply means that for maximum
compatibility, programs should use color as an enhancement rather than as
an essential ingredient. If programs can get along without color, they
will be compatible with computers that use monochrome displays, including
PCs with Monochrome Display Adapters, as well as Compaq Portable computers
with built-in monochrome displays.
In general, you must weigh the advantage of broad compatibility against
the convenience and simplicity of writing programs for a narrower range of
displays. Our own experience and judgment tell us that far too often
programmers err by opting for a narrower range of displays, thereby
greatly reducing the variety of computers their programs can be used on.
Be forewarned.
────────────────────────────────────────────────────────────────────────────
Chapter 5 Disk Basics
Disk Data Mapping
Data Storage
Bootable Disks
DOS Disk Formats
Diskette Formats
Fixed-Disk Formats
The Disk's Logical Structure
How DOS Organizes the Disk
The Logical Structure in Detail
The Boot Sector
The Root Directory
The Files Area
The File Allocation Table
Comments
Copy Protection
Most computer systems have some way to store information permanently,
whether it is on punched paper tape, bar-coded print media, magnetic disks
or tape, or laser disks. By far the most widely used media in the PC and
PS/2 family are diskettes (floppy disks) and fixed disks (hard disks).
Diskettes and fixed disks come in various sizes and capacities but they
all work in basically the same way: Information is magnetically encoded on
their surfaces in patterns determined by the disk drive and by the
software that controls the drive.
When the PC family was introduced in 1981, it used one main type of
storage device: the 51/4-inch diskette, which was double density, single
sided, and soft sectored, and stored only 160 kilobytes (KB). Since then,
higher-capacity 51/4-inch and 31/2-inch diskettes have become standard
equipment on PCs and PS/2s, as have fixed disks with capacities from 10
megabytes (MB) on the PC/XT to 314 MB on the PS/2 Model 80.
Although the type of storage device is important, it is the way stored
information is laid out and managed that concerns programmers most. In
this chapter, we'll focus on how information is organized and stored on
both diskettes and fixed disks. Much of the information provided in this
chapter applies to RAM disks──that is, the simulation of disk storage in
memory──as much as it does to conventional diskettes, fixed disks, and
disk cartridges.
Disk Data Mapping
To understand how data is organized on a disk, consider the physical
structure of the disk itself and the drive mechanism that reads from and
writes to it. We'll start with diskettes, but both diskettes and fixed
disks have the same basic geometry.
Inside a diskette's square plastic case is a circular platter made of
tough plastic coated with a magnetic medium. A diskette drive stores data
on the diskette by writing and reading magnetically encoded patterns that
represent digital data. Because both sides of the diskette are coated,
both sides can be used to store data.
A diskette drive contains a motor that rotates the diskette at a constant
speed. The drive has two read/write heads, one for each side of the
diskette. The heads are mounted on an arm that moves them in unison to any
position toward or away from the center of the disk. (The original IBM PC
came with a diskette drive that had only one read/write head and could
access only one side of a diskette. Most PC users perceived this as
wasteful, so single-sided diskette drives gradually went the way of the
dinosaur.)
Like the tape heads in a common tape recorder, a diskette drive's
read/write heads can magnetize the diskette medium to store data on the
diskette; they can also retrieve data from the diskette by decoding the
magnetically encoded patterns in the diskette medium.
The geometry of a fixed disk is similar to that of a diskette. Fixed disks
rotate much faster than diskettes, so the platters are made of
magnetically coated metal or glass, not flexible plastic. Also, fixed
disks usually consist of a stack of several platters that rotate together,
so fixed-disk drives have multiple read/write heads──one for each disk
surface.
Data Storage
The way data is mapped on diskettes and fixed disks is a natural result of
the geometry of the hardware. When a particular read/write head is held
motionless, a ring of magnetic medium moves past it as the disk rotates.
For each position of the read/write head, relative to the center of the
disk, there is a corresponding ring of disk medium on which data can be
stored. These rings are called tracks. (See Figure 5-1.)
Because each disk track can store 4 KB or more of data, each track of data
is divided into a number of smaller units called sectors. All sectors hold
the same amount of data──typically, 512 bytes for diskettes and most fixed
disks. The sectors and tracks are numbered sequentially, so you can locate
any particular byte of data on a disk surface by specifying its track
number and its sector number.
Because two-sided diskettes and fixed disks have more than one disk
surface, however, you need to think three-dimensionally to locate a byte
of data. So the position of the read/write heads for these disks is
described by a cylinder number. Like tracks, cylinders are numbered
sequentially. If you think of a cylinder as a stack of tracks at a given
position of the read/write heads, you can see that the location of a
particular track is determined by specifying a cylinder number plus a
read/write head.
With this in mind, it's easy to make sense of the various diskette formats
used in PC and PS/2 disk drives. (See Figure 5-2.) With the original
single-sided IBM PC diskette drives you could use diskettes formatted with
40 tracks, each of which contained eight sectors of data, so the capacity
of the diskette was 40 x 8 x 512, or 160 KB. Now, with more accurate
diskette drives and with high-density diskette media that can store more
data per track, you can use diskettes with higher-capacity formats.
Fixed-disk drives are mechanically more accurate than diskette drives, and
their magnetic media are of comparatively higher density, so the number of
tracks and the number of sectors per track are higher than for diskettes.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 5-1 can be found on p.102 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 5-1. One side of a diskette formatted with 40 concentric tracks
and eight sectors per track.
Manufacturers' terminology and advertising regarding these variations of
disk format and disk-storage capacity is somewhat fuzzy. "Quad-density"
refers to a diskette or drive that can use an 80-track diskette format.
"High-density" and "high-capacity" generally refer to the PC/AT 1.2 MB or
PS/2 1.44 MB diskette formats. "Double-density" diskettes can be formatted
with eight or nine sectors per track, but they can't be used reliably with
higher-capacity formats.
Sectors
Disk Capacity Cylinders per Track Heads
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 160 KB 40 8 1
180 KB 40 9 1
320 KB 40 8 2
360 KB 40 9 2
1.2 MB 80 15 2
31/2-inch diskette 720 KB 80 9 2
1.44 MB 80 18 2
──────────────────────────────────────────────────────────────────────────
Figure 5-2. PC and PS/2 diskette formats.
Bootable Disks
Regardless of their data formats, all diskettes and disks are potentially
bootable; that is, they can contain the information necessary to get an
operating system running at the time you start your computer. There is
nothing special about the format of a bootable disk; it's just one that
contains information that lets the ROM BIOS boot the operating system.
Here's how it works.
On all PC and PS/2 diskettes and fixed disks, the first sector on the
disk──cylinder 0, head 0, sector 1──is reserved for a short bootstrap
program. (The program has to be short because the size of a sector is only
512 bytes.) The function of this bootstrap program is to read the bulk of
the operating system into memory from elsewhere on the disk and then to
transfer control to the operating system.
When you start or restart your computer, the last tasks performed by the
start-up ROM BIOS routines are reading the contents of the disk boot
sector into memory and checking those contents for a bootstrap program.
The BIOS does this checking by examining the last 2 bytes of the boot
sector for a signature (55H and AAH) that indicates that the data in the
boot sector represents a bootstrap program. If the signature value isn't
correct, the BIOS assumes there's no bootstrap program in the boot sector
and, therefore, that the disk isn't bootable.
The bootstrap program's job is to copy the start-up program for an
operating system from the disk into memory. There's no restriction on the
size and location of the operating system's start-up program, so this
stepwise transfer of control──from ROM BIOS to boot sector to operating
system──can be used to start DOS, XENIX, OS/2, or even a stand-alone
application.
DOS Disk Formats
The diskette formats listed in Figure 5-2 aren't the only ones you can
use for diskettes, but because diskettes are intended to be portable, the
number of diskette formats that DOS recognizes is limited to those in the
list. In the earliest releases of DOS, only the 160 KB and 320 KB formats
could be used. Later DOS versions recognize higher-capacity diskette
formats and fixed disks in addition to the original diskette formats
(Figure 5-3).
Disk Capacity DOS Version Media Descriptor
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 160 KB 1.0 FEH
320 KB 1.1 FFH
180 KB 2.0 FCH
360 KB 2.0 FDH
1.2 MB 3.0 F9H
31/2-inch diskette 720 KB 3.2 F9H
1.44 MB 3.3 F0H
Fixed disk 2.0 F8H
──────────────────────────────────────────────────────────────────────────
Figure 5-3. Standard DOS disk formats. The media descriptor value is used
by DOS to identify different disk formats.
Diskette Formats
Beginning with version 2.0, DOS had the potential to recognize virtually
any physical disk format. This became possible because DOS versions 2.0
and later provide the necessary tools to write an installable device
driver── a machine-language routine that can configure a disk drive to
read or write different formats or allow you to hook up a non-IBM disk
drive to your system. (See Appendix A for more on installable device
drivers.)
Fortunately, installable diskette device drivers have not led to a
proliferation of nonstandard, incompatible diskette formats. Instead,
software vendors and programmers have relied on the standard DOS formats
listed in Figure 5-3. On 51/4-inch diskettes, the 360 KB nine-sector
format is used most frequently, while on 31/2-inch diskettes, the 720 KB
format is most common. These are not the highest capacity formats, but
they can be used on machines that aren't equipped with higher-capacity
diskette drives as well as on those that are.
If you're interested in creating your own diskette formats, or in
understanding DOS diskette formats in more detail, be sure to read about
ROM BIOS disk services in Chapter 10.
Fixed-Disk Formats
High-capacity fixed-disk systems present some special problems and
opportunities. Fixed-disk formats vary much more than diskette formats do
(Figure 5-4). Still, data is organized on fixed disks by cylinder, head,
and sector numbers, just as it is on diskettes.
Sectors
Disk Capacity Cylinders per Track Heads
──────────────────────────────────────────────────────────────────────────
Typical PC/XT 10 MB 306 17 4
fixed disk
PC/AT fixed disk 30 MB 733 17 5
type 20
PS/2 Model 30 20 MB 612 17 4
fixed disk, type 26
PS/2 Model 60 44 MB 732 17 7
fixed disk, type 31
──────────────────────────────────────────────────────────────────────────
Figure 5-4. Some typical fixed-disk formats. All use 512 bytes per
sector.
Because the storage capacity of a fixed disk is relatively large, some PC
users prefer to use only part of the disk space for DOS and to use other
portions of the disk for other operating systems. To facilitate this, the
available space on a fixed disk can be split into as many as four logical
partitions, each of which is accessed separately. Each partition's data
can be kept completely separate from the data in the other partitions.
Each partition can contain its own boot sector and operating system.
The first sector on a fixed disk contains a 64-byte partition table
(Figure 5-5) and a disk bootstrap program. The partition table indicates
where each partition is located on the disk. The table also designates one
bootable partition. The first sector in the bootable partition is a
partition boot sector that the ROM BIOS can use to load an operating
system.
The disk bootstrap program examines the partition table to determine which
one of the partitions is bootable. It then reads the partition's boot
sector from the disk into memory. The partition boot sector contains a
bootstrap program that reads the operating system from the disk into
memory and transfers control to it.
Because bootable partitions are indicated in a table, you can select among
fixed-disk partitions simply by updating the table and restarting the
computer. All operating systems capable of supporting fixed disks provide
a utility program that lets you update the partition table. (The DOS
utility FDISK is such a program.)
Offset from
Start of Entry Size (bytes) Meaning
──────────────────────────────────────────────────────────────────────────
00H 1 Boot indicator (80H = bootable, 0 = not
bootable)
01H 1 Starting head number
02H 2 Starting cylinder number (10 bits) and
sector number (6 bits)
04H 1 System indicator:
1 = primary DOS, 12-bit FAT
2 = XENIX
4 = primary DOS, 16-bit FAT
5 = extended DOS
8 = other non-DOS
05H 1 Ending head number
06H 2 Ending cylinder and sector numbers
08H 4 Starting sector (relative to beginning of
disk)
0CH 4 Number of sectors in partition
──────────────────────────────────────────────────────────────────────────
Figure 5-5. The format of an entry in a fixed-disk partition table. The
table consists of four such 16-byte entries, starting at offset 1BEH in
the disk boot sector.
──────────────────────────────────────────────────────────────────────────
NOTE:
Be very careful if you access a fixed disk's boot sector. The
information contained there is intended only for use by the ROM BIOS
bootstrap loader. Should the data in a disk's boot sector be erased or
corrupted, the entire contents of the disk may become inaccessible.
──────────────────────────────────────────────────────────────────────────
The Disk's Logical Structure
Regardless of the type of disk you use, all DOS disks are logically
formatted in the same way: The disk's sides, tracks, and sectors are
identified numerically with the same notation, and certain sectors are
always reserved for special programs and indexes that DOS uses to manage
disk operations. Before we describe how DOS organizes space on a disk, we
need to briefly cover the conventional notation used by DOS and the ROM
BIOS to locate information.
Diskette cylinder numbers start from 0 at the outside edge of the disk
surface and increase toward the center of the disk. Read/write heads are
also numbered from 0, but sector numbers start with 1. Any location on the
disk can thus be described by a unique combination of cylinder, head, and
sector numbers. This in fact is how the ROM BIOS services access disk
data.
DOS, however, does not recognize cylinders, heads, and sectors. Instead,
DOS sees a disk as a linear sequence of logical sectors. The sequence of
logical sectors begins with the first sector on a disk: Sector 1, cylinder
0, head 0 (the boot sector) is DOS logical sector 0.
Logical sectors are numbered from track to track in the same cylinder, and
then are numbered from cylinder to cylinder. Thus the last sector in
cylinder 0, head 0, is followed by the first sector in cylinder 0, head 1;
the last sector in a cylinder is followed by the first sector in the next
cylinder. See page 300 for information on converting DOS notation to ROM
BIOS notation and vice versa.
The use of logical sector numbers lets DOS avoid having to deal with
cylinder, head, and sector numbers that vary among different types of
disk-drive hardware. However, this same feature means that DOS is limited
in the amount of disk space it can access on a particular disk drive.
Because DOS maintains logical sector numbers as 16-bit integers, it can
recognize, at most, 65,536 logical sectors on a disk. Because the default
size of a disk sector is 512 bytes, the largest disk DOS can manage is
65,536 x 512, or 32 MB. This certainly is no problem on diskettes, but
it's an unwelcome limitation for the many PC/AT and PS/2 users who have
fixed disks larger than 32 MB.
To get around this restriction, DOS version 3.3 introduced the notion of
the extended DOS partition. With DOS 3.3, you can use the DOS utility
program FDISK to allocate a fixed-disk partition as an extended DOS
partition. You can format the extended partition as one or more separate
logical drives. Thus, for example, you could use both a primary and an
extended DOS partition on a fixed disk, with the primary partition as
drive C and the extended partition as drives D and E.
How DOS Organizes the Disk
When DOS formats a diskette, it erases and verifies every sector. In a
fixed-disk partition, DOS verifies the integrity of each sector without
erasing pre-existing data. (That is why a program like the Norton
Utilities' Format Recover can retrieve data from a fixed disk after you
have accidentally reformatted it.) On both diskettes and fixed disks, the
format program reserves a certain amount of disk space to store control
information and indexes that DOS uses to organize the data you store on
the disk.
Every DOS diskette or fixed-disk DOS partition is mapped into four
separate areas. These areas, in the order they are stored, are the
reserved area, the file allocation table (FAT), the root directory, and
the files area. (See Figure 5-6.) The size of each area varies among
formats, but the structure and the order of the areas don't vary.
┌───────────────────┐
Logical sector 0 │ Reserved area │
├───────────────────┤
│ │
│ File allocation │
│ table (FAT) │
│ │
├───────────────────┤
│ Root directory │
├───────────────────┤
│ │
│ Files area (files │
│and subdirectories)│
│ │
│ │
│ │
└───────────────────┘
Figure 5-6. DOS disk map.
The reserved area can be one or more sectors long; the first sector is
always the disk boot sector (logical sector 0). A table within the boot
sector specifies the size of the reserved area, the size (and number of
copies) of the file allocation table, as well as the number of entries in
the root directory. All diskettes have a reserved area of at least one
sector, even if they aren't bootable.
The file allocation table, or FAT, immediately follows the reserved area.
The FAT maps the usage of all the disk space in the files area of the
disk, including space used for files, space that hasn't been used, and
space that is unusable due to defects in the disk medium. Because the FAT
maps the entire usable data storage area of a disk, two identical copies
of it are stored in case one is damaged. The size of a FAT depends on the
size of the disk (or of the partition of a fixed disk): Larger disks
usually require larger FATs. Figure 5-7 shows FAT sizes for several
different disk sizes.
Reserved Root
Disk Capacity Area FAT Directory
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 360 KB 1 sector 4 sectors 7 sectors
1.2 MB 1 14 14
31/2-inch diskette 720 KB 1 6 7
1.44 MB 1 18 14
──────────────────────────────────────────────────────────────────────────
Figure 5-7. Reserved area, FAT, and root-directory overhead for some
common DOS diskette formats.
The root directory is the next item on a DOS disk. It is used as a table
of contents, identifying each file on the disk with a directory entry that
contains several pieces of information, including the file's name, size,
and location on the disk. The size of the root directory varies with the
disk format. (See Figure 5-7.)
The files area, which occupies the bulk of the available disk space, is
used to store files; in DOS versions 2.0 and later, the files area may
contain subdirectories as well as files. For both files and
subdirectories, space in the files area is allocated as needed in chunks
of contiguous sectors called clusters. As with the sizes of the FAT and
the root directory, a DOS disk's cluster size varies with the format. (See
Figure 5-8.) The number of sectors in a cluster is always a power of 2;
generally, the cluster size is one sector for single-sided diskettes, two
sectors for double-sided diskettes, and four or more for fixed disks.
Disk Capacity Cluster Size
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 360 KB 2 sectors
1.2 MB 1
31/2-inch diskette 720 KB 2
1.44 MB 1
Typical PC/XT fixed disk 10 MB 8
PC/AT fixed disk, type 20 30 MB 4
PS/2 Model 30, fixed disk, type 26 20 MB 4
PS/2 Model 60, type 31 44 MB 4
──────────────────────────────────────────────────────────────────────────
Figure 5-8. Cluster size for some common DOS disk formats.
The Logical Structure in Detail
Now it's time to delve a little more deeply into each of the four sections
of a disk: the boot sector, the root directory, the files area, and the
FAT.
The Boot Sector
The boot sector on a DOS diskette or in a DOS partition on a fixed disk
consists primarily of a short machine-language program that starts the
process of loading DOS into memory. As we mentioned, to perform this task
the ROM BIOS checks to see whether the disk is bootable and then proceeds
accordingly.
──────────────────────────────────────────────────────────────────────────
NOTE:
A bootable disk contains the start-up programs for an operating system
or for a stand-alone application that runs without operating-system
support. In the case of DOS, a bootable disk contains two hidden files
that represent the DOS start-up routines and essential low-level DOS
functions. See Chapter 3, page 45 for details about these files.
──────────────────────────────────────────────────────────────────────────
You can inspect the boot program by using the DOS DEBUG utility, which
combines the ability to read data from any sector on a disk with the
ability to disassemble──or unassemble──machine language into
assembly-language code. If you want to learn more about the boot program
and you aren't intimidated by DEBUG's terse command format, place a
bootable diskette in drive A and enter the following commands to display
the diskette's boot program:
DEBUG
L 0 0 0 1 ; load first logical sector
U 0 L 3 ; unassemble and list first and second bytes
At this point, DEBUG will display the first instruction in the boot
program, a JMP to the address that contains the rest of the program. Use
DEBUG's U command with the address specified in the JMP to inspect the
rest of the boot program. For example, if the first instruction is JMP
0036, enter
U 0036 ; unassemble and list next portion of boot program
For all disk formats (except diskettes formatted with eight sectors per
track) you will find some key parameters in the boot sector, beginning
with the 11th byte. (See Figure 5-9.) These parameters are part of the
BIOS parameter block used by DOS to control any disk-type device. If
you're using DEBUG to inspect the boot sector of a diskette in drive A,
you can see a hexadecimal dump of the BIOS parameter block by entering the
following command:
D 0B L 1B
Offset Length Description
──────────────────────────────────────────────────────────────────────────
03H 8 bytes System ID
0BH 1 word Number of bytes per sector
0DH 1 byte Number of sectors per cluster
0EH 1 word Number of sectors in reserved area
10H 1 byte Number of copies of FAT
11H 1 word Number of root directory entries
13H 1 word Total number of sectors
15H 1 byte DOS media descriptor
16H 1 word Number of sectors per FAT
18H 1 word Number of sectors per track
1AH 1 word Number of heads (sides)
1CH 1 word Number of hidden sectors
──────────────────────────────────────────────────────────────────────────
Figure 5-9. The BIOS parameter block in the boot sector.
The Root Directory
The root directory on a diskette or in a fixed-disk partition is created
by the DOS FORMAT program. The root directory's size is determined by
FORMAT, so the number of root directory entries is limited. (See Figure
5-10.)
Disk Capacity Size Number of
Entries
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 180 KB 4 sectors 64
360 KB 7 112
1.2 MB 14 224
31/2-inch diskette 720 KB 7 112
1.44 MB 14 224
Typical PC/XT fixed disk 10 MB 32 512
PC/AT fixed disk, type 20 30 MB 32 512
PS/2 Model 30, fixed disk, 20 MB 32 512
type 26
PS/2 Model 60, fixed disk, 44 MB 32 512
type 31
──────────────────────────────────────────────────────────────────────────
Figure 5-10. Root directory sizes for some common DOS disk formats.
In DOS versions 1.0 and later, which did not support subdirectories, the
size of the root directory limited the number of files that could be
stored on a diskette. This restriction disappeared in DOS versions 2.0 and
later, where file names could be placed in subdirectories as well as in
the root directory.
The root directory contains a series of 32-byte directory entries. Each
directory entry contains the name of a file, a subdirectory, or a disk
volume label. The directory entry for a file contains such basic
information as the file's size, its location on the disk, and the time and
date it was most recently modified. This information is contained in the
eight fields listed in Figure 5-11.
Size
Offset Description (bytes) Format
──────────────────────────────────────────────────────────────────────────
00H Filename 8 ASCII characters
08H Filename extension 3 ASCII characters
0BH Attribute 1 Bit coded
0CH Reserved 10 Unused; zeros
16H Time 2 Word, coded
18H Date 2 Word, coded
1AH Starting cluster number 2 Word
1CH File size 4 Integer
──────────────────────────────────────────────────────────────────────────
Figure 5-11. The eight parts of a directory entry.
Offset 00H: The filename
The first 8 bytes in a directory entry contain the filename, stored in
ASCII format. If the filename is less than eight characters, it is filled
out to the right with blanks (CHR$(32)). Letters should be uppercase,
because lowercase letters will not be properly recognized. Normally,
blanks should not be embedded in the filename, as in AA BB. Most DOS
command programs, such as DEL and COPY, will not recognize filenames with
embedded blanks. BASIC works successfully with these filenames, however,
and DOS services usually can too. (See Chapters 16 and 17.) This
capability suggests some useful tricks, such as creating files that cannot
easily be erased.
Two codes, used to indicate special situations, may appear in the first
byte of the filename field. When a file is deleted, DOS sets the first
byte of the filename field in its directory entry to E5H to indicate that
the directory entry can be reused for another filename. In DOS versions
2.0 and later, the first byte of a directory entry can also be set to 00H
to indicate the end of the list of directory entries.
When a file is erased, only two things on the disk are affected: The first
byte of the directory entry is set to E5H, and the file's space-allocation
chain in the FAT is wiped out (we'll cover this in the section on the
FAT). All other directory information about the file is retained,
including the rest of its name, its size, and even its starting cluster
number. The lost information can be recovered, with suitably sophisticated
methods, provided that the directory entry has not been reused for another
file. Be forewarned, though, that whenever a new directory entry is
needed, DOS uses the first available entry, quickly recycling an erased
file's entries and making recovery more problematic.
Offset 08H: The filename extension
Directly following the filename is the standard filename extension, stored
in ASCII format. It is 3 bytes long and, like the filename, is padded with
blanks if it is less than the full three-character length. While a
filename must have at least one ordinary character in it, the extension
can be all blanks. Generally, the rules that apply to the filename also
apply to the filename extension.
──────────────────────────────────────────────────────────────────────────
NOTE:
When the directory contains a volume ID label entry, the filename and
extension fields are treated as one combined field of 11 bytes. In this
case, embedded blanks are permitted.
──────────────────────────────────────────────────────────────────────────
Offset 0BH: The file attribute
The third field of the directory entry is 1 byte long. The bits of the
attribute byte are individually coded as bits 0 through 7, as shown in
Figure 5-12, and each bit is used to categorize the directory entry.
Bit
7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
. . . . . . . 1 Read-only
. . . . . . 1 . Hidden
. . . . . 1 . . System
. . . . 1 . . . Volume label
. . . 1 . . . . Subdirectory
. . 1 . . . . . Archive
. 1 . . . . . . Unused
1 . . . . . . . Unused
──────────────────────────────────────────────────────────────────────────
Figure 5-12. The 8 file-attribute bits.
Bit 0, the low-order bit, is set to mark a file as read-only. In this
state, the file is protected from being changed or deleted by any DOS
operation. We should point out that many DOS services ignore this
attribute, so even though bit 0 can provide worthwhile protection for
data, it is not foolproof.
Bit 1 marks a file as hidden and bit 2 marks a file as a system file.
Files marked as hidden, system, or both, cannot be seen by ordinary DOS
operations, such as the DIR command. Programs can gain access to such
files only by using DOS services to search explicitly for hidden or system
files. There is no particular significance to the system attribute; it
exists to perpetuate a feature of CP/M and has absolutely nothing to do
with DOS.
──────────────────────────────────────────────────────────────────────────
Subdirectories
There are two types of directories: root directories and subdirectories.
The contents and use of each type are essentially the same (both store
the names and locations of files on the disk) but their characteristics
are different. The root directory has a fixed size and is stored in a
fixed location on the disk. A subdirectory has no fixed size and can be
stored anywhere on the disk. Any version of DOS numbered 2.0 or later
can use subdirectories.
Root Directory
│
┌─────────────────┼────────────────────────────────┐
│ │ │
Programs Word-processing data Accounting data
subdirectory subdirectory subdirectory
│ │
┌───────┴────────┐ ┌───────┴────────┐
│ │ │ │
Letters Reports Current year Prior year
subdirectory subdirectory subdirectory subdirectory
A subdirectory is stored in a disk's files area, just like any other
file. The format of directory entries in a subdirectory is identical to
the format of entries in a root directory, but a subdirectory is not
limited in size. Like an ordinary file, a subdirectory can grow without
bounds as long as disk space is available to hold it.
A subdirectory is always attached to a parent directory, which can be
either the root directory or another subdirectory. When you nest
subdirectories, one within another, they are related in the form of a
tree structure.
A parent directory has one entry for each of its subdirectories. A
subdirectory entry is just like a filename entry, except that the
attribute byte marks the entry as a subdirectory and the file-size field
is set to 0. The actual size of the subdirectory can be found by tracing
its allocation chain through the FAT.
When DOS creates a subdirectory, it places two special entries in it,
with . and . . as filenames. These act like entries for further
subdirectories, but . actually refers to the present subdirectory and .
. refers to its parent directory. The starting cluster number in each of
these directory entries gives the location of the subdirectory itself or
of its parent. When the starting cluster number is 0, the parent of the
subdirectory is the root directory.
If the size of a "normal" file is reduced, you can generally count on
DOS to release any unused space. In the case of subdirectories, however,
clusters of space that are no longer used (because the directory entries
that occupied that space are erased) are not released until the entire
subdirectory is deleted.
──────────────────────────────────────────────────────────────────────────
Bit 3 marks a directory entry as a volume label. A volume label entry is
properly recognized only in the root directory, and uses only a few of the
eight fields available in the directory entry: The label itself is stored
in the filename and extension fields, which are treated as one unified
field for this purpose; the size and starting cluster fields are not used,
but the date and time fields are.
Bit 4, the subdirectory attribute, identifies a directory entry as a
subdirectory. Because subdirectories are stored like ordinary data files,
they need a supporting directory entry. All the directory fields are used
for these entries, except the file-size field, which is zero. The actual
size of a subdirectory can be found simply by following its space
allocation chain in the FAT.
Bit 5, the archive attribute, was created to assist in making backup
copies of the many files that can be stored on a fixed disk. This bit is 0
on all files that haven't changed since they were last backed up; DOS sets
this bit to 1 whenever a file is created or modified.
Offset 0CH: Reserved
This 10-byte area is set aside for possible future uses. All 10 bytes are
normally set to 0.
Offset 16H: The time
This field contains a 2-byte value that marks the time that the file was
created or last changed. It is used in conjunction with the date field,
and the two together can be treated as a single 4-byte unsigned integer.
This 4-byte integer can be compared with those in other directory entries
for greater-than, less-than, or equal values. The time, by itself, is
treated as an unsigned word integer. It is based on a 24-hour clock and is
built out of the hour, minutes, and seconds with this formula:
Time=(Hourx2048)+(Minutesx32)+(Seconds÷2)
The 2-byte word used to store the time is one bit too short to store all
the seconds, so seconds are stored in units of 2 seconds from 0 through
29; a value of 5, for example, would represent 10 seconds. The time
11:32:10 would be stored as the value 5C05H (decimal 23557).
Offset 18H: The date
This field contains a 2-byte value that marks the date the file was
created or last changed. It is used in conjunction with the time field,
and the two together can be treated as a single 4-byte unsigned integer
that can be compared with those in other directory entries for
greater-than, less-than, or equal values. The date, by itself, is treated
as an unsigned word integer that is built out of the year, month, and day
with this formula:
Date=((Year-1980)x512)+(Monthx32)+Day
This formula compresses the year by subtracting 1980 from it. Thus, the
year 1988 is calculated as a value of 8. Using this formula, a date such
as December 12, 1988 is stored by the formula as 118CH (decimal 4492):
(1988-1980)x512+12x32+12=4492
Although this scheme allows for years up through 2107, the highest year
supported by DOS is 2099.
Offset 1AH: The starting cluster number
The seventh field of a directory entry is a 2-byte value that gives the
starting cluster number for the file's data space. This cluster number
acts as the entry point into the file's space allocation chain in the FAT.
For files with no space allocated and for volume-label entries, the
starting cluster number is 0.
Offset 1CH: The file size
The last field of a directory entry gives the size of the file in bytes.
It is coded as a 4-byte unsigned integer, which allows file sizes to grow
very large──4,294,967,295 bytes, to be exact──large enough for all
practical purposes.
DOS uses the file size in a file's directory entry to determine the exact
size of the file. Because a file's disk space is allocated in clusters of
512 bytes or more, the actual disk space occupied by a file is usually
greater than the value in the directory entry. On disk, the space between
the end of the file and the end of the last cluster in the file is wasted.
The Files Area
All data files and subdirectories are stored in the files area, which
occupies the last and largest part of each disk.
DOS allocates space to files, one cluster at a time, on an as-needed
basis. (Remember, a cluster is one or more consecutive sectors; the number
of sectors per cluster is a fixed characteristic of each disk format.) As
a file is being created, or as an existing file is extended, the file's
allocated space grows. When more space is needed, DOS allocates another
cluster to the file. In DOS versions 1 and 2, the first available cluster
is always allocated to the file. Later versions of DOS select clusters by
more complicated rules that we won't go into here.
Under ideal conditions, a file is stored in one contiguous block of space.
However, a file might be broken into several noncontiguous blocks,
especially if information is added to an existing file or a new file is
stored in the space left by an erased file. So it's not unusual for one
file's data to be scattered throughout the disk.
This file fragmentation slows access to the file's data to some degree.
Also, it is much harder to "unerase" a file you have unintentionally
erased if it is fragmented, simply because you have to do a lot more
searching for the individual clusters that make up the file's data space.
But fragmentation has no other effect, and programs generally do not need
to be concerned about where on a disk their data is stored. To determine
if a file is fragmented, use CHKDSK or a program such as the Norton
Utilities.
If you are concerned about diskette file fragmentation, the DOS COPY
command lets you transfer fragmented files to a newly formatted disk. DOS
allocates contiguous space for the copied files. This simple technique
also works for fixed-disk files, but it is much less convenient unless you
have an extra, newly formatted fixed disk to use. If you think that
fixed-disk file fragmentation is slowing down a particular application,
you can purchase any of several fixed-disk utility programs to rearrange
fragmented fixed-disk files and make them contiguous. Most of the time,
however, file fragmentation has little impact on the speed of your
programs.
Whether you ever look at your fragmented files or not, you should know how
DOS uses the file allocation table (FAT) to allocate disk space and how
the FAT forms a space allocation chain to connect all of the clusters that
make up a file.
The File Allocation Table
The file allocation table (FAT) is DOS's map of how space is utilized in
the files area of a disk. We've already discussed how space for the FAT
itself is reserved on a diskette or in a fixed-disk partition. Now we'll
describe how the FAT is formatted and used.
For most disk formats, DOS maintains two copies of the FAT, just in case
one of them is damaged or becomes unreadable. Curiously, the CHKDSK
program, which tests for most errors that can occur in the FAT and in
directories, does not even notice if the two FATs are different.
The organization of the FAT is simple: There is one entry in the FAT for
each cluster in the files area. A FAT entry can contain any of the values
listed in Figure 5-13. If the value in a FAT entry doesn't mark an
unused, reserved, or defective cluster, then the cluster that corresponds
to the FAT entry is part of a file, and the value in the FAT entry itself
indicates the next cluster in the file.
This means that the space that belongs to a given file is mapped by a
chain of FAT entries, each of which points to the next entry in the chain.
(See Figure 5-14.) The first cluster number in the chain is the starting
cluster number in the file's directory entry. When a file is created or
extended, DOS allocates clusters to the file by searching the FAT for
unused clusters (that is, clusters whose FAT entries are 0) and adding
them to the chain. Conversely, when a file is truncated or deleted, DOS
frees the clusters that had been allocated to the file by clearing the
corresponding FAT entries.
12-bit Value 16-bit Value Meaning
──────────────────────────────────────────────────────────────────────────
0 0 Unused cluster
FF0-FF6H FFF0-FFF6H Reserved cluster
FF7H FFF7H Bad cluster
FF8-FFFH FFF8-FFFFH Last cluster in a file
(other values) Next cluster in a file
──────────────────────────────────────────────────────────────────────────
Figure 5-13. FAT values.
File- Extension Starting
name cluster
Directory ┌─────┬────┬─────────────────┬────┬─┐
entry │ALPHA│TEXT│ │0003│ │
└─────┴────┴─────────────────┴──┬─┴─┘
┌────────────────────────┘
│┌─────────────┐┌─────────┐
──┬────┬─▼┴─┬────┬────┬─▼┴─┬────┬──▼──┬────┬──
FAT │0000│0006│0000│0000│0000│0008│FFFFH│0000│
──┴────┴────┴────┴────┴────┴────┴─────┴────┴──
2 3 4 5 6 7 8 9
Figure 5-14. Disk-space allocation using the FAT.
The FAT can be formatted with either 12-bit or 16-bit entries. The 12-bit
format is used for diskettes and fixed-disk partitions with no more than
4078 clusters. (A fixed-disk's partition table indicates whether a DOS
partition's FAT uses 12-bit or 16-bit entries.) The entries in a 12-bit
FAT are harder to access because they don't fit neatly into the 16-bit
word size of the 8086 family of microprocessors, but a 12-bit FAT takes up
less room on a diskette, where disk space is scarcer.
The first two entries in the FAT are reserved for use by DOS. The first
byte of the FAT contains the same media descriptor value that appears in
the BIOS parameter block in the disk boot sector. The remaining bytes of
the first two entries are filled with the value 0FFH. Because the first
two cluster numbers (0 and 1) are reserved, cluster number 2 corresponds
to the first cluster of available disk space in the files area.
Reading the values in the FAT is simple enough for a 16-bit FAT: Multiply
a given cluster number by 2 to find the byte offset of the corresponding
FAT entry. In the 16-bit FAT in Figure 5-15, for example, the byte offset
of the FAT entry for cluster 2 is 04H, and the value in that entry is
0003; the byte offset of the FAT entry for cluster 3 is 06H, and the value
in that entry is 0004; and so on.
For a 12-bit FAT, the computation is a bit trickier, because each pair of
FAT entries occupies 3 bytes (0 and 1 occupy the first 3 bytes, 2 and 3
occupy the next 3 bytes, and so forth). Given any cluster number, you can
find the FAT entry by multiplying the cluster number by 3, dividing by 2,
and then using the whole number of the result as a displacement into the
FAT. By grabbing a word at that address, you have the three hex digits of
the FAT entry, plus one extraneous hex digit, which can be removed by any
one of several quick machine-language instructions. If the cluster number
is even, you discard the high-order digit; if it is odd, you discard the
low-order digit. Try this on the 12-bit FAT in Figure 5-15. You'll find
that the entries are the same as in the 16-bit FAT in Figure 5-15.
(a) 16-bit FAT
Reserved Cluster Cluster Cluster Cluster Cluster Cluster Cluster Cl
2 3 4 5 6 7 8
┌──────┴─────┐ ┌──┴─┐ ┌──┴─┐ ┌──┴─┐ ┌──┴─┐ ┌──┴─┐ ┌──┴─┐ ┌──┴─┐ ┌─
F8 FF FF FF 03 00 04 00 05 00 06 00 0A 00 08 00 FF FF 23
(b) 12-bit FAT
Reserved Clusters Clusters Clusters Clusters
2 and 3 4 and 5 6 and 7 8 and 9
┌────┴───┐ ┌────┴───┐ ┌────┴───┐ ┌────┴───┐ ┌────┴───┐
FO FF FF 03 40 00 05 60 00 0A 80 00 FF 3F 12
Figure 5-15. The first few entries in a 16-bit FAT (a) and in a 12-bit
FAT (b).
As we have said, the first two FAT entries, in both 12-bit and 16-bit
formats, are not used to indicate the status of clusters; instead, they
are set aside so that the very first byte of the FAT can be used as a
media descriptor byte that indicates the format of the disk. (See Figure
5-16.) However, you should not assume that these IDs uniquely identify
formats: they don't necessarily. If you considered every disk format in
use, you'd find quite a few duplications. Beware.
Sectors Media
Disk Capacity Heads per Track Descriptor
──────────────────────────────────────────────────────────────────────────
51/4-inch diskette 160 KB 1 8 FEH
320 KB 2 8 FFH
180 KB 1 9 FCH
360 KB 2 9 FDH
1.2 MB 2 15 F9H
31/2-inch diskette 720 KB 2 9 F9H
1.44 MB 2 18 F0H
Fixed disk F8H
──────────────────────────────────────────────────────────────────────────
Figure 5-16. DOS media descriptor values.
Your programs can learn the format of a disk by reading and inspecting the
FAT media descriptor byte. The easy way to do this is to use DOS function
1BH (decimal 27). For more information about this function, see page 335.
Special notes on the FAT
Normally, programs do not look at or change a disk's FAT; they leave the
FAT completely under the supervision of DOS. The only exceptions are
programs that perform space-allocation functions not supported by DOS──for
example, programs that recover erased files, such as the UnErase program
in the Norton Utilities program set.
Be aware that a FAT can be logically damaged; for example, an allocation
chain can be circular, referring back to a previous link in the chain; or
two chains can converge on one cluster; or a cluster can be orphaned,
meaning that it is marked as in use even though it is not part of any
valid allocation chain. Also, an end-of-file marker (FFFH or FFFFH) may be
missing. The DOS programs CHKDSK and RECOVER are designed to detect and
repair most of these problems as well as can reasonably be done.
For special notes on the interaction of the space allocation chain in the
FAT and DOS's record of a file's size, see page 117.
Comments
Although this chapter has included detailed information for direct use of
the logical structure of the disk itself, including the boot sector, FAT,
and directories, it is not a good idea to use these elements directly
unless you have a compelling reason. In fact, except where such use is
completely unavoidable, as in a copy-protection program, it's unwise to
incorporate any knowledge of the disk format in your programs. On the
whole, your best approach is to consider the standard hierarchy of
operations and use the highest level of services that can satisfy your
needs:
■ First choice: Language services (the facilities provided by your
programming language; for example, BASIC's OPEN and CLOSE statements)
■ Second choice: DOS services (described in Chapters 16 and 17)
■ Third choice: ROM BIOS disk services (described in Chapter 10)
■ Last choice: Direct control (for example, direct programming of the
disk-drive controller through commands issued via I/O ports)
Most disk operations for the PC family can be handled quite easily with
the services that your programming language provides. There are, however,
two obvious circumstances that can call for more exotic methods. One,
which we've already mentioned, occurs when your programming involves
control of a disk on the same level exercised by DOS. This level of
control would be called for if you were writing a program similar to DOS's
CHKDSK or the Norton Utilities. The other circumstance involves copy
protection. In one way or another, all diskette copy-protection schemes
involve some type of unconventional diskette I/O. This type of control
usually leads to the use of the ROM BIOS services, but may also lead to
the extreme measure of directly programming the disk-drive controller
itself.
Copy Protection
A variety of copy-protection schemes are commercially available. Some are
simple, others are more complex. If you're interested in devising your own
scheme, however, here are some things to consider.
For diskettes, there are dozens of ways to approach copy protection.
Perhaps the most common methods involve reformatting the sectors in
certain tracks on the diskette by using the ROM BIOS format routines.
Because DOS cannot read sectors that don't conform to its specific
formats, the DOS COPY program can't copy a disk that has an occasional odd
sector size interspersed with normal sectors. This DOS limitation inspired
a number of companies to produce copy programs that can read and copy
sectors of any size, so it is not a particularly effective means of copy
protection.
On a more advanced level, there are two special aspects of diskette copy
protection that are worth noting. First, some of the most exotic and
unbreakable protection schemes have been based on the discovery of
undocumented abilities hidden in the diskette-drive controller. Second,
some protection schemes are intentionally or unintentionally dependent
upon the particular characteristics of different diskette drives. This
means that a copy-protected program may function on one model of computer
but fail to function on another model, even though the copy protection has
not been tampered with. If you use a copy-protection scheme, keep this in
mind.
Many of the copy-protection techniques used on diskettes are not
appropriate for fixed disks, mainly because most fixed-disk users need to
be able to make backup copies of programs on their fixed disks. This means
you should avoid copy-protection schemes that prevent fixed-disk backups
by making it impossible for DOS or the ROM BIOS to read part of the disk.
Most of the fixed-disk copy-protection schemes in use today rely on
data-encryption techniques, which discourage software piracy without
preventing legitimate copying.
In an encrypted program, the program's executable code and data are stored
on the disk in an encrypted, hard-to-unravel format. When you execute the
program, a special start-up program decrypts the encrypted code and data
so that it can be used. The start-up program might also rely on data saved
in hidden files or subdirectories to decrypt the main program.
There is no particular additional guidance that we can give you here,
except to remind you that variety and ingenuity are the keys to successful
copy protection.
────────────────────────────────────────────────────────────────────────────
Chapter 6 Keyboard Basics
Keyboard Operation
Keystrokes and Scan Codes
Communicating with the ROM BIOS
Translating the Scan Codes
Entering ASCII Codes Directly
Keyboard Data Format
The ASCII Keys
The Special Keys
ROM BIOS Keyboard Control
The Insert State
The Caps Lock State
The Num Lock State
The Keyboard-Hold State
The Toggle-Key States
Comments
This chapter is about the IBM PC and PS/2 keyboards. The first part of
this chapter explains how the keyboard interacts with the computer on a
hardware and software level. In the second part, we'll describe how the
ROM BIOS treats keyboard information and makes it available to programs.
──────────────────────────────────────────────────────────────────────────
NOTE:
If you plan to play around with keyboard control, we urge you to read
the comments on page 140 first and then apply the information in this
chapter to your programs only if you have a reason to do so (for
example, if you are creating a keyboard-enhancer program to modify the
operation of the keyboard; see the sidebar on page 133 for more
information on such programs). If you have any such application in mind,
take a look at the ROM BIOS keyboard services in Chapter 11.
──────────────────────────────────────────────────────────────────────────
The keyboard has undergone several modifications since the IBM PC was
released. The original IBM PC keyboard had 83 keys. The PC/AT was
introduced with an 84-key keyboard that changed the locations of several
keys on the 83-key keyboard and added one new key, the Sys Req key.
IBM later upgraded the AT with a 101/102-key keyboard that provided extra
function keys and a new keyboard layout. The 101/102-key keyboard became
standard equipment in the PS/2 series. The 101/102-key layout includes two
extra function keys (F11 and F12), a number of duplicate shift and control
keys, and modifications to several keys and keyboard combinations found in
the 83- and 84-key layouts (Pause, Alt-Sys Req, and Print Screen).
A trend in IBM's keyboard design has been to increase the similarity
between the PC and PS/2 keyboards and the keyboards on their mainframe
display terminals. For example, the 101/102-key keyboard's 12 function
keys (F1 through F12) are reminiscent of the Program Function (PF) keys on
IBM mainframe display terminals. Similarly, the Sys Req key is like the
Sys Req key in IBM mainframe terminals: A mainframe terminal-emulator
program running on a PC or PS/2 could use the Sys Req key for the same
purpose a mainframe terminal would──to switch among terminal sessions or
to initiate a keyboard reset function.
Another trend in IBM's keyboard design has been to accommodate non-English
alphabets in the keyboard layout. The English-language version of the
101/102-key keyboard released in the United States and United Kingdom has
101 keys, but for other languages the same keyboard has an extra key next
to the left Shift key, a different arrangement of keys around the Enter
key, and a different map of ASCII characters to key locations. From a
programmer's point of view, however, these two keyboards are so similar
that IBM describes them together in its technical documentation──and we'll
do the same in this chapter.
Keyboard Operation
The keyboard unit contains a dedicated microprocessor that performs a
variety of jobs, all of which help cut down on system overhead. The main
duty of the keyboard microprocessor is to watch the keys and report to the
main computer whenever a key is pressed or released. If any key is pressed
continuously, the keyboard microprocessor sends out a repeat action at
specific intervals. The keyboard microprocessor controller also has
limited diagnostic and error-checking capabilities and has a buffer that
can store key actions in the rare instance that the main computer is
temporarily unable to accept them.
The PC/AT and PS/2s have sophisticated keyboard control circuitry that can
perform several functions the original IBM PC and PC/XT keyboard cannot.
These features include programmable typematic control, programmable
scan-code sets, and improved hardware for error detection.
On the 83-key keyboard, the typematic delay and repeat rate are built into
the hardware: A key must be pressed for 0.5 seconds before auto-repeat
begins, and the repeat rate is about 10 characters per second. With the
PC/AT and PS/2 keyboards, you can modify the typematic delay and rate by
programming the keyboard controller. The most convenient way to do this is
through the ROM BIOS keyboard services described in Chapter 11.
The keyboard controller in the PC/AT and PS/2s can also assign any of
three different sets of scan-code values to the keys on the 84- and
101/102-key layouts. By default, however, the ROM BIOS establishes a
scan-code set that is compatible with that used on the 83-key keyboard.
You will probably find use for the alternative scan-code sets only if your
program bypasses the ROM BIOS and processes scan codes directly. (See the
PC/AT and PS/2 technical reference manuals for details.)
The improved error-detection ability of the AT and PS/2 keyboard
controllers is largely invisible to your programs; the keyboard hardware
and the ROM BIOS service routines are very reliable. The most common
errors you may encounter are a full ROM BIOS keyboard buffer or a key
combination that the PS/2 ROM BIOS cannot process. In both situations, the
ROM BIOS generates a warning beep to inform you that something unusual has
occurred. (For example, try holding down both pairs of Ctrl and Alt keys
on a PS/2 keyboard.)
Keystrokes and Scan Codes
Each time you press or release one of the keys on the keyboard, the
keyboard circuits transmit a sequence of one or more 8-bit numbers through
the connector cable to the computer. This sequence, called a scan code,
uniquely identifies the key you pressed. The keyboard produces different
scan codes, depending on whether the key was pressed or released. Whenever
you press a key, the scan-code byte contains a number ranging from 01H
through 58H. When you release the key, the keyboard generates a scan code
80H higher than the keystroke scan code by setting bit 7 of the scan-code
byte to 1. For example, when you press the letter Z, the keyboard
generates a scan code of 2CH; when you release it, the keyboard generates
a scan code of ACH (2CH + 80H). The keyboard diagrams in Figures 6-1,
6-2, and 6-3 show the standard keyboard keys and their associated scan
codes.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 6-1 can be found on p.128 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 6-1. Scan codes for the 83-key keyboard (PC, PC/XT). Scan-code
values are in hex.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 6-2 can be found on p.128 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 6-2. Scan codes for the 84-key keyboard (PC/AT). Scan-code values
are in hex.
┌────────────────────────────────────────────────────────────────────────┐
│ Figure 6-3 can be found on p.129 of the printed version of the book. │
└────────────────────────────────────────────────────────────────────────┘
Figure 6-3. Scan codes for the 101/102-key keyboard (PC/AT and PS/2).
Scan-code values are in hex.
If you compare the scan codes for the 83-, 84-, and 101/102-key keyboards,
you'll see that a key generates the same scan code regardless of its
location on the keyboard. For example, the Esc key has a scan code of 01H,
whether it's next to the 1 key, next to the Num Lock key, or by itself in
the upper-left corner. (The 101/102-key keyboard can actually generate
different scan codes, but the start-up ROM BIOS suppresses this by
configuring the keyboard to be compatible with the 83-key keyboard.)
The 101/102-key layout contains duplicate shift and control keys that
don't exist on the other keyboards. The 101/102-key keyboard distinguishes
between duplicate keys by transmitting multiple-byte scan codes. For
example, the two Alt shift keys have different scan codes: The left Alt
key has a scan code of 38H, and the right Alt key has a 2-byte scan code,
E0H 38H.
──────────────────────────────────────────────────────────────────────────
NOTE:
The multiple-byte scan codes for shift and control keys can vary
depending on whether one of the shift keys (Ctrl, Alt, Shift), Num Lock,
or Caps Lock is pressed at the same time. See IBM's PS/2 technical
reference manuals for details.
──────────────────────────────────────────────────────────────────────────
The 101/102-key keyboard also assigns special scan codes to certain
keystroke combinations. The Alt-Sys Req combination is intended to be the
same as the Sys Req key on the 84-key layout, so the 101/102-key keyboard
transmits the same scan code, 54H. Because the Print Screen key has the
same function as the Shift-PrtSc combination in the other keyboard
layouts, the 101/102-key keyboard transmits a Shift key scan code (E0H
2AH) followed by the PrtSc scan code (E0H 37H). The Pause key's scan code,
E1H 1DH 45H, resembles the scan-code sequence for the Ctrl-Num Lock
combination, but when you press Ctrl-Pause (that is, Ctrl-Break), the
keyboard transmits E0H 46H E0H C6H, which is derived from the scan code
for the Scroll Lock (Break) key on the 83- and 84-key keyboards. Figure
6-4 lists these keystroke combinations and their associated codes.
101/102-key Keyboard 84-key Keyboard Scan Code
Keystroke Combination Equivalent Transmitted
──────────────────────────────────────────────────────────────────────────
Alt-Sys Req Sys Req 54H
Print Screen Shift-Print Screen E0H 2AH
E0H 37H
Ctrl-Break Ctrl-Break E0H 46H E0H C6H
──────────────────────────────────────────────────────────────────────────
Figure 6-4. Scan codes for special keystroke combinations on the
101/102-key keyboard.
──────────────────────────────────────────────────────────────────────────
NOTE:
The "compact" keyboard available for the PS/2 Model 25 is really a
101/102-key keyboard in disguise. The numeric keypad is mapped to a
group of 14 keys on the main keyboard, and the Num Lock key is the shift
state of the Scroll Lock key. However, keyboard scan codes and ROM BIOS
processing are the same for the compact version as for the full-size
101/102-key keyboard.
──────────────────────────────────────────────────────────────────────────
Any program that processes keyboard scan codes must be aware of which
machine it's running on and which keyboard is in use. Fortunately, few
programs need to respond directly to keyboard scan codes──the ROM BIOS
keyboard service routines translate scan codes into meaningful information
that a program can use. The following sections describe this translation
process more fully.
Communicating with the ROM BIOS
The keyboard-controller circuitry on the computer's system board monitors
the keyboard for input. The keyboard controller generates interrupt 09H
each time it receives a byte of data from the keyboard. The ROM BIOS
contains an interrupt 09H handler that reads the byte from the keyboard
controller and processes it. (I/O port 60H contains the keyboard data
byte.) The interrupt 09H handler translates scan codes into 2-byte values
that are generally more useful to a program than the original scan codes.
The low-order byte of each 2-byte keyboard value contains the ASCII value
corresponding to each key pressed. The high-order byte usually contains
the corresponding keyboard scan code.
Special keys, such as the function keys and the numeric-keypad keys, have
a 0 in the low-order byte, with the keyboard scan code in the high-order
byte. (More about this later, on page 134.)
The ROM BIOS routines place the translated byte-pairs in a queue, which is
kept in low memory in location 0040:001EH. The byte-pairs are stored there
until they are requested by a program, such as DOS or interpreted BASIC,
that expects to read keyboard input.
Translating the Scan Codes
The scan-code translation job is moderately complicated because the IBM
keyboard recognizes two types of keys that change the meaning of a
keystroke: shift keys and toggle keys.
The shift keys
Three keys──Ctrl, Shift, and Alt──are known as shift keys: They change the
shift state, and thereby the meaning, of whatever key they are used with.
For example, when you press Shift-C, you get a capital C; when you press
Ctrl-C, you generate the "break" character. The ROM BIOS recognizes that
all subsequent key actions will be influenced by that shift state as long
as a shift key is pressed.
The toggle keys
In addition to the shift keys, two toggle keys also affect the keyboard's
shift state: the Caps Lock key and the Num Lock key. When activated, Caps
Lock reverses the shift state of the alphabet keys; it doesn't affect the
other keys. When activated, the Num Lock key disables cursor-control
functions on the numeric keypad. Toggle keys are activated with a single
keystroke and remain active until released by a second keystroke.
The shift-key and toggle-key status information is kept by the ROM BIOS in
a low-memory location (0040:0017H), where you can use or change it. When
you press a shift key or a toggle key, the ROM BIOS sets a specific bit in
one of these two bytes. When the ROM BIOS receives the release scan code
of a shift key, it switches the status bit back to its original shift
state.
Whenever the ROM BIOS receives a scan code for an ordinary keystroke, such
as the letter z or a right arrow key, it first checks the shift state and
then translates the key into the appropriate 2-byte code. (We'll discuss
the status bytes in more detail on page 137.)
The combination keys
While the ROM BIOS routine is translating scan codes, it checks for Sys
Req keystrokes and for certain shift-key combinations; specifically, it
checks for the Ctrl-Alt-Del, Shift-PrtSc, Ctrl-Num Lock, and Ctrl-Break
combinations. These five command-like key actions cause the ROM BIOS to
perform a specific task immediately.
Ctrl-Alt-Del causes the computer to reboot. Ctrl-Alt-Del is probably used
more often than any other special key combination. It works dependably as
long as the keyboard interrupt service is working. If the interrupt
service is not working, turn the power off, wait a few seconds, then turn
it on again; the power-on program resets all interrupt vectors and
services.
Shift-PrtSc (Print Screen on the 101/102-key keyboard) causes the ROM BIOS
interrupt 09H handler to execute software interrupt 05H. The default
interrupt 05H handler is also part of the ROM BIOS; it prints a "snapshot"
of the current contents of the screen.
Ctrl-Num Lock (Pause on the 101/102-key keyboard) suspends operation of a
program until another keystroke occurs.
Ctrl-Break causes the ROM BIOS to generate software interrupt 1BH and to
set bit 7 of the byte at 0040:0071H to 1. The default DOS handler for
interrupt 1BH simply sets a flag internal to DOS that causes DOS to
interpret Ctrl-Break as Ctrl-C. You can override the default DOS action
for Ctrl-Break by pointing the interrupt 1BH vector (located at
0000:006CH) to your own interrupt handler.
Sys Req (on the 84-key keyboard) and Alt-Sys Req (on the 101/102-key
keyboard) cause the ROM BIOS to issue interrupt 15H with AH = 85H. Your
program can provide its own interrupt 15H handler that intercepts and
processes Sys Req keystrokes. (See Chapter 12 for details.)
These are the only key combinations that are especially meaningful to the
ROM BIOS. When an invalid combination is reported from the keyboard, the
ROM BIOS simply ignores it and moves on to the next valid key action.
Two more features of the PC keyboard should be presented before we discuss
the details of keyboard coding: repeat key action and duplicate keys.
Repeat key action
The PC keyboard features automatic repeat key action, a process called
typematic by IBM. The circuitry inside the keyboard monitors how long each
key is pressed, and if a key is held down longer than a defined interval,
the circuitry generates repeat key actions. This typematic action is
reported as successive keystroke scan codes, without the intervening
key-release codes. This makes it possible for an interrupt 09H handler to
distinguish between actual keystrokes and typematic action. However, the
ROM BIOS does not always distinguish between the two. The ROM BIOS
keyboard-handling routine treats each automatic repeat key action as
though the key were actually pressed and interprets the key accordingly.
──────────────────────────────────────────────────────────────────────────
Keyboard-enhancer programs
Thanks to the flexible software design of the PC, it's possible to
create programs that customize the keyboard. Such programs are called
keyboard-enhancer programs.
Keyboard-enhancer programs monitor the scan codes that come in from the
keyboard and respond to them in ways that aren't supported by the ROM
BIOS or by DOS. Typically, these programs are fed instructions, called
keyboard macros, that tell them what keystrokes to look for and what
changes to make. The change might involve suppressing a keystroke
(acting as if it never happened), replacing one keystroke with another,
or replacing one keystroke with a long series of keystrokes. The most
common use of keyboard macros is to abbreviate frequently used phrases;
for example, you might instruct a keyboard enhancer to convert a key
combination, such as Alt-S, into a salutation you use in your
correspondence, such as Sincerely yours. You can also use keyboard
macros to condense multiple-keystroke program commands to a single
keystroke.
Keyboard enhancers work by combining the powers of two special
facilities──one that's part of DOS and one that's part of the PC's ROM
BIOS. The DOS facility allows the enhancer program to remain resident in
the computer's memory, quietly monitoring the operation of the computer
while the ordinary control of the computer is turned over to a
conventional program, such as a word processor. The ROM BIOS facility
lets programs divert the stream of keyboard information so that it can
be inspected and changed before it is passed on to a program. These
programs use the DOS Terminate and Stay Resident facility to stay active
in memory while other programs are run; then they use the ROM BIOS
keyboard-monitoring facility to preview keyboard data and change it as
needed.
──────────────────────────────────────────────────────────────────────────
For example, if you press and hold the A key long enough for the keyboard
to begin generating successive keystroke signals, then the ROM BIOS will
create a series of As to be passed on to whatever program is reading
keyboard data. On the other hand, when you press and hold a shift key, the
ROM BIOS sets bits in its status bytes in segment 40H. While you hold the
shift key down, the ROM BIOS continues to set the same bits to 1. When you
release the key, the ROM BIOS resets the status bits. All this boils down
to the simple fact that the ROM BIOS treats repeat key actions in a
sensible way, acting on them or ignoring them as needed.
Duplicate keys
We've already described how the keyboard differentiates duplicate keys by
assigning different scan codes to each. The ROM BIOS translates duplicate
keys into the same ASCII character codes. For example, if you press either
of the two asterisk keys, the ROM BIOS returns ASCII 2AH (the ASCII code
for an asterisk); if you press either of the two Ctrl keys on a
101/102-key keyboard, the ROM BIOS sets the appropriate bit in its
shift-state byte.
The ROM BIOS also lets programs tell the difference between duplicate
keys, in some cases. Remember that the ROM BIOS translates each keystroke
into a scan code as well as an ASCII code. A program that requests a
keystroke from the ROM BIOS can inspect the scan code to determine which
key was pressed. In the case of shift keys, a program can inspect the BIOS
shift-state bytes at 0040:0017H and 0040:0018H to determine exactly which
shift keys are pressed. (See the discussion of the shift-state bytes on
pages 137 and 138.)
Entering ASCII Codes Directly
We should mention that the PC keyboard, in conjunction with the ROM BIOS,
provides an alternate way to enter nearly any ASCII character code. This
is done by holding down the Alt key and then entering the decimal ASCII
character code from the numeric keypad on the right side of the keyboard.
This method lets you enter any ASCII code from 01H through FFH (decimal 1
through 255).
Keyboard Data Format
Once a keyboard action is translated, it is stored as a pair of bytes in
the ROM BIOS buffer. We call the low-order byte the main byte and the
high-order byte the auxiliary byte. The contents of these bytes will vary,
depending on whether an ASCII key or a special key was pressed.
The ASCII Keys
When the main byte is an ASCII character value from 01H to FFH, one of two
events has occurred: One of the standard keyboard characters was pressed,
or an ASCII character was entered directly using the Alt-number method
mentioned above. (See Appendix C for the complete ASCII character set.)
For these ASCII characters, the auxiliary byte contains the scan code of
the pressed key. (The scan code is 0 for characters entered with
Alt-number.) Usually you can ignore this scan code. DOS does not report
keyboard scan codes, nor do high-level programming language functions like
getch() in C or INKEY$ in BASIC. However, a program can examine the
auxiliary byte (scan code) to differentiate among duplicate keyboard
characters.
The Special Keys
When the main byte is null (00H), it means that a special, non-ASCII key
was pressed. The special keys include function keys, shifted function
keys, cursor-control keys such as Home and End, and some of the Ctrl- and
Alt-key combinations. When any of these keys are pressed by themselves or
in combination with other keys, the auxiliary byte contains a single value
that indicates which key was pressed. Figure 6-5 lists these values in a
rough mixture of logical and numeric order. (For a complete breakdown of
ROM BIOS key codes, see the IBM BIOS Interface Technical Reference
Manual.)
──────────────────────────────────────────────────────────────────────────
NOTE:
With the 101/102-key keyboard, the main byte value for the gray
cursor-control keys is E0H. This value distinguishes these keys from
their counterparts on the numeric keypad, which have a main byte value
of 00H.
──────────────────────────────────────────────────────────────────────────
╓┌─┌──────────────────┌─────────────────┌────────────────────────────────────╖
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
3BH 59 F1
3CH 60 F2
3DH 61 F3
3EH 62 F4
3FH 63 F5
40H 64 F6
41H 65 F7
42H 66 F8
43H 67 F9
44H 68 F10
85H 133 F11
86H 134 F12
54H 84 Shift-F1
55H 85 Shift-F2
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
55H 85 Shift-F2
56H 86 Shift-F3
57H 87 Shift-F4
58H 88 Shift-F5
59H 89 Shift-F6
5AH 90 Shift-F7
5BH 91 Shift-F8
5CH 92 Shift-F9
5DH 93 Shift-F10
87H 135 Shift-F11
88H 136 Shift-F12
5EH 94 Ctrl-F1
5FH 95 Ctrl-F2
60H 96 Ctrl-F3
61H 97 Ctrl-F4
62H 98 Ctrl-F5
63H 99 Ctrl-F6
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
63H 99 Ctrl-F6
64H 100 Ctrl-F7
65H 101 Ctrl-F8
66H 102 Ctrl-F9
67H 103 Ctrl-F10
89H 137 Ctrl-F11
8AH 138 Ctrl-F12
68H 104 Alt-F1
69H 105 Alt-F2
6AH 106 Alt-F3
6BH 107 Alt-F4
6CH 108 Alt-F5
6DH 109 Alt-F6
6EH 110 Alt-F7
6FH 111 Alt-F8
70H 112 Alt-F9
71H 113 Alt-F10
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
71H 113 Alt-F10
8BH 139 Alt-F11
8CH 140 Alt-F12
78H 120 Alt-1
79H 121 Alt-2
7AH 122 Alt-3
7BH 123 Alt-4
7CH 124 Alt-5
7DH 125 Alt-6
7EH 126 Alt-7
7FH 127 Alt-8
80H 128 Alt-9
81H 129 Alt-0
82H 130 Alt-Hyphen
83H 131 Alt-=
10H 16 Alt-Q
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
10H 16 Alt-Q
11H 17 Alt-W
12H 18 Alt-E
13H 19 Alt-R
14H 20 Alt-T
15H 21 Alt-Y
16H 22 Alt-U
17H 23 Alt-I
18H 24 Alt-O
19H 25 Alt-P
1EH 30 Alt-A
1FH 31 Alt-S
20H 32 Alt-D
21H 33 Alt-F
22H 34 Alt-G
23H 35 Alt-H
24H 36 Alt-J
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
24H 36 Alt-J
25H 37 Alt-K
26H 38 Alt-L
2CH 44 Alt-Z
2DH 45 Alt-X
2EH 46 Alt-C
2FH 47 Alt-V
30H 48 Alt-B
31H 49 Alt-N
32H 50 Alt-M
0FH 15 Shift-Tab
47H 71 Home
48H 72 Up arrow
49H 73 PgUp
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
4BH 75 Left arrow
4DH 77 Right arrow
4FH 79 End
50H 80 Down arrow
51H 81 PgDn
52H 82 Insert
53H 83 Del
72H 114 Ctrl-PrtSc
73H 115 Ctrl-Left arrow
74H 116 Ctrl-Right arrow
75H 117 Ctrl-End
76H 118 Ctrl-PgDn
77H 119 Ctrl-Home
Value
(hex) (dec) Keys Pressed
──────────────────────────────────────────────────────────────────────────
77H 119 Ctrl-Home
84H 132 Ctrl-PgUp
──────────────────────────────────────────────────────────────────────────
Figure 6-5. ROM BIOS auxiliary byte values for the special keys.
Codes generated by the ROM BIOS for the complete set of characters and
special keys are handled differently in different programming languages.
BASIC, for example, takes a mixed approach to the special keys. When you
use ordinary input statements, BASIC returns the ASCII characters and
filters out any special keys. Some of these keys can be acted on with the
ON KEY statement, but you can use the BASIC INKEY$ function to get
directly to the ROM BIOS coding for keyboard characters and find out
immediately what special key was pressed. If the INKEY$ function returns a
1-byte string, it is reporting an ordinary or extended ASCII keyboard
character. If INKEY$ returns a 2-byte string, the first byte in the string
is the ROM BIOS's main byte and will always be 00H; the second byte is the
auxiliary byte and will indicate which special key was pressed.
ROM BIOS Keyboard Control
The ROM BIOS stores keyboard status information in several portions of the
ROM BIOS data area in segment 40H in low memory. Your programs can use
some of the ROM BIOS status variables to check the keyboard status or to
modify ROM BIOS keyboard processing.
The two keyboard status bytes at locations 0040:0017H (shown in Figure
6-6) and 0040:0018H (shown in Figure 6-7) are coded with individually
meaningful bits that indicate which shift keys and toggle keys are active.
All the standard models of the PC family have these two bytes, although
the bits representing the Sys Req, left Alt, and left Ctrl keys are
updated only for the keyboards that support these keys.
The status byte at 0040:0017H is particularly useful because it
establishes the state of ROM BIOS keystroke processing. Changes to this
status byte affect the next keystroke that the ROM BIOS processes.
Bit
7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
X . . . . . . . Insert state: 1 = active; 0 = inactive
. X . . . . . . Caps Lock: 1 = active; 0 = inactive
. . X . . . . . Num Lock: 1 = active; 0 = inactive
. . . X . . . . Scroll Lock: 1 = active; 0 = inactive
. . . . X . . . 1 = Alt pressed
. . . . . X . . 1 = Ctrl pressed
. . . . . . X . 1 = Left Shift pressed
. . . . . . . X 1 = Right Shift pressed
──────────────────────────────────────────────────────────────────────────
Figure 6-6. The coding of the keyboard status byte at location
0040:0017H. Bits 4─7 are toggles; their values change each time the key is
pressed. Bits 0─3 are set only while the corresponding key i s pressed.
Bit
7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
X . . . . . . . 1 = Ins pressed
. X . . . . . . 1 = Caps Lock pressed
. . X . . . . . 1 = Num Lock pressed
. . . X . . . . 1 = Scroll Lock pressed
. . . . X . . . 1 = Hold state active (Ctrl-Num Lock or Pause)
. . . . . X . . 1 = Sys Req key pressed
. . . . . . X . 1 = Left Alt key pressed
. . . . . . . X 1 = Left Ctrl key pressed
──────────────────────────────────────────────────────────────────────────
Figure 6-7. The coding of the keyboard status byte at location
0040:0018H. These bits are set only while the corresponding key is
pressed.
The Insert State
The ROM BIOS keeps track of the insert state in bit 7 of byte 0040:0017H.
However, every program we know of ignores this bit and keeps its own
record of the insert state. This means that you should not rely on this
status bit to tell you anything about the current state of Insert key
processing.
The Caps Lock State
Some programmers force the Caps Lock state to be active by setting bit 6
of byte 0040:0017H. This can confuse or irritate some program users, so we
don't recommend it. However, this trick works reliably and precedent
exists for using it. If you do you'll see that the ROM BIOS updates the
LED indicator on the 84- and 101/102-key keyboards accordingly. This also
occurs when you update the Num Lock or Scroll Lock states.
The Num Lock State
Because the Num Lock key's location on the keyboard makes it susceptible
to inadvertent keystrokes, some programmers force the Num Lock toggle (bit
5 of byte 0040:0017H) to a predetermined state at the beginning of a
program. For example, clearing the Num Lock status bit before requesting
user input from the keypad forces keypad keystrokes to be processed as
direction keys instead of numbers, even if the Num Lock key was pressed
accidentally. This can be particularly helpful with the 83-key keyboard
for the IBM PC and PC/XT because this keyboard has no status LEDs and
provides no visual indication of the Num Lock state.
The Keyboard-Hold State
The ROM BIOS establishes the keyboard-hold (pause) state when it detects a
Ctrl-Num Lock or Pause keystroke. During keyboard hold, the ROM BIOS
executes a do-nothing loop until a printable key is pressed; it doesn't
return control of the computer to whatever program is running until this
happens. This feature is used to suspend the operation of the computer.
During keyboard hold, all hardware interrupts are handled normally. For
example, if a disk drive generates an interrupt (signaling the completion
of a disk operation), the disk interrupt handler receives the interrupt
and processes it normally. But when the interrupt handler finishes
working, it passes control back to whatever was happening when the
interrupt took place──which is that endless do-nothing loop inside the ROM
BIOS. So, during the keyboard hold, the computer can respond to external
interrupts but programs are normally completely suspended. The keyboard
BIOS continues to handle interrupts that signal key actions, and when it
detects a normal keystroke (for example, the Spacebar or a function key,
but not just a shift key), it ends the keyboard hold, finally returning
control to whatever program was running.
The keyboard-hold state is of no practical use in programming, except that
it provides a standard way for users of our programs to suspend a
program's operation.
Be aware that the keyboard-hold state is not "bullet-proof." A program can
continue working through the keyboard hold by acting on an external
interrupt, such as the clock-tick interrupt. If a program really wanted to
avoid being put on hold, it could set up an interrupt handler that would
work through the hold state, or it could simply turn the hold state off
whenever the hold state was turned on.
The Toggle-Key States
Notice that bits 4 through 7 in the bytes at 0040:0017H and 0040:0018H
refer to the same keys. In the first byte, the bits show the current state
of the toggle keys; in the second byte, they show whether or not the
corresponding toggle key is pressed.
You can read the status of any of these bits to your heart's content, but
few, if any, are likely to be useful in your programs. With the partial
exception of controlling the Caps Lock state, we don't think it's wise to
change any of the shift-state bits (bits 4 through 6 of byte 0040:0017H).
And it is potentially very disruptive to change any of the key-is-pressed
bits (bits 0 through 3 of byte 0040:0017H or any bits in byte 0040:0018H).
Comments
If you want to gain a deeper understanding of the PC's keyboard operation,
study the ROM BIOS program listing in the IBM technical reference manuals
for the PC, PC/XT, or PC/AT. If you do this, be careful to avoid making a
simple mistake that is common when anyone first studies the ROM BIOS,
particularly the interrupts used by the ROM BIOS. The ROM BIOS provides
two different interrupts for the keyboard: one that responds to keyboard
hardware interrupts (interrupt 09H) and collects keyboard data into the
low-memory buffer, and one that responds to a software interrupt
requesting keyboard services (interrupt 16H, decimal 22) and passes data
from the low-memory buffer to DOS and your programs. It is easy to confuse
the operation of these two interrupts, and it is just as easy to further
confuse them with the break-key interrupts, 1BH and 23H (decimal 27 and
35). The table in Figure 6-8 lists the keyboard interrupts.
Interrupt
Hex Dec Origin of Use
Interrupt
──────────────────────────────────────────────────────────────────────────
09H 9 Keyboard Signals keyboard action.
16H 22 User program Invokes standard BIOS keyboard
services. (See Chapter 11.)
1BH 27 ROM BIOS Occurs when Ctrl-Break is pressed
under BIOS control; a routine is
invoked if you create it.
23H 35 DOS If you create it, an interrupt
routine is invoked when a break-key
combination is pressed under DOS
control.
──────────────────────────────────────────────────────────────────────────
Figure 6-8. The interrupts related to keyboard action.
A general theme running throughout this book advises you not to play fast
and loose, but to play by the rules. This means, again, to write programs
that are general to the IBM PC family rather than tied to the quirks of
any one model, and to write programs that use portable means (such as DOS
or ROM BIOS services) to manipulate data, instead of direct hardware
programming. These rules apply to keyboard programming as much as they do
to any other type of programming.
────────────────────────────────────────────────────────────────────────────
Chapter 7 Clocks, Timers, and Sound Generation
Clocks and Timers
The CPU Clock
System Timers
Using the System Timer Tick
The Physics of Sound
How the Computer Produces Sound
Timer-Chip Sound Control
Direct Speaker Control
Speaker Volume and Sound Quality
The Real-Time Clock
Using the Date and Time
Setting the Alarm
Clocks and timers are the heartbeat of a computer. The computer's
essential functions of computation and data transfer take place in step
with the pulses generated by electronic clocks. PCs and PS/2s play host to
several clocks and timers that you should know about:
■ The system timer generates "clock-ticks" and other timing pulses at
precisely controlled intervals.
■ The sound generator produces tones through a speaker with a wide range
of frequencies and durations.
■ The real-time clock/calendar keeps track of the date and time and can
also serve as an "alarm clock." (This is supported only in the PC/AT
and PS/2s.)
To understand how to use the system timer, the sound generator, and the
real-time clock, you need to know about the basic clock and timing
mechanisms in PCs and PS/2s. That is what we'll outline in this chapter.
Clocks and Timers
PCs and PS/2s have several clocks and timers that run at different rates
and perform different functions. Some of them are intrinsic to the circuit
design of these computers; their operation is independent of software
control. Others are designed to support timing functions in software; the
operation of these timers can be controlled by software through ROM BIOS
services or by direct hardware programming.
The CPU Clock
Probably the most basic of the timed events in a PC or PS/2 is the
step-by-step operation of the computer's CPU, whose speed is determined by
the frequency of a special oscillator circuit that generates
high-frequency pulses at regular intervals. This frequency is the CPU's
clock speed, and it determines how quickly the CPU can carry out its
functions.
The CPU oscillator keeps time for the CPU in much the same way a metronome
keeps time for a musician. At each tick of the CPU clock (that is, at each
pulse in the CPU oscillator's signal), the CPU carries out part of one
machine instruction. All instructions require two or more clock cycles to
complete. For example, the register INC instruction requires two clock
cycles to execute; more complicated instructions like CALL and MUL take a
longer amount of time.
In IBM PCs and PC/XTs, the CPU's clock speed is 4,772,727 cycles per
second, or about 4.77 megahertz. (A megahertz, or MHz, is one million
cycles per second.) One CPU clock cycle thus lasts about 1/4,772,727 of a
second, or about 210 nanoseconds (billionths of a second). With this clock
frequency, a 2-cycle INC instruction executes in roughly 420 nanoseconds
(0.42 microseconds or millionths of a second).
The odd clock speed of 4.77 MHz was actually a convenient frequency for
the designers of the original PC to use. In fact, the CPU clock frequency
is derived from a basic oscillator frequency of 14.31818 MHz, which is
commonly used in television circuitry. Dividing the basic frequency by 3
gives the CPU clock frequency. Dividing by 4 gives a clock rate of 3.57955
MHz, which is the frequency of the color burst signal used in color
televisions and in the PC's Color Graphics Adapter. Dividing the basic
frequency by 12 gives 1.19318 MHz, which is the clock frequency used by
the PC's system timers.
In later, faster members of the PC and PS/2 family, the CPU clock speed is
higher, so the overall computational speed of these computers is greater.
The 80286 and 80386 processors also execute many machine instructions in
fewer clock cycles than the 8088 used in the PC and PC/XT. For example,
the register PUSH instruction in the 8088 executes in 15 clock cycles; in
the 80286 the same instruction takes 3 cycles; and in the 80386 only 2
cycles. The combination of a higher CPU clock rate and faster machine
instructions means that the 80286- and 80386-based members of the PC
family execute programs significantly faster than do the 8088- and
8086-based machines. (See Figure 7-1.)
Approximate Speed
CPU Clock Relative to
Model CPU Frequency 4.77 MHz IBM PC
──────────────────────────────────────────────────────────────────────────
PC 8088 4.77 MHz 1.0
PC/XT 8088 4.77 MHz 1.0
PC/AT 80286 6 MHz 3.4
8 MHz 4.8
PS/2 models 25 and 30 8086 8 MHz 2.5
PS/2 models 50 and 60 80286 10 MHz 6.1
PS/2 Model 80 80386 16 MHz 12.5
20 MHz 15.5
──────────────────────────────────────────────────────────────────────────
Figure 7-1. CPU clock frequencies and relative computation speeds for PCs
and PS/2s.
System Timers
Apart from the operation of the CPU, other basic hardware and software
functions occur at regular intervals based on a preset clock frequency.
For example, the dynamic RAM chips that constitute the computer's main
memory must be accessed at regular intervals to refresh the information
represented in them. Also, ROM BIOS and operating system functions such as
keeping track of the time of day require the computer to generate a
"clock-tick" signal at a predetermined rate. All PCs and PS/2s have
circuitry that generates the necessary timing signals.
In the PC and PC/XT, an Intel 8253-5 programmable timer/counter chip
produces the RAM refresh and timer-tick signals. In the PC/AT, an Intel
8254-2 is used in the same way. The PS/2 models 25 and 30 use an 8253-5
for the timer tick, but RAM refresh timing is a function of a custom
integrated circuit. In the PS/2 models 50, 60, and 80, all timing
functions are implemented in custom silicon. Despite these hardware
variations, the timer programming interface is the same in all PCs and
PS/2s.
In the PC/XT/AT family, the timer chip has three output channels, each
with a particular dedicated function:
■ Channel 0 is the system clock-tick timer. When the computer is cold
booted, the ROM BIOS programs the timer to oscillate with a frequency
of about 18.2 ticks per second. This signal is tied to the computer's
interrupt controller in such a way that interrupt 08H is generated each
time the clock ticks.
■ Channel 1 is always dedicated to producing the RAM refresh timing
signal; it's not intended for use in software applications.
■ Channel 2 is used to control the computer's speaker: The frequency of
the timer's channel 2 signal determines the frequency of the sound
emitted by the loudspeaker. (We'll come back to this later.)
PS/2 models 50, 60, and 80 also have a timer channel 3. The signal
produced on channel 3 is tied to the computer's nonmaskable interrupt
(interrupt 02H), and can be used by an operating system as a "watchdog" to
ensure that some other critical function, such as servicing a clock-tick
interrupt, does not crash the computer by taking too long to execute.
Using the System Timer Tick
In all PCs and PS/2s, the input oscillator to the system timer circuit has
a frequency of 1.19318 MHz. On each cycle, the timer chip decrements the
values in a set of internal 16-bit counters, one for each of the timer's
output channels. When the value in a counter reaches 0, the chip generates
a single output pulse on the corresponding channel, resets the count, and
starts counting down again.
When the ROM BIOS initializes the system timer, it stores a countdown
value of 0 in the count register for channel 0. This means that the timer
chip decrements the counter 2^16 times between output pulses on channel 0,
so output pulses occur 1,193,180/65,536, or about 18.2 times per second.
The output from timer channel 0 is used as the signal on interrupt request
level 0 (IRQ0), so interrupt 08H occurs whenever channel 0 of the system
timer counts down to 0──that is, 18.2 times per second.
The ROM BIOS contains an interrupt handler for interrupt 08H that
increments a running count of clock ticks at 0040:006CH in the BIOS data
area. This same interrupt handler also decrements the byte at 0040:0040H;
if the value in the byte reaches 0, the interrupt handler issues a command
to the diskette drive controller to turn off the diskette drive motor if
it's on.
The ROM BIOS interrupt 08H handler also issues software interrupt 1CH,
which is intended for use in programs that want to be notified when a
system timer tick occurs. A program can detect when each timer tick occurs
simply by pointing the interrupt 1CH vector at 0000:0070H to its own
interrupt handler. If you use an interrupt 1CH handler in a program,
however, be aware that the ROM BIOS interrupt 08H handler does not allow
subsequent clock-tick interrupts on IRQ0 to occur until your interrupt 1CH
handler returns. If you install an interrupt 1CH handler, be certain that
it doesn't keep IRQ0 disabled for too long or the system may crash.
The system timer tick and its interrupt are useful in programs that must
perform a simple task at a regular interval regardless of what else is
going on in the computer. The timer-tick interrupt has the highest
priority of any of the hardware interrupts (except the nonmaskable
interrupt), so the code in the corresponding interrupt 08H and 1CH
handlers takes precedence over all other system software.
For this reason, the timer tick is used primarily in operating system
software and in memory-resident "pop-up" programs like SideKick or the
Norton Guides. Such programs have their own timer-tick interrupt handlers
that check whether it is time to pop up on the screen. These programs
generally rely on the system timer tick to occur at the default frequency
of 18.2 ticks per second.
Because timer-tick function is so essential to the proper operation of the
computer, you should change the output frequency of system timer channel 0
only if you are careful to preserve the functionality of the ROM BIOS
interrupt 08H handler. For example, BASIC uses the timer tick to measure
the duration of tones created with the PLAY or SOUND command. However,
because the standard rate of 18.2 ticks per second is not fast enough to
provide the precision that some kinds of music demand, BASIC reprograms
the timer to tick four times faster, which causes interrupt 08H to occur
72.8 times per second instead of 18.2 times per second. When BASIC counts
against the quadruple rate, it is able to more accurately reproduce the
proper tempo of a piece of music.
BASIC can do this because it has a special interrupt 08H handler that
calls the default interrupt 08H handler on every fourth timer tick. This
ensures that the usual interrupt 08H functions still occur 18.2 times per
second. If you reprogram system timer channel 0 to a nonstandard rate,
your program should use the same technique of preserving interrupt 08H
functionality.
Programming system timer channel 2, the sound frequency generator, is not
as demanding, because no ROM BIOS or operating system functions rely on
it. Before we cover the programming details, however, we'll describe some
of the basic mechanics of creating sounds with a computer.
The Physics of Sound
Sounds are simply regular pulses or vibrations in air pressure. Sound is
produced when air particles are set into motion by a vibrating source.
When the vibrating source pushes out, it compresses the air particles
around it. As it pulls in, the pressure release pulls the particles apart.
A vibration composed of both the pressing and the pulling actions causes
air particles to bump into each other. This motion begins a chain reaction
that carries the vibration through the air away from the original source.
Such a motion is called a sound wave.
The speaker in the IBM PCs and PS/2s is made to vibrate by the electrical
impulses sent to it by the computer. Because computers normally deal with
binary numbers, the voltages they produce are either high or low. Every
transition from one voltage state to another either pushes the speaker
cone out or relaxes it. A sound is produced when the voltage to the
speaker goes from low to high to low again, causing the speaker to move
out and then in. This single vibration, consisting of a pulse out and a
pulse in, is called a cycle. Through the speaker, a single cycle of sound
is heard as a click. A continuous sound is produced when a continuous
stream of pulses is sent to the speaker. As the pulse rate increases, so
does the pitch of the tone. For example, if you pulse the speaker in and
out 261.63 times a second (that is, at a rate of 261.63 hertz, or cycles
per second), you hear the musical note known as middle C. Figure 7-2
lists the frequencies required to generate other musical notes.
╓┌─┌───────┌───────────────┌──────┌───────────────┌───────┌───────────────┌──►
Note Frequency Note Frequency Note Frequency Note
───────────────────────────────────────────────────────────────────────────
C0 16.35 C2 65.41 C4 261.63 C6
C#0 17.32 C#2 69.30 C#4 277.18 C#6
D0 18.35 D2 73.42 D4 293.66 D6
D#0 19.45 D#2 77.78 D#4 311.13 D#6
E0 20.60 E2 82.41 E4 329.63 E6
F0 21.83 F2 87.31 F4 349.23 F6
F#0 23.12 F#2 92.50 F#4 369.99 F#6
Note Frequency Note Frequency Note Frequency Note
───────────────────────────────────────────────────────────────────────────
F#0 23.12 F#2 92.50 F#4 369.99 F#6
G0 24.50 G2 98.00 G4 392.00 G6
G#0 25.96 G#2 103.83 G#4 415.30 G#6
A0 27.50 A2 110.00 A4 440.00 A6
A#0 29.14 A#2 116.54 A#4 466.16 A#6
B0 30.87 B2 123.47 B4 493.88 B6
C1 32.70 C3 130.81 C5 523.25 C7
C#1 34.65 C#3 138.59 C#5 554.37 C#7
D1 36.71 D3 146.83 D5 587.33 D7
D#1 38.89 D#3 155.56 D#5 622.25 D#7
E1 41.20 E3 164.81 E5 659.26 E7
F1 43.65 F3 174.61 F5 698.46 F7
F#1 46.25 F#3 185.00 F#5 739.99 F#7
G1 49.00 G3 196.00 G5 783.99 G7
G#1 51.91 G#3 207.65 G#5 830.61 G#7
A1 55.00 A3 220.00 A5 880.00 A7
A#1 58.27 A#3 233.08 A#5 932.33 A#7
B1 61.74 B3 246.94 B5 987.77 B7
C8
Note Frequency Note Frequency Note Frequency Note
───────────────────────────────────────────────────────────────────────────
C8
───────────────────────────────────────────────────────────────────────────
Note: Equal Tempered Chromatic Scale; A4 = 440
American Standard pitch──adopted by the American Standards
Association in 1936
Figure 7-2. Eight octaves of musical note frequencies.
The average person can hear sounds ranging from roughly 20 to 20,000
hertz. The IBM PC can generate sounds through its speaker at frequencies
that could theoretically range from about 18 to more than a million hertz,
far beyond the range of human hearing. To give this frequency range some
perspective, compare it to an average human voice, which has a range of
only 125 to 1000 hertz.
The speaker that comes with the standard IBM personal computers has no
volume control and is not really intended for accurate sound reproduction.
As a result, different frequencies will produce different effects; some
may sound louder than others and some may have a more accurate pitch. This
variation is a by-product of the speaker design and is not something you
can control.
How the Computer Produces Sound
You can generate sounds through the speaker in two ways, using one or both
of two different sound sources. One method is to write a program that
turns the speaker on and off by manipulating two speaker bits in the I/O
port that provides access to the speaker-control circuitry. When you use
this method, your program controls the timing of the pulse and the
resulting sound frequency. The other method is to use channel 2 of the
system timer chip to pulse the speaker at a precise frequency. Using the
timer chip is a more popular method for two reasons: Because speaker
pulses are controlled by the timer chip instead of a program, the CPU can
devote its time to the other demands of the computer system; and the timer
chip is not dependent on the working speed of the CPU, which varies
according to which PC or PS/2 model you use. The program method and timer
method can be used together or separately to create many simple and
complex sounds.
Timer-Chip Sound Control
The programmable timer chip is the heart of the standard PC models'
sound-making abilities. As we have seen, channel 2 of the timer chip is
dedicated to sound generation. To create sounds, you must program channel
2 properly and then use the pulses from channel 2 to drive the speaker.
The timer can be programmed to produce pulses at whatever frequency you
want, but because it does not keep track of how long the sound continues,
the sound will continue forever unless it is turned off. Therefore, your
programs must choose when to end a sound through some sort of timing
instruction.
Programming the timer chip
To program timer channel 2, load the timer chip with an appropriate
countdown value for the channel 2 counter. (The timer chip holds this
value in an internal register so that it can reset the counter each time
it reaches zero.) The countdown value takes effect immediately after you
load it into the timer chip. The timer chip decrements the counter with
each cycle of its 1.19318 MHz clock until the counter reaches zero, and
then it sends an output pulse on channel 2 to the sound generator
circuitry and starts counting down again.
In effect, the timer "divides" the countdown value into the clock
frequency to produce an output frequency. The result is that the timer
sends out a series of pulses that produce a sound of a certain frequency
when you turn on the speaker.
The controlling count and the resulting frequency have a reciprocal
relationship, as shown by these formulas:
Count=1,193,180÷Frequency
Frequency=1,193,180÷Count
You can see that a low-frequency (low-pitched) sound is produced by a high
count and that a high-frequency (high-pitched) sound is produced by a low
count. A count of 100 would produce a high pitch of roughly 11,931 cycles
per second, and a count of 10,000 would produce a low pitch of about 119
cycles per second.
You can produce just about any frequency, within the limitations of 16-bit
arithmetic. The lowest frequency is 18.2 hertz with a divisor of 65,535
(FFFFH), and the highest is 1.193 megahertz with a divisor of 1. BASIC
holds this to a practical range of 37 through 32,767 hertz. The following
program demonstrates that the actual frequency range of the internal
speaker is even less than BASIC provides.
Once you calculate the count that you need for the frequency you want, you
send it to the timer channel 2 registers. This is done with three port
outputs. The first port output notifies the timer that the count is coming
by sending the value B6H (decimal 182) to port 43H (decimal 67). The next
two outputs send the low- and high-order bytes of the count, a 16-bit
unsigned word, to port 42H (decimal 66)──the low-order byte followed by
the high-order byte. The BASIC program on the following page illustrates
the process.
10 COUNT = 1193280! / 3000 ' 3000 is the desired frequency
20 LO.COUNT = COUNT MOD 256 ' calculate low-order byte value
30 HI.COUNT = COUNT / 256 ' calculate high-order byte value
40 OUT &H43, &HB6 ' get timer ready
50 OUT &H42, LO.COUNT ' load low-order byte
60 OUT &H42, HI.COUNT ' load high-order byte
Activating the speaker
After you have programmed the timer, you still need to activate the
speaker circuitry in order to use the signal that the timer is generating.
As with most other parts of the PC and PS/2, the speaker is manipulated by
sending certain values to a specific port, a process illustrated in Figure
7-3. The speaker is controlled by changing the values of bits 0 and 1 at
I/O port 61H (decimal 97). Only 2 of the port's 8 bits are used by the
speaker: the low-order bits numbered 0 and 1. The other 6 bits are used
for other purposes, so it is important that you don't disturb them while
working with the speaker.
Get Send pulses
timer Port to
┌─────┐ ready 43H ┌──────────────┐speaker┌───────────┐
│ ├──────────────────►│ ├──────►│ │
│ │ Load │ 8253-5 │ │ │
│ │ frequency Port │ Programmable │ │ │ ┌─────┐
│ │ count 42H │ timer │ │ │ ┌─┘ │
│ CPU ├──────────────────►│ │ │ Amplifier ├─►│Speaker│
│ │ └──────────────┘ │ │ └─┐ │
│ │ Turn on Port │ │ └─────┘
│ │ speaker 61H │ │
│ ├─────────────────────────────────────────►│ │
└─────┘ └───────────┘
Figure 7-3. How sound frequencies are generated through the system timer
and speaker.
The lowest bit, bit 0, controls transmission of the timer chip's output
signal to the speaker. The second bit, bit 1, controls the pulsing of the
speaker. Both bits must be set to make the speaker respond to the timer
chip's signal. You can turn them on without disturbing the nonspeaker bits
with an operation like this:
70 OLD.PORT = INP (&H61) ' read the value at port 61H
80 NEW.PORT = (OLD.PORT OR &H03) ' set bits 0 and 1
90 OUT &H61, NEW.PORT ' turn speaker on
Direct Speaker Control
The timer controls the speaker by sending periodic signals that pulse the
speaker in and out. You can do the same thing with a program that sends in
or out signals directly to the speaker. Do this by setting bit 0 of port
61H (decimal 97) to 0 to turn the speaker off and then alternately setting
bit 1 on and off to pulse the speaker. When you use this method, the speed
of the program determines the frequency of the sound; the faster the
program executes, the higher the pitch. The following BASIC program is an
example of this method:
10 X = INP (&H61) AND &HFC ' read port value, turn off bits 1 and 0
20 OUT &H61, X ' pull speaker in
30 OUT &H61, X OR 2 ' push speaker out
40 GOTO 20
The actions in lines 20 and 30 pulse the speaker in and out. Each one is a
half-cycle, and the two together produce one complete sound cycle.
This example runs as fast as BASIC can process it, producing as high a
note as possible. If you needed more range in your application, you could
use a faster language and insert deliberate delays equal to half the
frequency cycle time between each complete cycle (half the cycle time,
because each ON or OFF operation is a half-cycle). No matter what language
you use, you must include a duration count to end the sound. To produce
different sounds at a particular frequency, such as clicking or buzzing
sounds, just vary the delays between pulses.
Despite all these wonderful possibilities, generating sounds through the
speaker by direct program action is not a good way to make sounds. It has
three big disadvantages compared to the use of the timer:
■ A program requires the constant attention of the CPU, so the computer
has a hard time getting any other work done.
■ The frequency is at the mercy of the speed of the computer; that is,
the same program would make a lower or higher sound on a slower or
faster model.
■ The clock-tick interrupts interfere with the smoothness of the sound,
making a warble. The only way to avoid this is to suspend the clock
tick by disabling the interrupts──and that disrupts the computer's
sense of time.
As far as we know, there is only one advantage to making sounds using the
direct method: With the proper control over the program delays, the direct
method lets you make a low-fidelity polyphonic sound. Be forewarned,
though, that this requires some very clever and tedious programming and,
all in all, may not be worth the trouble.
Speaker Volume and Sound Quality
The computer's internal speaker has no volume control of any kind and,
like all speakers, varies in how well it responds to different
frequencies; some frequencies may sound louder than others. In the case of
a crude speaker like that found in most PCs and PS/2s, the loudness of the
sound varies widely with the frequency. You can use the following program
to test this──it may help you choose the best sound pitch for your
purpose:
10 PLAY "MF" ' plays each sound separately
20 FREQUENCY = 37
30 WHILE FREQUENCY < 32000 ' use all frequencies to
32000 Hz
40 PRINT USING "##,###"; FREQUENCY ' display frequency
50 SOUND FREQUENCY, 5 ' produce sound with
duration of 5
60 FREQUENCY = FREQUENCY * 1.1 ' increment frequency by 1/10
70 WEND
Be aware that the speakers in the various PC and PS/2 models may not sound
alike, partly because the materials of each system housing resonate
differently as speaker enclosures. Try the following samples on two
different models and be prepared for these variations in sound:
100 'sound samples
110 '
120 'warble (two rapidly alternating tones)
130 FOR N% = 0 TO 5
140 SOUND 440, .7
150 SOUND 466.16, .5
160 NEXT
170 WHILE(INKEY$="") : WEND ' wait for a keystroke
180 '
190 'two tones played quickly
200 SOUND 900, .1
210 SOUND 760, 1
220 WHILE(INKEY$="") : WEND
230 '
240 'random noise
250 X = INP(&H61) AND &HFC
260 I=20 ' changing I changes the noise
270 FOR N% = 0 TO 500
280 IF (RND * 100 < I) THEN OUT &H61,X OR 2 : OUT &H61,X
290 NEXT
The Real-Time Clock
The PC/AT and the PS/2s all have a real-time clock that keeps track of
the current date and time. In the PC/AT, the real-time clock is part of
the Motorola MC146818 chip that supports the PC/AT's nonvolatile CMOS RAM.
In the PS/2s, the real-time clock is in custom silicon. In all these
machines, the real-time clock runs off a battery so that the time and date
are maintained even while the computer is turned off.
Using the Date and Time
When you boot a PC/AT or PS/2, the ROM BIOS start-up routines read the
time of day from the real-time clock and convert it into the corresponding
number of timer ticks. This value is used to initialize the 4-byte count
stored at 0040:006CH in the ROM BIOS data area. All versions of DOS use
this count value to determine the current time of day. Starting in version
3.0, DOS also obtains the current date from the real-time clock and
initializes its own internal record of the date at boot-up time.
To work with the current date and time in a program, we recommend that you
use the DOS date and time services (Chapter 16) to get and set the
current values. You could also use ROM BIOS services to access the
real-time clock (Chapter 10). However, if you call the ROM BIOS to change
the date or time, DOS may not be aware of the change and may assume an
incorrect time or date.
Setting the Alarm
The real-time clock's alarm feature generates an interrupt at a specific
time. To take advantage of this feature, you must create an interrupt
handler that performs an action when the alarm interrupt occurs. You can
even make this action independent of other programs by leaving the
interrupt handler resident in memory with a DOS
Terminate-and-Stay-Resident service. (See Chapters 16 and 17.)
The ROM BIOS provides a set of services through interrupt 1AH that give
you access to the real-time clock's alarm feature. See Chapter 12 for
more details.
────────────────────────────────────────────────────────────────────────────
Chapter 8 ROM BIOS Basics
The ROM BIOS Philosophy
The ROM BIOS Service Interrupts
ROM BIOS Service Operating Characteristics
Creating an Assembly-Language Interface
The Basic Form of an Interface Routine
Advanced BIOS Interface
One secret of successful programming for the PC family lies in the
effective use of the software that is built right into the machine: the
ROM BIOS services. Conceptually, the ROM BIOS services are sandwiched
between the hardware and the high-level languages (including the operating
system). They work directly with the computer's hardware and peripheral
devices, performing some of the system's most fundamental tasks, such as
reading and writing individual bytes of data to the display screen or
disk. DOS services and programming-language services are often built from
these basic functions and enhanced to make a particular process more
efficient. You can enhance your programs in the same way by plugging them
directly into the ROM BIOS, thereby gaining access to an extremely
powerful set of tools and using your computers in the way that IBM
intended them to be used.
That last point is worth emphasizing. IBM has gone to considerable lengths
to create a clean and well-defined method for directing the operation of
the computer through the ROM BIOS services. As each new PC model is
designed, IBM (and any other computer maker who is faithfully extending
the PC family) makes sure its ROM BIOS services are thoroughly compatible
with those of the other members of the family. As long as you control your
computers through the ROM BIOS, whether directly or indirectly, you are
safe from any compatibility problems. If you bypass the ROM BIOS and
program directly to the hardware, you are not only asking for trouble, but
you are also severely limiting the range and viability of your programs.
That's not to say that you should always use ROM BIOS services when
they're available. The input/output functions provided in DOS and in
high-level programming languages often provide the same services as the
ROM BIOS, but in a form that is easier to use within your programs.
However, when a program needs more direct access to the computer's
input/output devices than DOS or your programming language can provide,
the ROM BIOS services are usually the answer.
The next five chapters discuss the ROM BIOS service routines. Fortunately,
the routines fall naturally into groups derived from the hardware devices
they support, so the video services, disk services, and keyboard services
can all be reviewed separately. But before you take a closer look at the
individual services, you need to find out how to incorporate them into
your programs. This chapter sets the stage by explaining what goes into
writing an interface routine, the bridge between programming languages and
the ROM BIOS services. First, a word on how the ROM BIOS operates.
The ROM BIOS Philosophy
All ROM BIOS services are invoked by interrupts. Each interrupt
instruction selects a particular entry in the interrupt vector table in
low memory. The addresses of all ROM BIOS service routines are stored in
this table. This design makes it possible for any program to request a
service without knowing the specific memory location of the ROM BIOS
service routine. It also allows the services to be moved around, expanded,
or adapted without affecting the programs that use the services. Although
IBM has tried to maintain the absolute memory location of some parts of
the ROM BIOS, it would be foolish to use these addresses because they may
change in the future. The standard, preferred, and most reliable way to
invoke a ROM BIOS service is to use its interrupt rather than its absolute
address.
The ROM BIOS services could be supervised by one master interrupt, but
instead they are divided into subject categories, each with its own
controlling interrupt. This design lets each interrupt handler be easily
replaced. For example, if a hardware manufacturer created a radically
different video display that operated under a completely new ROM BIOS
program, the manufacturer could provide the new ROM BIOS program along
with the hardware. The new ROM BIOS program might be stored in RAM, and it
would replace the one part of IBM's ROM BIOS that was used with the old
hardware. By making the ROM BIOS modular, IBM has made it easier to
improve and extend the capabilities of its computers.
The ROM BIOS Service Interrupts
The twelve ROM BIOS interrupts fall into five groups (Figure 8-1):
■ Six interrupts serve specific peripheral devices.
■ Two interrupts report on the computer's equipment.
■ One interrupt works with the time/date clock.
■ One interrupt performs the print-screen operation.
■ Two interrupts place the computer into another state altogether,
activating ROM BASIC and the system start-up routine.
As you'll see, most of the interrupts are tied to a group of subservices
that actually do the work. For example, the video service interrupt 10H
(decimal 16) has 25 subservices that do everything from setting the video
mode to changing the size of the cursor. You call a subservice by invoking
its governing interrupt and specifying the subservice number in register
AH. This process is explained in the example at the end of this chapter.
╓┌─┌──────────────────┌──────────────────────────────────────────────────────╖
Interrupt
Hex Dec Use
──────────────────────────────────────────────────────────────────────────
Peripheral Devices Services
10H 16 Video-display services (see Chapter 9)
13H 19 Diskette services (see Chapter 10)
Interrupt
Hex Dec Use
──────────────────────────────────────────────────────────────────────────
13H 19 Diskette services (see Chapter 10)
14H 20 Communications services (see Chapter 12)
15H 21 System services (see Chapter 12)
16H 22 Standard keyboard services (see Chapter 11)
17H 23 Printer services (see Chapter 12)
Equipment Status Services
11H 17 Equipment-list service (see Chapter 12)
12H 18 Memory-size service (see Chapter 12)
Time/Date Service
1AH 26 Time and date services (see Chapter 12)
Print-Screen Service
5H 5 Print-screen service (see Chapter 12)
Special Services
18H 24 Activate ROM BASIC (see Chapter 12)
Interrupt
Hex Dec Use
──────────────────────────────────────────────────────────────────────────
18H 24 Activate ROM BASIC (see Chapter 12)
19H 25 Activate bootstrap start-up routine (see Chapter 12)
──────────────────────────────────────────────────────────────────────────
Figure 8-1. The 12 ROM BIOS services.
ROM BIOS Service Operating Characteristics
The ROM BIOS services use some common calling conventions that provide
consistency in the use of registers, flags, the stack, and memory. We'll
outline the characteristics of these operating conventions, beginning with
the segment registers.
The code segment register (CS) is automatically reserved, loaded, and
restored as part of the interrupt process. Consequently, you don't have to
worry about your program's CS. The DS and ES registers are preserved by
the ROM BIOS service routines, except in the few cases where they are
explicitly used. The stack segment register (SS) is left unchanged, and
the ROM BIOS services depend on you to provide a working stack.
(Everything depends on a working stack!)
The stack requirements of the ROM BIOS services are not spelled out and
can vary considerably, particularly because some services invoke other
services. Generally, however, most programs ought to be working with a
much larger stack than the ROM BIOS services need.
The ROM BIOS varies in its usage of the other 8086 registers. The
instruction pointer (IP) is preserved by the same mechanism that preserves
the code segment. In effect, the stack pointer (SP) is preserved because
all the ROM BIOS services leave the stack clean, popping off anything that
was pushed on during the service-routine execution.
As usual, the general-purpose registers, AX through DX, are considered
fair game. The standard rule is not to expect any contents of these
registers to be maintained when you pass control to another routine, and
that applies to the ROM BIOS services as well. If you closely inspect the
coding of the services in the IBM technical reference manuals, you will
find that one or more registers are left undisturbed in one service or
another, but you would be foolish to try to take advantage of this. As a
general rule, when a simple result is returned from a subroutine, it is
left in the AX register; this applies to both the ROM BIOS and to all
programming languages. We'll see how often this really happens when we
cover the ROM BIOS services in detail.
The index registers (SI and DI) can be changed, exactly like the AX
through DX registers. The stack frame register (BP) can also be changed by
a few ROM BIOS service routines.
The various flags in the flag register are routinely changed as a
by-product of the instruction steps in the ROM BIOS routines. You should
not expect any of them to be preserved. In a few instances, the carry flag
(CF) or the zero flag (ZF) is used to signal the overall success or
failure of a requested operation.
These details are important but rather tedious, and there is little reason
for you to pay much attention to them. If your programs follow the general
interface rules given in the next section, and if they follow the specific
requirements of your programming language (covered in Chapters 19 and
20), you may not need to be concerned with them at all.
──────────────────────────────────────────────────────────────────────────
NOTE:
If you set out to use the ROM BIOS services in your programs, you'll
naturally be concerned about the possible conflicts between the services
and the operating conventions that your language follows. Put your mind
at ease. You will find that you do not have to take any extraordinary
precautions to protect your programming language from the ROM BIOS, or
vice versa.
──────────────────────────────────────────────────────────────────────────
Creating an Assembly-Language Interface
In order to make direct use of the ROM BIOS services from your programs,
you generally need to create an assembly-language interface routine to
link the programming language to the ROM BIOS. When we say "interface
routine," we are referring to the conventional program-development
subroutines──subroutines that are assembled into object modules (.OBJ
files) and then linked into working programs (.EXE or .COM files in DOS).
For more on this subject, see Chapter 19.
Working with assembly language can seem a fearsome task if you are not
already comfortable with it. While there are plenty of good reasons to be
intimidated by assembly language──after all, it is the most difficult and
demanding kind of programming──it's really not that difficult to create an
assembly-language interface routine.
──────────────────────────────────────────────────────────────────────────
ROM BIOS Interrupt Conflicts
In the hardware specification for the 8086 family of microprocessors,
Intel reserved interrupt numbers 00H through 1FH for use by the
microprocessor itself. (See Figure 8-2.) Unfortunately, IBM had
appropriated several of these reserved interrupt numbers for its own use
in the design of the IBM PC. This wasn't a problem with the PC and
PC/XT, which used the Intel 8088, because the 8088 predefined only
interrupts 00H through 04H.
When the PC/AT appeared, however, IBM's use of Intel's reserved
interrupt numbers led to a conflict. The reason: The AT's 80286 chip
predefines some of the same interrupt numbers that IBM's ROM BIOS uses.
The conflict appears when you use the 80286 BOUND instruction to
validate an array index, because the 80286 signals an out-of-bounds
array index by executing interrupt 05H──which IBM had previously
assigned to the ROM BIOS print-screen function. If you aren't careful, a
program that executes the BOUND instruction can unexpectedly print the
screen.
To resolve the conflict, you must install an interrupt 05H handler that
inspects the code that caused the interrupt: This handler can determine
whether the interrupt was executed in software or by the CPU. You can
also avoid this problem by using a protected-mode operating system like
OS/2, which bypasses the ROM BIOS. If you use DOS, however, be aware
that a programming error can occasionally lead to unexpected execution
of a ROM BIOS routine.
──────────────────────────────────────────────────────────────────────────
Interrupt CPU Function
────────────────────────────────────────────────────────────────────────
00H 8088,8086,80286,80386 Divide error
01H 8088,8086,80286,80386 Single-step
02H 8088,8086,80286,80386 NMI (nonmaskable interrupt)
03H 8088,8086,80286,80386 Breakpoint (INT 3)
04H 8088,8086,80286,80386 Overflow (INTO)
05H 80286,80386 BOUND out of range
06H 80286,80386 Invalid opcode
07H 80286,80386 Coprocessor not available
08H 80286,80386 Double exception (double
fault)
09H 80286,80386 Coprocessor segment overrun
0AH 80386 Invalid task-state segment
0BH 80386 Segment not present
0CH 80386 Stack fault
0DH 80286,80386 General protection exception
0EH 80386 Page fault
10H 80286,80386 Coprocessor error
────────────────────────────────────────────────────────────────────────
Figure 8-2. Predefined hardware interrupts in Intel microprocessors.
To create your own interfaces, you will need to have an assembler that is
compatible with the DOS standards for object files. All the examples we
give here are for the Microsoft Macro Assembler.
──────────────────────────────────────────────────────────────────────────
NOTE:
Interpreted BASIC can work with machine-language subroutines put
directly into memory. Preparing the sort of assembler subroutine that
will work with BASIC can be done as easily with DEBUG's A (assemble)
command as it can with an ordinary assembler. See Chapter 20 for more
on this subject.
──────────────────────────────────────────────────────────────────────────
The Basic Form of an Interface Routine
An interface routine's form varies with its intended use. An
assembly-language interface is a handshaker between your programming
language and a ROM BIOS service, so it has to be tailored to meet the
needs of both ends. It matters which programming language is being used;
it matters which ROM BIOS service is being invoked; and it matters whether
any data is being passed in one direction or the other. However, the
general outline of an assembly-language interface is basically the same,
no matter what you are doing.
One of the best ways to understand how an assembly-language interface is
coded is to view it as five nested parts, which are outlined here:
Level 1: General assembler overhead
Level 2: Subroutine assembler overhead
Level 3: Entry code
Level 4: Get parameter data from caller
Level 5: Invoke ROM BIOS service
Level 4: Pass back results to caller
Level 3: Exit code
Level 2: Finish subroutine assembler overhead
Level 1: Finish general assembler overhead
In this outline, Levels 1 and 2 tell the assembler what's going on, but
don't produce any working instructions. Levels 3 through 5 produce the
actual machine-language instructions.
We'll examine each of these levels to show you the rules and explain
what's going on. Don't forget that the specific requirements of an
interface routine change for different circumstances. We'll point out the
few design elements that are universal to all routines.
Here is a simple ROM BIOS interface routine. It's designed to be called
from a C program, but the elements of the interface design are the same
whether you use this routine as is or adapt it to another programming
language.
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT
PUBLIC _GetMemSize
_GetMemSize PROC near
push bp
mov bp,sp
int 12H
pop bp
ret
_GetMemSize ENDP
_TEXT ENDS
END
In the next few pages we'll examine the construction of this routine.
Level 1: General assembler overhead
Here is an outline of a typical Level-1 section of an interface routine,
with the lines numbered for reference:
1-1 _TEXT SEGMENT byte public 'CODE'
1-2 ASSUME cs:_TEXT
(Levels 2 through 5 appear here)
1-3 _TEXT ENDS
1-4 END
Line 1-1 is a SEGMENT directive that declares the name of a logical
grouping of executable machine instructions and informs the assembler (and
any person who reads the source code) that what follows consists of
executable code. Line 1-2, the ASSUME directive, tells the assembler to
associate the CS register with any address labels in the _TEXT segment.
This makes sense because the CS register is used by the 8086 to address
executable code.
Line 1-3 ends the segment started in line 1-1, and line 1-4 marks the end
of the source code for this routine.
The names _TEXT and CODE conform to the conventions used by virtually all
C language compilers for PCs and PS/2s, as do the BYTE and PUBLIC
attributes. Alternative names and attributes are available to advanced
programmers, but for now we'll stick with the simplest.
Level 2: Subroutine assembler overhead
Next, let's look at an outline of a typical Level 2, the assembler
overhead for a subroutine (called a procedure in assembler parlance). The
sample on the following page shows some typical Level-2 coding.
2-1 PUBLIC _GetMemSize
2-2 _GetMemSize PROC near
(Levels 3 through 5 appear here)
2-3 _GetMemSize ENDP
Line 2-1 instructs the assembler to make the name of the procedure,
_GetMemSize, public information, which means that the link program can
then connect it to other routines that refer to it by name.
Lines 2-2 and 2-3 bracket the procedure, named _GetMemSize. PROC and ENDP
are mandatory and surround any procedure, with PROC defining the beginning
of the procedure and ENDP signaling the end of it. Again, the near
attribute on the PROC statement follows the conventions established for
linking assembly-language routines to C programs. In more advanced C
programs and in routines linked with programs written in languages like
FORTRAN and BASIC, you must sometimes use a different attribute, far.
(More about this in Chapter 20.)
Level 3: Entry and exit code
Levels 3, 4, and 5 contain actual executable instructions. In Level 3, the
assembly-language routine handles the housekeeping overhead required if a
subroutine is to work cooperatively with the calling program. The key to
this cooperation is the stack.
When the calling program transfers control to the subroutine, it does so
by means of a CALL instruction. (In this example, the instruction would be
CALL _GetMemSize.) When this instruction executes, the 8086 pushes a
return address──the address of the instruction following the CALL──onto
the stack. Later, the assembly-language routine can return control to the
calling program by executing a RET instruction, which pops the return
address off the stack and transfers control to the instruction at that
address.
If any parameters are to be passed to the assembly-language routine, the
calling program pushes them onto the stack before it executes the CALL
instruction. Thus, when the routine gets control, the value on top of the
stack is the return address, and any parameters are found on the stack
below the return address. If you keep in mind that the stack grows from
higher to lower addresses and that each value on the stack is 2 bytes in
size, you end up with the situation depicted in Figure 8-3.
To access the parameters on the stack, most compilers and
assembly-language programmers copy the value in SP into register BP. In
this way the values on the stack can be accessed even within a routine
that changes SP by pushing parameters or calling a subroutine. The
conventional way of doing this is shown by the code on the next page.
3-1 push bp ; preserve the current contents of BP
3-2 mov bp,sp ; copy SP to BP
(Levels 4 and 5 appear here)
3-3 pop bp
3-4 ret
After lines 3-1 and 3-2 have executed, the stack is addressable as in
Figure 8-4. (In a moment, we'll show how useful this is.) When it's time
to return control to the calling program, the routine restores the
caller's BP register value (line 3-3) and then executes a RET instruction
(line 3-4).
Bottom of stack
│ │ │
│ │ │
Higher addresses ├────────────────┤
│ Parameter │
├────────────────┤◄──── SP + 4
│ Parameter │
├────────────────┤◄──── SP + 2
│ Return address │
Lower addresses └────────────────┘◄──── SP
Figure 8-3. The stack at the time a subroutine is called.
Bottom of stack
│ │ │
│ │ │
├────────────────┤
│ Parameter │
├────────────────┤◄──── BP + 6
│ Parameter │
├────────────────┤◄──── BP + 4
│ Return address │
├────────────────┤◄──── BP + 2
│ Caller's BP │
└────────────────┘◄──── BP
Figure 8-4. The stack after register BP is initialized.
If you think about it, you'll realize that things could be more
complicated. For example, a calling program might use either a near or a
far CALL instruction to transfer control to a subroutine. If your program
uses far subroutine calls by convention (instead of the near calls used by
default in C), the PROC directive (Line 2-2) would require the far
attribute instead of near. This would instruct the assembler to generate a
far RET instruction instead of a near RET.
Furthermore, with a far calling convention, the return address on the
stack would be 4 bytes in size instead of 2 bytes, so the first parameter
would be at address [BP + 6] instead of [BP + 4] as shown in Figure 8-4.
In this book, however, we'll stick to the most straightforward case: near
PROCs and 2-byte return addresses.
Level 4: Get parameter data from caller
Level 4 deals with the parameters by passing them from the caller to the
ROM BIOS, and with the results by passing them from the ROM BIOS to
the caller. (Note, however, that the sample program contains no parameters
from the caller.) The caller's parameters are on the stack, either in the
form of data or addresses. (See Chapter 20 for help with this.) The
registers, mostly AX through DX, are used for ROM BIOS input and output.
The trick here──and it can be tricky──is to use the correct stack offsets
to find the parameters. We'll sneak up on this problem in stages.
First, you get to the parameters on the stack by addressing relative to
the address stored in BP in lines 3-1 and 3-2. (Refer to Figure 8-2 to
determine how items on the stack relate to the value in BP.) When more
than one parameter is present on the stack, you must decide which
parameter is which. Most languages push their parameters onto the stack in
the order they are written. This means that the last parameter is the one
closest to the top of the stack, at [BP + 4]. However, C uses the reverse
order, so that the parameter at [BP + 4] is the first one written in the
calling program.
Parameters normally take up 2 or 4 bytes on the stack, although 2 bytes is
more common. If any of these parameters were 4 bytes in size, you would
need to adjust the subsequent references accordingly.
If data were placed on the stack, then you could get it immediately by
addressing it like this: [BP + 4]. If an address were placed on the stack,
two steps would be needed: First, you would get the address, and second,
you would use the address to get the data. A Level-4 example showing both
data ([BP + 4]) and address ([BP + 6]) retrieval follows on the next page.
4-1 mov ax,[bp+4] ; value of parameter1
4-2 mov bx,[bp+6] ; address of parameter2
4-3 mov dx,[bx] ; value of parameter2
(Level 5 appears here)
4-4 mov bx,[bp+6] ; address of parameter2 (again)
4-5 mov [bx],dx ; store new value at parameter2 address
All of these MOV instructions move data from the second operand to the
first operand. Line 4-1 grabs data right off the stack and slaps it into
the AX register. Lines 4-2 and 4-3 get data by means of an address on the
stack: Line 4-2 gets the address (parking it in BX), and then line 4-3
uses that address to get to the actual data, which is moved into DX. Lines
4-4 and 4-5 reverse this process: Line 4-4 gets the address again, and
then line 4-5 moves the contents of DX into that memory location.
──────────────────────────────────────────────────────────────────────────
NOTE:
A crucial bit of assembler notation is demonstrated here: BX refers to
what's in BX, and [BX] refers to a memory location whose address is in
BX. A reference like [BP + 6] indicates a memory location 6 bytes past
the address stored in register BP.
──────────────────────────────────────────────────────────────────────────
While sorting out these references may not be a snap, if you think it
through carefully, it works out right.
Level 5: Invoke ROM BIOS service
Level 5 is our final step: It simply invokes the ROM BIOS service.
Once all registers contain appropriate values (usually passed from
the calling program and copied into registers by means of the stack), the
routine can transfer control to the ROM BIOS using an interrupt:
5-1 int 12h
In this example, this single INT instruction does all the work for you.
The ROM BIOS returns the computer's memory size in register AX, where C
expects the routine to leave it when the routine returns control to the
calling program. In other cases, you might need to leave a result
elsewhere, as in Lines 4-4 and 4-5, above.
Most ROM BIOS interrupts, however, provide access to several different
services. In such cases, you must specify a service number in register AH
before you execute the interrupt. For example, to access the first video
service, you would execute the commands on the following page.
mov ah,0 ; AH=service number 0
int 10h ; ROM BIOS video services interrupt
This five-step process outlines the basic principles of nearly all aspects
of an assembly-language interface. In the following chapters, you'll see
how this design is used in specific examples.
Advanced BIOS Interface
To conclude this chapter we'd like to mention the alternative BIOS
interface that IBM introduced in the PS/2 models 50, 60, and 80. This
Advanced BIOS (ABIOS) interface addresses some of the major design
shortcomings of the interrupt-based interface described in this chapter.
The traditional, interrupt-based ROM BIOS interface is limited in two
important ways:
■ It cannot be used in protected mode in a PS/2 Model 50, 60, or 80.
■ It provides poor support for multitasking, so an operating system that
offers multitasking cannot rely on the traditional ROM BIOS interface.
IBM's solution to these problems is the Advanced BIOS interface in the
PS/2 models 50, 60, and 80. Through the Advanced BIOS interface, BIOS
services are accessed through a set of address tables and common data
areas designed for use in protected mode as well as with a multitasking
operating system. However, the complexity of the Advanced BIOS interface
makes it better suited to supporting an operating system than to
supporting applications programs. Unless you're writing a protected-mode,
multitasking operating system, we recommend that you keep using the
traditional ROM BIOS interface that is common to all computers in the PC
family.
────────────────────────────────────────────────────────────────────────────
Chapter 9 ROM BIOS Video Services
Accessing the ROM BIOS Video Services
Service 00H (decimal 0): Set Video Mode
Service 01H (decimal 1): Set Cursor Size
Service 02H (decimal 2): Set Cursor Position
Service 03H (decimal 3): Read Cursor Position
Service 04H (decimal 4): Read Light-Pen Position
Service 05H (decimal 5): Set Active Display Page
Service 06H (decimal 6): Scroll Window Up
Service 07H (decimal 7): Scroll Window Down
Service 08H (decimal 8): Read Character and Attribute
Service 09H (decimal 9): Write Character and Attribute
Service 0AH (decimal 10): Write Character
Service 0BH (decimal 11): Set 4-Color Palette
Service 0CH (decimal 12): Write Pixel
Service 0DH (decimal 13): Read Pixel
Service 0EH (decimal 14): Write Character in Teletype Mode
Service 0FH (decimal 15): Get Current Video Mode
Service 10H (decimal 16): Color Palette Interface
Service 11H (decimal 17): Character Generator Interface
Service 12H (decimal 18): "Alternate Select"
Service 13H (decimal 19): Write Character String
Service 1AH (decimal 26): Read/Write Display Combination Code
Service 1BH (decimal 27): Return Functionality/State Information
Service 1CH (decimal 28): Save/Restore Video State
Comments and Example
In this chapter, we will discuss each of the video, or screen-control,
services provided by the ROM BIOS. We have devoted most of the chapter to
detailed descriptions of each video service. Beginning on page 194, we
have included some programming hints and an assembly-language routine that
makes use of some of the video services. For a more general discussion of
video hardware in the PC family, see Chapter 4. For information on
low-memory locations used by the ROM BIOS for video status information,
turn to page 54.
Accessing the ROM BIOS Video Services
The ROM BIOS video services are all requested by generating interrupt 10H
(decimal 16). There are 25 principal services available under this
interrupt. (See Figure 9-1.) Like all other ROM BIOS services, the video
services are numbered from 00H and are selected by placing the service
number in the AH register. The services usually require you to specify
additional parameters in register AL, BX, CX, or DX. We'll cover the
purpose and placement of the parameters under each service description.
╓┌─┌────────────────────────┌────────────────────────────────────────────────╖
Service
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
00H 0 Set Video Mode.
01H 1 Set Cursor Size.
02H 2 Set Cursor Position.
03H 3 Read Cursor Position.
04H 4 Read Light-Pen Position.
05H 5 Set Active Display Page.
06H 6 Scroll Window Up.
07H 7 Scroll Window Down.
08H 8 Read Character and Attribute.
09H 9 Write Character and Attribute.
0AH 10 Write Character.
0BH 11 Set 4-Color Palette.
0CH 12 Write Pixel.
0DH 13 Read Pixel.
0EH 14 Write Character in Teletype Mode.
0FH 15 Get Current Video Mode.
10H 16 Color Palette Interface.
Service
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
10H 16 Color Palette Interface.
11H 17 Character Generator Interface.
12H 18 "Alternate Select".
13H 19 Write Character String.
14H 20 (PC convertible only)
15H 21 (PC convertible only)
1AH 26 Read/Write Display Combination Code.
1BH 27 Return Functionality/State Information.
1CH 28 Save/Restore Video State.
──────────────────────────────────────────────────────────────────────────
Figure 9-1. The 25 video services.
Service 00H (decimal 0): Set Video Mode
Service 00H (decimal 0) is used to configure your video subsystem into one
of the 20 video modes listed in Figure 9-2. For details of the video
modes, see page 72.
You may recall from our discussion in Chapter 4 that modes 00H through
06H apply to the standard Color Graphics Adapter; mode 07H applies to the
Monochrome Display Adapter; modes 0DH through 10H were added for the
Enhanced Graphics Adapter; and modes 11H through 13H were introduced with
the Multi-Color Graphics Array (PS/2 models 25 and 30) and Video Graphics
Array (PS/2 models 50, 60, and 80).
Mode Type Resolution Colors Video Subsystem
──────────────────────────────────────────────────────────────────────────
00H, 01H Text 40 x 25 16 CGA, EGA, MCGA, VGA
02H, 03H Text 80 x 25 16 CGA, EGA, MCGA, VGA
04H, 05H Graphics 320 x 200 4 CGA, EGA, MCGA, VGA
06H Graphics 640 x 200 2 CGA, EGA, MCGA, VGA
07H Text 80 x 25 Mono MDA, EGA, VGA
08H, 09H,
0AH (PCjr only)
0BH,0CH (used internally by EGA
BIOS)
0DH Graphics 320 x 200 16 EGA,VGA
0EH Graphics 640 x 200 16 EGA,VGA
0FH Graphics 640 x 350 Mono EGA,VGA
10H Graphics 640 x 350 16 EGA,VGA
11H Graphics 640 x 480 2 MCGA,VGA
12H Graphics 640 x 480 16 VGA
13H Graphics 320 x 200 256 MCGA,VGA
──────────────────────────────────────────────────────────────────────────
Figure 9-2. Video modes available through ROM BIOS video service 00H.
Normally, the ROM BIOS clears the screen memory buffer when the mode is
set, even if it is set to the same mode again and again. In fact,
resetting the same video mode can be an easy way to clear the screen. In
some versions of DOS, in fact, the DOS command CLS clears the screen this
way. Setting the video mode also sets the color palette to default color
values, however, so don't rely on service 00H to clear the screen if
you're working with colors; use video service 06H instead.
On the EGA, MCGA, and VGA, you can also tell the ROM BIOS not to clear the
screen when it sets up the video mode. Do this by adding 80H (decimal 128)
to the video mode number you specify in AL. For example, to change to 640
x 200, 2-color mode without clearing the screen, call service 00H with AL
= 86H. Use this feature with caution, though. Displayable video data is
formatted differently in different modes, so a screenful of useful data in
one video mode may become unintelligible when you switch to another mode
without clearing the screen.
See Chapter 4, page 72 for more on video modes. See page 58, memory
location 0040:0049H, for more on how a record of the mode is stored in
memory. See service 0FH (decimal 15) to find out how to determine the
current video mode.
Service 01H (decimal 1): Set Cursor Size
Service 01H (decimal 1) controls the form and size of the blinking cursor
that appears in text modes. The default cursor appears as one or two
blinking scan lines at the bottom of a character display position. You can
change the default cursor size by redefining the number of lines that are
displayed.
The Color Graphics Adapter (CGA) can display a cursor that has 8 scan
lines, numbered from 0 at the top to 7 at the bottom. The Monochrome
Display Adapter (MDA) and the EGA can display a cursor that has 14 scan
lines, also numbered from the top, from 0 through 13. Both the MCGA and
the VGA have default text characters that are 16 scan lines high, so the
maximum size of the text cursor in default PS/2 text modes is 16 scan
lines. You set the cursor size by specifying the starting and ending scan
lines. (These are the same as the start and stop parameters of BASIC's
LOCATE statement.) The start line number is loaded into the CH register
and the stop line number into the CL register. Default cursor settings are
CH = 6, CL = 7 for the CGA, CH = 11, CL = 12 for the MDA and EGA, and CH =
13, CH = 14 for the MCGA and VGA.
You will notice that the valid scan line numbers occupy only four of the
bits (bits 0 through 3) placed in these registers. If bit 5 of CH is set
on by specifying a value of 20H (decimal 32), the cursor will disappear.
This is one of two techniques that you can use to remove the cursor in the
text modes. The other technique is to actually move it off the screen, say
to row 26, column 1. When a graphics mode is set, bit 5 is automatically
set to keep the cursor from being displayed. Because there is no true
cursor in the graphics modes, you must simulate one with the solid-block
character, DFH (decimal 223), or with a change of background attributes.
Service 02H (decimal 2): Set Cursor Position
Service 02H (decimal 2) sets the position of the cursor using row and
column coordinates. In text modes, multiple display pages can exist, each
one having an independently recorded cursor position. Even though the
graphics modes have no visible cursor, they keep track of the logical
cursor position in the same way as the text modes. This logical cursor
position is used to control character I/O.
The cursor position is specified by placing a row number in register DH, a
column number in DL, and a display page number in BH. The numbering for
the rows and columns begins with coordinates 0,0 in the top left corner.
The graphics modes also use the character row and column coordinates to
identify the cursor location, rather than pixel coordinates. The display
page number must be set to 0 in CGA-compatible graphics modes, although
the EGA and VGA both support multiple display pages in 16-color graphics
modes as well as in text modes.
See Figure 9-3 for a summary of register settings. See page 87 for more
on display pages. See service 03H for the reverse operation: Read cursor
position.
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 02H DH = row number
DL = column number
BH = page number
──────────────────────────────────────────────────────────────────────────
Figure 9-3. Registers values for setting the cursor position using
service 02H.
Service 03H (decimal 3): Read Cursor Position
Service 03H (decimal 3) is the opposite of services 01H and 02H. When you
specify the page number in BH, the ROM BIOS reports the cursor size by
returning the starting scan line in CH and the ending scan line in CL. In
addition, it reports the cursor position by returning the row in DH and
the column in DL. (See Figure 9-4.)
Service Number Returns
──────────────────────────────────────────────────────────────────────────
AH = 03H BH = page number (set to 0 in graphics modes)
DH = row number
DL = column number
CH = starting scan line of cursor
CL = ending scan line of cursor
──────────────────────────────────────────────────────────────────────────
Figure 9-4. Values reported by video service 03H.
Service 04H (decimal 4): Read Light-Pen Position
Service 04H (decimal 4) reports the light-pen status on a CGA or EGA,
specifically whether or not the pen has been triggered, and where it is on
the screen if it has been triggered.
Register AH is set to indicate triggering: If AH = 01H, the light pen has
been triggered; if AH = 00H, it has not been triggered. If the pen has
been triggered, the ROM BIOS determines the light pen's character column
and pixel row (y-coordinate) from the video hardware. From these, the ROM
BIOS computes the character row and pixel column (x-coordinate). The
results are returned in registers BX, CX, and DX as shown in Figure 9-5.
Service Number Returns
──────────────────────────────────────────────────────────────────────────
AH = 04H DH = character row number
DL = character column number
CH = pixel line number (CGA and EGA video modes
04H, 05H, and 06H)
CX = pixel line number (all other EGA video
modes)
BX = pixel column number
──────────────────────────────────────────────────────────────────────────
Figure 9-5. Light-pen position values returned by service 04H.
Service 05H (decimal 5): Set Active Display Page
Service 05H (decimal 5) selects the active display page for text modes 0
through 3 and also for 16-color EGA and VGA graphics modes. You specify
the page number in register AL. (See Figure 9-6.) In text modes, page
numbers range from 0 through 7. Don't forget, however, that the CGA
hardware can display only four different 80-column pages, so CGA pages 4
through 7 overlap pages 0 through 3 when you're in 80 x 25 text mode. On
the EGA and in the PS/2 video subsystems, you can also select among
multiple display pages in 16-color graphics modes.
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 05H AL = new display page number
──────────────────────────────────────────────────────────────────────────
Figure 9-6. The registers used to set the active display page using
service 05H.
In all video modes, page 0 is used by default. Page 0 is located at the
beginning of display memory, with higher page numbers in higher memory
locations. See page 87 for more on display pages.
Service 06H (decimal 6): Scroll Window Up
Service 06H (decimal 6) and companion service 07H are used to define a
rectangular window of text on the screen and to scroll the window's
contents up or down one or more lines. To accomplish the scrolling effect,
blank lines are inserted at the bottom of the window area with service 06H
(at the top with service 07H) and the top lines of the window (the bottom
lines with service 07H) are scrolled off and disappear.
The number of lines to be scrolled is specified in AL. If AL = 00H, the
entire window is blanked. (The same thing would happen if you scrolled
more lines than the window size allowed.) The location or size of the
window is specified in the CX and DX registers: CH is the top row, and DH
is the bottom row; CL is the left column, and DL is the right column. The
display attribute for the new blank lines inserted by the two services is
taken from BH. Figure 9-7 summarizes the register settings for both
services 06H and 07H.
When you fill a window with lines of text, you'll discover that window
scrolling is normally a two-stage process: When a new line is ready to be
written in the window, service 06H (or service 07H) scrolls the current
window contents. Then the new line is filled with text using the cursor-
positioning and character-writing services. The following example
demonstrates this window action.
DEBUG ; invoke DEBUG from DOS utilities
A ; ask to assemble instructions
INT 10 ; create interrupt 10H instruction
[Return] ; finish assembling
R AX ; ask to see and change contents of AX
0603 ; specify service 06H (scroll up), using
; 3-line window
R CX ; ask to see and change contents of CX
050A ; specify top left corner: row 5, column 10
R DX ; ask to see and change contents of DX
1020 ; specify bottom right corner: row 16, column 32
D 0 L 180 ; fill screen with nonsense
G =100 102 ; execute INT 10H, then stop
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 06H (scroll up) AL = number of lines to scroll
AH = 07H (scroll down) CH = row number of upper-left corner
CL = column number of upper-left corner
DH = row number of lower-right corner
DL = column number of lower-right corner
BH = display attribute for blank lines
──────────────────────────────────────────────────────────────────────────
Figure 9-7. Register values for scrolling using services 06H and 07H.
See Chapter 8 for more on assembly-language routines. See the IBM DOS
Technical Reference Manual for more on DEBUG.
Service 07H (decimal 7): Scroll Window Down
Service 07H (decimal 7) is, as we've already mentioned, the mirror image
of service 06H. The difference between the two services is the scrolling
action. In service 07H, the new blank lines appear at the top of the
window and the old lines disappear at the bottom. The opposite scrolling
action takes place in service 06H. See Figure 9-7 under service 06H for
the register parameter settings.
Service 08H (decimal 8): Read Character and Attribute
Service 08H (decimal 8) is used to read characters "off the screen," that
is, directly out of the display memory. This service is unusually spiffy
because it works in both text and graphics modes.
In graphics modes, the same character-drawing tables used to write
characters are also used to recognize them by a pattern-matching
operation. Even if you create your own character set in graphics mode,
this service will be able to recognize them. In text modes, of course, the
ASCII character codes are directly available in the display memory.
Service 08H returns the ASCII character code of the character in AL. (See
Figure 9-8.) In graphics modes, if the character doesn't match any
characters in the graphics character set, the ROM BIOS returns ASCII code
0. In text modes, the service also returns the character's color
attributes in AH. Remember to specify a display page number in BH when you
call this service.
Service Number Parameters Returns
──────────────────────────────────────────────────────────────────────────
AH = 08H BH = active display AL = ASCII character read from
page number cursor location
AH = attribute of text
character (text modes only)
──────────────────────────────────────────────────────────────────────────
Figure 9-8. The registers used to read a character and attribute with
service 08H.
See page 82 for more on text characters and attribute bytes. See page
84 for more on text- and graphics-mode characters. See Appendix C for
more on ASCII characters.
Service 09H (decimal 9): Write Character and Attribute
Service 09H (decimal 9) writes one or more copies of a single character
and its color attribute. The character is specified in AL, and the
text-mode attribute or graphics-mode color is specified in BL. The number
of times the character is to be written (one or more times) is placed in
CX, and BH contains the display page number. (See Figure 9-9.)
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 09H AL = ASCII character to write to screen
BL = attribute value (text modes) or foreground
color (graphics modes)
BH = background color (video mode 13H only) or
display page number (all other modes)
CX = number of times to write character and
attribute
──────────────────────────────────────────────────────────────────────────
Figure 9-9. The registers used to write a text character and attribute
using service 09H.
The ROM BIOS writes the character and its color attributes as many times
as requested, starting at the current cursor location. Although the cursor
is not moved, duplicate characters are written at subsequent screen
locations. In text mode, the duplicated characters will successfully wrap
around from line to line, which increases the usefulness of this service.
In graphics mode, the characters will not wrap around.
Service 09H is quite useful both for writing individual characters and for
replicating a character. The repeat operation is most often used to
rapidly lay out blanks or other repeated characters, such as the
horizontal lines that are part of box drawings. (See Appendix C.) When
you want to make a single copy of the character, be sure to set the count
in CX to 1. If it's set to 0, the number of repetitions will be a lot more
than you want.
Service 09H has an advantage over the similar service 0EH, in that you
can control the color attributes. However, its one disadvantage is that
the cursor is not automatically advanced.
In graphics modes, the value specified in BL is the foreground color──the
color of the pixels that make up the character drawing. Normally the ROM
BIOS displays the character with the specified foreground color on a black
background. If, however, you set bit 7 of the color value in BL to 1, then
the ROM BIOS creates the character's new foreground color by using an
exclusive OR operation (XOR) to combine each of the previous foreground
pixels with the value in BL. The same feature also applies to the
character and pixel writing services, services 0AH and 0CH.
Here's an example of what can happen when the ROM BIOS uses the XOR
operation to display a character. Imagine you're in 320 x 200, 4-color
graphics mode and the screen is completely filled with white pixels. If
you now write a white character in the usual way, with a color value of
03H (white) in register BL, the ROM BIOS displays a white character on a
black background. If, however, you write the same character with a color
value of 83H (bit 7 set to 1), the ROM BIOS uses XOR to display a black
character on a white background.
See page 82 for more on display attributes in text modes. See page 84
for more on color attributes in graphics modes.
Service 0AH (decimal 10): Write Character
Service 0AH (decimal 10) is the same as service 09H (write character and
attribute to cursor location) with one exception: Service 09H lets you
change the existing screen color attribute in text mode but service 0AH
does not.
However, in graphics mode you must still specify a color in BL (see
Figure 9-10), which makes the description of this service as only a
character-writing service partly incorrect. Service 0AH has the same
graphics color rules as services 09H and 0CH: The color can be used
directly or used with XOR and the existing color. (See service 09H for an
explanation.)
See page 82 for more on display attributes in text modes. See page 84
for more on color attributes in graphics modes.
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 0AH AL = ASCII character to write to screen
BL = foreground color (graphics modes only)
BH = background color (video mode 13H only) or
display page number (all other modes)
CX = number of times to write character
──────────────────────────────────────────────────────────────────────────
Figure 9-10. The registers used to write a character using service 0AH.
Service 0BH (decimal 11): Set 4-Color Palette
Service 0BH (decimal 11) actually consists of two subservices. You select
either subservice 00H or subservice 01H by storing the proper value in
register BH. (See Figure 9-11.) Subservice 00H lets you set the border
color in CGA alphanumeric modes or the background color in CGA 320 x 200,
4-color graphics mode. You designate the border color in BL with a value
between 00H and 0FH.
Subservice 01H lets you select one of the two 4-color palettes used in 320
x 200, 4-color mode. The value in BL specifies which of the two hardware
palettes to use. A value of 0 designates the red-green-brown palette, and
a value of 1 selects the cyan-magenta-white palette. (See page 77 for
more on color palettes.)
This service was designed primarily for use with the CGA. Use service
10H to control colors in other video modes on the EGA, MCGA, and VGA.
Service Number Subservice Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 0BH BH = 00H BL = border or background
color
BH = 01H BL = palette number (0 or 1)
──────────────────────────────────────────────────────────────────────────
Figure 9-11. Color control in CGA-compatible video modes using service
0BH.
Service 0CH (decimal 12): Write Pixel
Service 0CH (decimal 12) writes an individual pixel. You specify the
pixel's location on the screen by passing its column (x-coordinate) in
register CX and its row (y-coordinate) in DX. Remember that pixel rows and
columns are not the same as the character row and column you use in other
services to locate the cursor or to display a character. Pixel coordinates
correspond to individual dots, not to characters.
If you're using a graphics mode that supports multiple display pages, be
sure to specify the display page number in register BH. (See Figure
9-12.) Also, when you specify the pixel's color in register AL, you have
the option of setting bit 7 of the color value to 1. As in service 09H,
this tells the BIOS to display the pixel with an XORed color value. (See
service 09H for an explanation.)
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 0CH AL = pixel color
BH = display page number
DX = row number of pixel
CX = column number of pixel
──────────────────────────────────────────────────────────────────────────
Figure 9-12. The registers used to write a pixel using service 0CH.
See page 91 for more on pixels in graphics modes.
Service 0DH (decimal 13): Read Pixel
Service 0DH (decimal 13) is the reverse of service 0CH: It reads a pixel's
color value rather than writing it. A pixel has only a single color
attribute, which is returned through service 0DH. (The read-character
service 08H returns both a color and an ASCII character code.) The row is
specified in DX, the column in CX, and the display page in BH. The pixel
color value is returned in AL. (See Figure 9-13.) All high-order bits of
the value returned in AL are set to 0, as you would expect.
Service Number Parameters Returns
──────────────────────────────────────────────────────────────────────────
AH = 0DH BH = display page number AL = pixel color value
DX = row number of pixel
CX = column number of pixel
──────────────────────────────────────────────────────────────────────────
Figure 9-13. The registers used to read a pixel using service 0DH.
Service 0EH (decimal 14): Write Character in Teletype Mode
Service 0EH (decimal 14) is the workhorse service of conventional
character output. It writes individual characters to the screen in what is
known as teletype (TTY) mode. This makes the screen act as the simplest
and crudest form of printer──exactly what is needed for routine text
output. As such, this service has no regard for such niceties as color,
blinking characters, or control over the cursor location.
With this service, the character is written at the current cursor location
and the cursor is advanced one position, wrapping to new lines or
scrolling the screen as needed. The character to be written is specified
in register AL.
In text modes, the character is displayed as in service 0AH; that is, with
the color attributes already in use at the screen location where the
character is written. In graphics modes, however, you must also specify
the foreground color value to be used for the character. (See Figure
9-14.)
There are four characters that service 0EH reacts to according to their
ASCII meaning: 07H (decimal 7)──beep, 08H (decimal 8)──backspace, 0AH
(decimal 10)──line feed, and 0DH (decimal 13)──carriage return. All other
characters are displayed normally.
The primary advantage of this service over service 09H is that the cursor
is automatically moved; the advantage of service 09H is that you can
control the color attribute. Now, if you could only combine the two....
Service Number Parameters
──────────────────────────────────────────────────────────────────────────
AH = 0EH AL = ASCII character to write
BL = foreground color (in graphics modes only)
BH = display page (IBM PC BIOS dated 10/19/81 or
earlier)
──────────────────────────────────────────────────────────────────────────
Figure 9-14. The registers used to write a character in teletype mode
using service 0EH.
Service 0FH (decimal 15): Get Current Video Mode
Service 0FH (decimal 15) returns the current video mode and two other
useful pieces of information: the screen width in characters (80 or 40)
and the display page number.
The video mode number, as explained under service 00H, is returned in AL.
The screen width is returned in AH as a number of characters per line. The
display page number will be returned in BH. (See Figure 9-15.)
Service Number Returns
──────────────────────────────────────────────────────────────────────────
AH = 0FH AL = current display mode
AH = number of characters per line
BH = active display page
──────────────────────────────────────────────────────────────────────────
Figure 9-15. Information returned by service 0FH.
See page 72 for more on video modes. See page 58, memory location
0040:0049H, for more on how a record of the mode is kept.
Service 10H (decimal 16): Color Palette Interface
Service 10H (decimal 16) was introduced with the PCjr and carried forward
in the EGA and PS/2 ROM BIOS. It consists of a set of subservices (Figure
9-16) that let you control palette colors, blinking, and (on the MCGA and
VGA) the video DAC. Be aware that different subservices are supported with
different hardware. Before you use these subservices in a program, be sure
your program "knows" which subsystem it's running on. (Video service 1AH
can provide this information to a program.)
Subservice Number Description
──────────────────────────────────────────────────────────────────────────
AL = 00H Update a specified palette register.
AL = 01H Specify the border color.
AL = 02H Update all 16 palette registers plus border.
AL = 03H Select background intensity or blink attribute.
AL = 07H Read a specified palette register.
AL = 08H Read the border color register.
AL = 09H Read all 16 palette registers plus border.
AL = 10H Update a specified video DAC color register.
AL = 12H Update a block of video DAC color registers.
AL = 13H Set video DAC color paging.
AL = 15H Read a specified video DAC color register.
AL = 17H Read a block of video DAC color registers.
AL = 1AH Get video DAC color paging status.
AL = 1BH Gray-scale a block of video DAC color registers.
──────────────────────────────────────────────────────────────────────────
Figure 9-16. Subservices available through video BIOS service 10H.
Subservice 00H (decimal 0) updates one of the 16 palette registers on an
EGA or VGA. You specify the palette register number in BL and a new
palette register value in BH when you call this subservice. The VGA BIOS
also supports subservice 07H (decimal 7), which performs the complementary
operation: When you call subservice 07H with a palette register number in
BL, the ROM BIOS returns that palette register's current contents in BH.
(Subservice 07H isn't available in the EGA BIOS because the EGA has
write-only palette registers.)
Subservice 01H (decimal 1) sets the border color on an EGA or VGA. You
pass the color value to the BIOS in register BH when you call this
subservice. The VGA BIOS supports subservice 08H, which returns the
current border color value in BH, but again this complementary subservice
isn't available on the EGA.
Here are two tips about setting the border color on an EGA or VGA. First,
in most EGA video modes the border area is very small, and selecting any
border color other than black results in a narrow, smeared border. On the
VGA, the border is better. Second, if compatibility with the CGA is
important, remember that you can also use video service 0BH (page 180) to
set the border color.
Subservice 02H (decimal 2) updates all 16 palette registers, plus the
border color, with a single ROM BIOS call. Before you call subservice 02H,
you must store all 16 palette register values plus the border color value
in a 17-byte table. You then pass the address (segment and offset) of this
table to the BIOS in registers ES and DX when you call this subservice.
The VGA also provides a subservice that lets you read the palette
registers back into a table: When you call subservice 09H (decimal 9) with
ES:DX pointing to a 17-byte table, the ROM BIOS fills the table with the
16 current palette register values and the border color.
Subservice 03H (decimal 3) lets you selectively enable or disable the
blinking attribute. The ROM BIOS uses blinking by default, but if you
prefer to have a full range of 16 background colors instead of only 8, you
can use subservice 03H to disable blinking. The value you pass in register
BL determines whether blinking is enabled (BL = 01H) or disabled (BL =
00H).
Subservices 10H (decimal 16) and 15H (decimal 21) are supported only by
the MCGA and VGA BIOS. These two subservices give you direct access to one
of the 256 color registers in the video digital to analog convertor (DAC).
To update a video DAC color register, call subservice 10H with the color
register number in BX and 6-bit red, green, and blue color values in
registers DH, CH, and CL. To read a specified color register, place the
color register number in BX and use subservice 15H, which returns the RGB
values in DH, CH, and CL.
The related subservices 12H (decimal 18) and 17H (decimal 23) operate on a
block of video DAC color registers instead of only one. To use subservice
12H, create a table of 3-byte red-green-blue values. Then place the
segment-offset address of the table in ES and DX, the first color register
number to update in BX, and the number of registers to update in CX. When
you call subservice 12H, the ROM BIOS stores each red-green-blue value in
turn into the block of color registers you specified in BX and CX.
The complementary subservice 17H requires you to pass the address of a
table in ES:DX, along with a starting register number in BX and a register
count in CX. The ROM BIOS fills the table with the red-green-blue values
it reads from the block of color registers you specified.
On the VGA, which has both palette registers and video DAC color
registers, you can use subservices 13H (decimal 19) and 1AH (decimal 26)
to switch rapidly between different palettes. By default, the ROM BIOS
configures the VGA hardware so that color decoding is the same as on the
EGA: Each of the 16 palette registers contains a 6-bit value that
specifies one of the first 64 video DAC registers, and these 64 color
registers specify the 64 colors available in the EGA palette.
Subservice 13H lets you use the other three color pages, or groups of 64
video DAC color registers. (See Figure 9-17.) If you call subservice 13H
with BH = 01H and BL = 01H, for example, the BIOS configures the VGA
hardware to display colors from the second group of 64 color registers
(color page 1). To use the first group (color page 0) again, you could
call the same subservice with BH = 00H and BL = 01H. If, for example, you
used the default, EGA-compatible colors in color page 0, and their
gray-scale equivalents in color page 1, you could switch rapidly between
the two with a single call to subservice 13H.
If you need to switch rapidly between more than four palettes, you can use
subservice 13H with BH = 01H and BL = 00H to configure the VGA color
decoding hardware to use 4-bit palette register values instead of 6-bit
values. In this case, each palette register value can specify one of only
16 different video DAC registers. This makes 16 color pages available,
each comprising 16 color registers. You can select any of the 16 color
pages using subservice 13H with BL = 01H.
Parameters Description
──────────────────────────────────────────────────────────────────────────
BL = 00H BH = 00H Use four 64-register pages.
BH = 01H Use sixteen 16-register pages.
BL = 01H BH = n Color page number.
(n = 00H─03H if using 64-register
pages
n = 00H─0FH if using 16-register
pages)
──────────────────────────────────────────────────────────────────────────
Figure 9-17. Video DAC color paging with service 10H, subservice 13H.
The VGA ROM BIOS supplements subservice 13H with a complementary function,
subservice 1AH. This subservice returns the color page status in BL (16-
or 64-register color pages) and BH (current color page number).
With subservice 1BH (decimal 27) on the MCGA and VGA, you can convert the
color values in a block of consecutive video DAC color registers to
corresponding shades of gray. Call this subservice with BX containing the
number of the first video DAC register to convert, and with CX containing
the number of registers to update.
Service 11H (decimal 17): Character Generator Interface
Service 11H (decimal 17) first appeared in the EGA ROM BIOS. The many
subservices available in service 11H were augmented and expanded in the
PS/2 ROM BIOS to provide full support for the new video subsystems (MCGA
and VGA) introduced with the PS/2s.
To make sense of the many service 11H subservices, it helps to consider
them in four groups (Figure 9-18):
■ Subservices in the first group (subservices 00H through 04H) change the
character set used in text modes.
■ Subservices in the second group (subservices 10H through 14H) change
the text-mode character set as well as the displayed height of
text-mode characters.
■ Subservices in the third group (subservices 20H through 24H) update
graphics-mode character sets.
■ The subservice in the fourth group (subservice 30H) returns information
about the character sets currently displayed and about the character
sets available to the ROM BIOS.
Subservices 00H (decimal 0), 01H (decimal 1), 02H (decimal 2) and 04H
(decimal 4) all change the character set used to display text-mode
characters on the EGA, MCGA, or VGA. Subservices 01H, 02H, and 04H are the
easiest to use. You need specify only which available tables in character
generator RAM should contain the character set. Thus, for example, a call
to service 11H with AL = 02H and BL = 00H instructs the ROM BIOS to use
its 8 x 8 characters in the first (default) table in character generator
RAM.
If you want to define your own characters you need to use subservice 00H,
as follows: Place a table of the bit patterns that define the characters
in a buffer. Then call subservice 00H with the address of the table in
ES:BP, the number of characters in CX, the ASCII code of the first
character in the table in DX, and the number of bytes in each character's
bit pattern in BH.
Subservice 03H (decimal 3) lets you select among text-mode character sets
once they are loaded into character generator RAM. The EGA and MCGA have
four such tables; the VGA has eight. The value in BL specifies which one
or two of the tables is to be used to display text-mode characters. On the
EGA and MCGA, bits 0 and 1 of BL specify one table, and bits 2 and 3
specify a second table. If the two bit fields specify the same table,
that's the table that will be used for all text-mode characters.
╓┌─┌─────────────────────────────┌───────────────────────────────────────────╖
Subservice Number Description
──────────────────────────────────────────────────────────────────────────
Load a text-mode character set:
AL = 00H Load a user-specified character set.
AL = 01H Load the ROM BIOS 8 x 14 character set.
AL = 02H Load the ROM BIOS 8 x 8 character set.
AL = 03H Select displayed character set.
AL = 04H Load the ROM BIOS 8 x 16 character set
(MCGA, VGA only).
Load a text-mode character set and adjust the displayed character height:
AL = 10H Load a user-specified character set.
AL = 11H Load the ROM BIOS 8 x 14 character set.
AL = 12H Load the ROM BIOS 8 x 8 character set.
AL = 14H Load the ROM BIOS 8 x 16 character set
(MCGA, VGA only).
Subservice Number Description
──────────────────────────────────────────────────────────────────────────
Load a graphics-mode character set:
AL = 20H Load a CGA-compatible, user-specified
character set.
AL = 21H Load a user-specified character set.
AL = 22H Load the ROM BIOS 8 x 14 character set.
AL = 23H Load the ROM BIOS 8 x 8 character set.
AL = 24H Load the ROM BIOS 8 x 16 character set
(MCGA, VGA only).
Get character generator information:
AL = 30H Get character generator information.
──────────────────────────────────────────────────────────────────────────
Figure 9-18. Subservices available through video BIOS service 11H.
Subservices 10H (decimal 16), 11H (decimal 17), 12H (decimal 18), and 14H
(decimal 20) are similar to subservices 00H, 01H, 02H, and 04H. The
difference is that with these higher-numbered subservices, the ROM BIOS
not only loads a character set but also adjusts the displayed character
height appropriately. This difference is obvious if you compare the effect
of executing subservice 02H and subservice 12H to load the ROM BIOS 8 x 8
character set. With subservice 02H, the 8 x 8 characters are used without
adjusting the displayed character height, so if you're in a default ROM
BIOS text mode, you'll see 25 rows of characters. With subservice 12H, the
ROM BIOS adjusts the displayed character height so that in a default ROM
BIOS text mode you see 43 rows of characters on an EGA or 50 rows of
characters on a VGA.
Subservices 20H through 24H (decimal 32 through decimal 36) are related to
subservices 00H through 04H in that they also load character sets into
memory. However, this third group of subservices is designed for use only
in graphics modes. Subservice 20H loads a CGA-compatible set of 8 x 8
characters into RAM. To use subservice 20H, place a table containing the
bit patterns for ASCII characters 80H through FFH into memory, and pass
the address of this table to the ROM BIOS in registers ES:BP. Subservices
21H through 24H are similar to subservices 00H, 01H, 02H, and 04H. Call
them with 00H in BL, the number of displayed character rows in DL, and
(for subservice 21H) the number of bytes in each character's bit pattern
in CX.
Subservice 30H (decimal 48) returns several pieces of handy information
regarding the ROM BIOS character generator. This subservice reports the
height of the displayed character matrix in CX and the number of the
bottom character row in DL. For example, if you call subservice 30H in the
default EGA text mode (80 x 25), the BIOS returns 14 in CX and 24 in DL.
Parameter Returns
──────────────────────────────────────────────────────────────────────────
BH = 00H CGA-compatible 8 x 8 graphics-mode characters
(contents of interrupt 1FH vector)
BH = 01H Current graphics-mode characters (contents of
interrupt 43H vector)
BH = 02H ROM BIOS 8 x 14 characters
BH = 03H ROM BIOS 8 x 8 characters
BH = 04H Second half of ROM BIOS 8 x 8 character table
BH = 05H ROM BIOS 9 x 14 alternate characters
BH = 06H ROM BIOS 8 x 16 characters (MCGA and VGA only)
BH = 07H ROM BIOS 9 x 16 alternate characters (VGA only)
──────────────────────────────────────────────────────────────────────────
Figure 9-19. Character bit pattern table addresses returned in ES:BP by
subservice 30H of video ROM BIOS service 11H.
Subservice 30H also returns the address of any of several bit pattern
tables for the default ROM BIOS character sets. The value you pass in BH
when you call this subservice determines which address the ROM BIOS
returns in ES:BP. (See Figure 9-19.)
Service 12H (decimal 18): "Alternate Select"
Service 12H (decimal 18) made its debut along with service 11H in the EGA
BIOS. It, too, is supported in the ROM BIOS in all PC/2 video subsystems.
IBM's name for this service derives from the purpose of one of the
subservices of service 12H, namely, to select an alternate print-screen
routine for the ROM BIOS Shift-PrtSc function. The name lingers on even
though service 12H has been expanded by adding a number of unrelated
subservices. (See Figure 9-20.)
Subservice Number Description
──────────────────────────────────────────────────────────────────────────
BL = 10H Return video configuration information.
BL = 20H Select alternate print-screen routine.
BL = 30H Select scan lines for VGA text modes.
BL = 31H Enable/disable default palette loading.
BL = 32H Enable/disable CPU access to video RAM.
BL = 33H Enable/disable gray-scale summing.
BL = 34H Enable/disable ROM BIOS cursor emulation.
BL = 35H PS/2 display switch interface.
BL = 36H Enable/disable video refresh.
──────────────────────────────────────────────────────────────────────────
Figure 9-20. Subservices available through video BIOS service 12H.
Subservice 10H (decimal 16) reports on the configuration of an EGA or VGA.
The value returned in BH indicates whether the current video mode is color
(BH = 00H) or monochrome (BH = 01H). BL contains a number between 0 and 3
that represents the amount of RAM installed on an EGA (0 means 64 KB; 1
means 128 KB; 2 means 192 KB; 3 means 256 KB). The value in CH reflects
the status of input from the EGA feature connector, and CL contains the
settings of the EGA configuration switches.
Subservice 20H (decimal 32) is provided for the convenience of users of
the EGA or a VGA adapter. It replaces the motherboard ROM BIOS
print-screen routine with a more flexible routine in the adapter ROM BIOS.
Unlike the motherboard ROM BIOS routine, the adapter BIOS routine can
print a snapshot of a text-mode screen that has more than 25 rows of
characters. In PS/2s, of course, the motherboard routine can already do
this, eliminating the need for this subservice.
Subservice 30H (decimal 48) lets you specify how many scan lines to
display in VGA text modes. The default ROM BIOS text modes contain 400
scan lines. When you call subservice 30H, the value you pass in register
AL can instruct the ROM BIOS to use a different vertical resolution: If AL
= 00H, ROM BIOS text modes will display 200 scan lines, as they do on a
CGA. If AL = 01H, text modes will display an EGA-compatible 350 scan
lines. Finally, when AL = 02H, the ROM BIOS uses its default resolution of
400 scan lines.
When you use subservice 30H, the vertical resolution does not change until
the next time a program uses video ROM BIOS service 00H to select a text
mode. Thus, changing the vertical resolution actually requires you to make
two different ROM BIOS calls: one to specify the resolution and another to
set up the text mode.
Subservice 31H (decimal 49) lets you enable or disable palette loading
when the ROM BIOS sets up a new MCGA or VGA video mode. Calling subservice
31H with AL = 01H disables palette loading, so you can subsequently change
video modes without changing the colors in a previously-loaded palette. A
call with AL = 00H enables default palette loading.
Subservices 32H (decimal 50) and 35H (decimal 53) are provided for
programmers who want to use two different video subsystems in the same
PS/2 computer. In particular, these routines support the use of a VGA
alongside the built-in MCGA subsystem in a PS/2 Model 30.
Subservice 32H enables or disables buffer and port addressing according to
the value passed in AL (AL = 00H means enable; AL = 01H means disable).
This feature is important if any addresses in the two video subsystems
overlap: Before accessing one subsystem, you must disable addressing in
the other one.
Subservice 35H provides a complete switching interface that lets you
selectively access both an MCGA and a VGA in the same computer. This
subservice relies on the function provided through subservice 32H to
independently enable and disable each video subsystem. See Chapter 13 and
the IBM BIOS Interface Technical Reference manual for details.
Subservice 33H (decimal 51) tells the ROM BIOS whether or not to average
colors to gray scales when it establishes a new video mode on an MCGA or
VGA. A call to this subservice with AL = 01H disables the gray-scaling; a
call with AL = 00H enables gray-scaling. You can also use this subservice
to force the ROM BIOS to use a gray-scale palette even if you're using a
color monitor.
Subservice 34H (decimal 52) enables or disables text-mode cursor emulation
on the VGA. When you call this subservice with AL = 00H, the ROM BIOS
emulates CGA text-mode cursor sizing whenever you change
video modes or update the cursor size. When called with AL = 01H, this
subservice disables text-mode cursor emulation.
Subservice 36H (decimal 54) lets you enable or disable VGA video refresh.
Calling this subservice with AL = 01H disables refresh, and a call with AL
= 00H enables refresh. When you disable refresh, the screen goes blank,
but reads and writes to the video buffer are somewhat faster than when
refresh is enabled. If you are writing a program that needs to run as fast
as possible, and if you don't mind having the screen go blank while you
access the video buffer, then consider using subservice 36H to temporarily
blank the screen while you update it.
Service 13H (decimal 19): Write Character String
Service 13H (decimal 19), allows you to write a string of characters to
the display screen. Through the four subservices that make up this
service, you can specify the character attributes individually or as a
group. You can also move the cursor to the end of the string or leave it
in place, depending on which subservice you choose.
The subservice number is placed in AL, the pointer to the string in ES:BP,
the length of the string in CX, the starting position where the string is
to be written in DX, and the display page number in BH.
Subservices 00H (decimal 0) and 01H (decimal 1) write a string of
characters to the screen using the attribute specified in register BL.
With subservice 00H, the cursor is not moved from the location specified
in register DX; with subservice 01H, the cursor is moved to the location
following the last character in the string.
Subservices 02H (decimal 2) and 03H (decimal 3) write a string of
characters and attributes to the screen, writing first the character and
then the attribute. With subservice 02H, the cursor is not moved from the
location specified in register DX; with subservice 03H, the cursor is
moved to the location following the last character in the string.
Service 13H is available only in the PC/AT, EGA, PS/2s, and later versions
of the PC/XT ROM BIOS.
Service 1AH (decimal 26): Read/Write Display Combination Code
Service 1AH (decimal 26) was introduced in the ROM BIOS in the PS/2s, but
it is also part of the ROM BIOS of the VGA. This service returns a 2-byte
code that indicates which combination of video subsystems and video
displays is found in your computer. The display combination codes
recognized by this ROM BIOS service are listed in Figure 9-21. Service
1AH lets you select either of two subservices using the value in register
AL; subservice 00H or subservice 01H.
Subservice 00H (decimal 0) returns a 2-byte display combination code in
register BX. If your computer has two different video subsystems, the
value in BL indicates which one is active; that is, which is currently
being updated by the video ROM BIOS. The value in BH indicates the
inactive subsystem. If your computer has only video subsystem, the value
in BH is zero.
Subservice 01H (decimal 1) performs the reverse function of subservice
00H. It lets you change the current display combination code known to the
ROM BIOS. Don't use this subservice, however, unless you know exactly what
you're doing. It's a rare program indeed that requires you to change the
ROM BIOS's idea of what the video hardware actually is.
Code Video Subsystem
──────────────────────────────────────────────────────────────────────────
00H (No display)
01H MDA
02H CGA
03H (Reserved)
04H EGA with color display
05H EGA with monochrome display
06H Professional Graphics Controller
07H VGA with monochrome display
08H VGA with color display
09H,0AH (Reserved)
0BH MCGA with monochrome display
0CH MCGA with color display
0FFH (Unknown)
──────────────────────────────────────────────────────────────────────────
Figure 9-21. Display combination codes returned by video BIOS service
1AH.
Service 1BH (decimal 27): Return Functionality/State Information
Service 1BH (decimal 27) is available in all PS/2s as well as with the
VGA. It returns a great deal of detailed information regarding the
capabilities of the ROM BIOS as well as the current ROM BIOS and video
hardware status.
Service 1BH returns this information in a 64-byte buffer whose address is
passed in registers ES:DI. In addition to this address, you must also
specify an "implementation type" value of 0 in register BX. (Presumably
future IBM video products will recognize implementation type values other
than 0.)
The BIOS fills the buffer with information about the current video mode
(the mode number, character columns and rows, number of colors
available) as well as about the video hardware configuration (total video
memory available, display combination code, and so on). See the IBM BIOS
Interface Technical Reference manual for details on the buffer format.
In the first 4 bytes of the buffer, the ROM BIOS returns a pointer to a
table of "static" functionality information. This table lists nearly all
of the features the ROM BIOS and the video hardware can support: the video
modes available, support for palette switching, RAM-loadable character
sets, light-pen support, and many other details.
When you write a program that runs on a PS/2 or in a system with a VGA
adapter, service 1BH offers a simple and consistent way for your program
to determine what the video subsystem's current and potential capabilities
are. Unfortunately, you can't rely on this service if your program must be
compatible with non-PS/2 computers. Neither the PC motherboard ROM BIOS
nor the EGA BIOS supports this service. A program can determine whether
service 1BH is supported by examining the value returned by this service
in AL; this value is 1BH if the service is supported.
Service 1CH (decimal 28): Save/Restore Video State
Service 1CH (decimal 28) is provided by the ROM BIOS only in the PS/2
models 50, 60, and 80, and with VGA adapters. (In other words, where you
find a VGA you also find service 1CH.) This BIOS service lets you preserve
all information that describes the state of the video BIOS and hardware.
The ROM BIOS can preserve three types of information: the video DAC state,
the BIOS data area in RAM, and the current values in all video control
registers.
You can select three different subservices with the value you pass in
register AL: subservices 00H, 01H, and 02H.
Subservice 00H (decimal 0) is designed to be called before subservices 01H
or 02H. Subservice 00H requires you to specify which of the three types of
information you want to preserve, by setting one or more of the three
low-order bits of the value in CX. When this service returns, BX contains
the size of the buffer you will need to store the information.
Subservice 01H (decimal 1) saves the current video state information in
the buffer whose address you pass in ES:BX. Then you can change video
modes, reprogram the palette, or otherwise program the ROM BIOS or video
hardware.
Subservice 02H (decimal 2) lets you restore the previous video state.
Comments and Example
In cruising through the ROM BIOS video services, you've seen how they work
individually. Once you have that information in mind, the next question
usually is: Given a choice between using the ROM BIOS services directly or
using higher-level services such as the DOS services or the services built
into your programming language, which is best? The general advice that we
always give is to use the highest-level services that will accomplish what
you want to do. In this case, there is no specific reason for you to avoid
using the ROM BIOS video services──you can't do any great harm by using
them. But in the next chapter on the diskette services, we'll argue the
case the other way, advising you to avoid using the ROM BIOS diskette
services because more risk is associated with them.
The video capabilities of the PC models are remarkable, and the ROM BIOS
services give you full use of them. The DOS services, as you'll see in
Chapters 14 through 18, are rather weak and provide only the simplest
character services. Likewise, many programming languages (for example,
Pascal and C) only provide a dressed-up version of the DOS services and
nothing more. So, if you need to use the PC's fancy screen capabilities
and if you aren't using a language such as BASIC that provides the
services you need, you should be using the ROM BIOS services. Getting
control of the display screen is one of the very best reasons for using
the ROM BIOS services.
Using the ROM BIOS services directly usually calls for an
assembly-language interface, so we'll give you an example of how one can
be set up. For the example, we'll set up a module in a format that would
be called by C. We'll make the module switch to video mode 1 (40-column
text in color) and set the border color to blue.
Here is the assembly module (see Chapter 8, page 161, for general notes
on the format):
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT
PUBLIC _Blue40
_Blue40 PROC near
push bp ; save previous BP value
mov bp,sp ; use BP to access the stack
; set video mode
mov ah,0 ; BIOS service number
mov al,1 ; video mode number
int 10h ; call BIOS to set 40x25 text mode
; set border color
mov ah,0Bh ; BIOS service number
mov bh,0 ; subservice number
mov bl,1 ; color value (blue)
int 10h ; call BIOS to set border color
pop bp ; restore previous BP value
ret
_Blue40 ENDP
_TEXT ENDS
────────────────────────────────────────────────────────────────────────────
Chapter 10 ROM BIOS Disk Services
The ROM BIOS Disk Services
Service 00H (decimal 0): Reset Disk System
Service 01H (decimal 1): Get Disk Status
Service 02H (decimal 2): Read Disk Sectors
Service 03H (decimal 3): Write Disk Sectors
Service 04H (decimal 4): Verify Disk Sectors
Service 05H (decimal 5): Format Disk Track
Service 06H (decimal 6): Format PC/XT Fixed-Disk Track
Service 07H (decimal 7): Format PC/XT Fixed Disk
Service 08H (decimal 8): Get Disk-Drive Parameters
Service 09H (decimal 9): Initialize Fixed-Disk Parameter Tables
Service 0AH and 0BH (decimal 10 and 11): Read and Write Long
Service 0CH (decimal 12): Seek to Cylinder
Service 0DH (decimal 13): Alternate Fixed-Disk Reset
Service 10H (decimal 16): Test for Drive Ready
Service 11H (decimal 17): Recalibrate Drive
Service 15H (decimal 21): Get Disk Type
Service 16H (decimal 22): Diskette Change Status
Service 17H (decimal 23): Set Diskette Type
Service 18H (decimal 24): Set Media Type for Format
Service 19H (decimal 25): Park Heads
Service 1AH (decimal 26): Format ESDI Unit
Disk-Base Tables
Comments and Examples
We're now going to cover the disk services provided by the ROM BIOS. To
understand the logical structure of the contents of a disk, see Chapter
5, particularly pages 106 through 121. For information about the
higher-level disk services provided by DOS, see Chapters 15 through 18.
Generally speaking, disk operations are best left to disk operating
systems. If you decide to use any of the ROM BIOS disk services, we
recommend that you read the section entitled "Comments and Examples" on
page 212 of this chapter.
The ROM BIOS Disk Services
The original IBM PC ROM BIOS offered only six different disk services. As
the diskette and fixed-disk subsystems of the PC and PS/2 family have
become increasingly sophisticated, the number of ROM BIOS services that
support disk I/O has increased. To keep the ROM BIOS software modular and
flexible, IBM separated the support routines for fixed-disk subsystems
from the diskette support routines. Nevertheless, the number of BIOS disk
services has grown from six on the original IBM PC to 22 in the PS/2s.
(See Figure 10-1.)
All ROM BIOS disk services are invoked with interrupt 13H (decimal 19) and
selected by loading the service number into the AH register. Disk drives
are identified by a zero-based number passed in DL, with the high-order
bit set to 1 to indicate a fixed disk. Thus the first diskette drive in
the computer is identified by drive number 00H, and the first fixed disk
is designated by drive number 80H.
The ROM BIOS uses a set of descriptive parameter tables called disk-base
tables to gain information about the capabilities of the disk controller
hardware and the disk media. The ROM BIOS maintains the segmented
addresses of the disk-base tables it uses in interrupt vectors: The
address of the table for the current diskette drive is in the interrupt
1EH vector (0000:0074H); addresses of tables for the first and second
fixed drives are in interrupt vectors 41H (0000:0104H) and 46H
(0000:0118H).
For most programmers, the disk-base tables are an invisible part of the
disk services. However, some disk-base parameters may occasionally need to
be changed for special purposes. For this reason we include a brief
description of the disk-base table toward the end of this chapter.
The following sections describe each of the ROM BIOS services.
╓┌─┌───────────┌────────────────────────────────────┌────────────┌───────────╖
Service Description Diskette Fixed Disk
──────────────────────────────────────────────────────────────────────────
00H Reset Disk System. x x
01H Get Disk Status. x x
02H Read Disk Sectors. x x
03H Write Disk Sectors. x x
04H Verify Disk Sectors. x x
05H Format Disk Track. x x
06H Format PC/XT Fixed-Disk Track. x
07H Format PC/XT Fixed Disk. x
08H Get Disk-Drive Parameters. x x
09H Initialize Fixed-Disk Parameter x
Tables.
0AH Read Long. x
Service Description Diskette Fixed Disk
──────────────────────────────────────────────────────────────────────────
0AH Read Long. x
0BH Write Long. x
0CH Seek to Cylinder. x
0DH Alternate Fixed-Disk Reset. x
10H Test for Drive Ready. x
11H Recalibrate Drive. x
15H Get Disk Type. x x
16H Get Diskette Change Status. x
17H Set Diskette Type. x
18H Set Media Type for Format. x
19H Park Heads. x
1AH Format ESDI Unit. x
──────────────────────────────────────────────────────────────────────────
Figure 10-1. The ROM BIOS disk services.
Service 00H (decimal 0): Reset Disk System
Service 00H resets the disk controller and drive. This service does not
affect the disk itself. Instead, a reset through service 00H forces the
ROM BIOS disk-support routines to start from scratch for the next disk
operation by recalibrating the disk drive's read/write head──an operation
that positions the head on a certain track. This reset service is normally
used after an error in any other drive operation.
When you call service 00H for a fixed-disk drive, the ROM BIOS also resets
the diskette-drive controller. If you want to reset the fixed-disk
controller only, use service 0DH. (See page 207.)
Service 01H (decimal 1): Get Disk Status
Service 01H (decimal 1) reports the disk status in register 0AH. The
status is preserved after each disk operation, including the read, write,
verify, and format operations. By preserving the disk status, an
error-handling or error-reporting routine can be completely independent of
the routines that operate the disk. This can be very useful. Under the
right circumstances, you can rely on DOS or your programming language to
drive the disk (a wise choice; see "Comments and Examples" on page 212),
and at the same time have your program find out and report the details of
what went wrong. See Figure 10-2 for details of the status byte.
╓┌─┌────────────────────────┌────────────────────────────────────────────────╖
Value (hex) Meaning
──────────────────────────────────────────────────────────────────────────
00H No error
01H Bad command
02H Address mark not found
03H Write attempted on
write-protected disk☼
04H Sector not found
05H Reset failed☼
06H Diskette removed☼
07H Bad parameter table☼
08H DMA overrun
09H DMA across 64 KB boundary
0AH Bad sector flag☼
0BH Bad cylinder☼
0CH Bad media type☼
0DH Invalid number of sectors on format☼
0EH Control data address mark detected☼
Value (hex) Meaning
──────────────────────────────────────────────────────────────────────────
0EH Control data address mark detected☼
0FH DMA arbitration level out of range☼
10H Bad CRC or ECC
11H ECC corrected data error☼
20H Controller failed
40H Seek failed
80H Time out
AAH Drive not ready☼
BBH Undefined error☼
CCH Write fault☼
EOH Status error☼
FFH Sense operation failed
──────────────────────────────────────────────────────────────────────────
Figure 10-2. The value of the disk status byte returned in register AH by
service 01H.
Service 02H (decimal 2): Read Disk Sectors
Service 02H (decimal 2) reads one or more disk sectors into memory. If you
want to read more than one sector, every sector must be on the same track
and read/write head. This is largely because the ROM BIOS doesn't know how
many sectors might be on a track, so it can't know when to switch from one
head or track to another. Usually, this service is used for reading either
individual sectors or an entire trackful of sectors for bulk operations
such as DISKCOPY in DOS. Various registers are used for control
information in a read operation. They are summarized in Figure 10-3.
Parameters Status Results
──────────────────────────────────────────────────────────────────────────
DL = drive number If CF = 0, then no error and AH = 0
DH = head number If CF = 1, then error and AH contains
service 01H status bits
CH = cylinder number☼
low-order 8 bits of cylinder
number☼
CL = sector number☼
high-order 2 bits of cylinder number
plus 6-bit sector number☼
AL = number of sectors to be read
ES:BX = address of buffer
──────────────────────────────────────────────────────────────────────────
Figure 10-3. The registers used for control information by the read,
write, verify, and format services.
DL contains the drive number, and DH contains the diskette side or
fixed-disk read/write head number.
CH and CL identify, for diskettes, the cylinder and sector number to be
read. CH contains the cylinder number, which should be less than the total
number of cylinders on the formatted diskette. (See Chapter 5 for a table
of standard IBM formats.) Of course, the cylinder number can be higher
with non-IBM formats or with some copy-protection schemes. CL contains the
sector number.
For fixed disks, there may be more than 256 cylinders, so the ROM BIOS
requires you to specify a 10-bit cylinder number in CH and CL: You must
place the 8 low-order bits of the cylinder number in CH. The 2 high-order
bits of CL contain the 2 high-order bits of the cylinder number. The 6
low-order bits of CL designate the sector number to be read. Don't forget
that sectors are numbered from 1, unlike drives, cylinders, or heads
(sides).
AL contains the number of sectors to be read. For diskettes, this is
normally either 1, 8, 9, 15, or 18. We are warned by IBM not to request 0
sectors.
ES:BX contains the buffer location. The location of the memory area where
the data will be placed is provided by a segmented address given in this
register pair.
The data area should be big enough to accommodate as much as is read; keep
in mind that while normal DOS sectors are 512 bytes, sectors can be as
large as 1024 bytes. (See the format service that follows.) When this
service reads more than one sector, it lays the sectors out in memory one
right after another.
CF (the carry flag) contains the error status of the operation. The result
of the operation is actually reported through a combination of the carry
flag and the AH register. If CF = 0, no error occurred, AH will also be 0,
and, for a diskette, the number of sectors read will be returned in AL. If
CF = 1, an error did occur, and AH will contain the status value detailed
under service 01H, the status service.
When using service 02H with a diskette drive or any other active diskette
service, remember that the diskette-drive motor takes some time to reach a
working speed and that none of these services waits for this to happen.
Although our own experience with the ROM BIOS diskette services suggests
that this is rarely a problem, IBM recommends that any program using these
services try three times before assuming that an error is real and that it
use the reset service between tries. The logic of the suggested operation
is as follows (partly expressed in BASIC):
10 ERROR COUNT = 0
20 WHILE ERROR.COUNT < 3
30 ' do read/write/verify/format operation
40 ' error checking here: if no error goto 90
50 ERROR.COUNT = ERROR.COUNT + 1
60 ' do reset operation
70 WEND
80 ' act on error
90 ' carry on after success
Be sure to see the section on page 209 for the effect of the disk-base
table on the reset operation.
Service 03H (decimal 3): Write Disk Sectors
Service 03H (decimal 3) writes one or more sectors to a disk──the reverse
of service 02H. All registers, details, and comments given for service
02H also apply to service 03H. (Also see Figure 10-3.) The disk sectors
must be formatted before they can be written to.
Service 04H (decimal 4) : Verify Disk Sectors
Service 04H (decimal 4) verifies the contents of one or more disk sectors.
This operation is not what many people think it is: No comparison is made
between the data on the disk and the data in memory. The verification
performed by this service simply checks that the sectors can be found and
read and that the cyclical redundancy check (CRC) is correct. The CRC acts
as a sophisticated parity check for the data in each sector and will
detect most errors, such as lost or scrambled bits, very reliably.
Most programmers use the verify service to check the results of a write
operation after using service 03H, but you can verify any part of a disk
at any time. The DOS FORMAT program, for example, verifies each track
after it is formatted. However, many people regard verification as an
unnecessary operation because the disk drives are so reliable and because
ordinary error reporting works so well. Even DOS doesn't verify a write
operation unless you ask it to with the VERIFY ON command.
──────────────────────────────────────────────────────────────────────────
NOTE:
It's worth pausing here to note that there is nothing unusual or
alarming about having "bad tracks" marked on a disk, particularly a
fixed disk. In fact, it is quite common for a fixed disk to have a few
bad patches on it. The DOS FORMAT program notices bad tracks and marks
them as such in the disk's file-allocation table. Later, the bad-track
marking tells DOS that these areas should be bypassed. Bad tracks are
also common on diskettes; with a diskette, unlike a fixed disk, you have
the option of throwing away the defective media and using only perfect
disks.
──────────────────────────────────────────────────────────────────────────
The verify service operates exactly as do the read and write services and
uses the same registers. The only difference between them is that the
verify operation does not use any memory area and therefore does not use
the register pair ES:BX.
Service 05H (decimal 5) : Format Disk Track
Service 05H (decimal 5) formats one track. The format service operates as
do the read and write services except that you need not specify a sector
number in CL. All other parameters are as shown in Figure 10-3.
Because formatting is done one full track at a time, you cannot format
individual sectors. However, on a diskette you can specify individual
characteristics for each sector on a track.
Every sector on a diskette track has 4 descriptive bytes associated with
it. You specify these 4 bytes for each sector to be formatted by creating
a table of 4-byte groups and passing the table's address in the register
pair ES:BX. When you format a disk track, the 4-byte groups are written to
the diskette immediately in front of the individual sectors in the track.
The 4 bytes of data associated with a sector on the disk are known as
address marks and are used by the disk controller to identify individual
sectors during the read, write, and verify operations. The 4 bytes are
referred to as C for cylinder, H for head, R for record (or sector
number), and N for number of bytes per sector (also called the size code).
When a sector is being read or written, the diskette controller searches
the diskette track for the sector's ID, the essential part of which is R,
the record or sector number. The cylinder and head parameters are not
actually needed in this address mark because the read/write head is
positioned mechanically at the proper track and the side is selected
electronically, but they are recorded and tested as a safety check.
The size code (N) can take on any one of the four standard values shown in
Figure 10-4. The normal setting is code 2 (512 bytes).
Sectors are numbered on the diskette in the order specified by R. On
diskettes, the sectors are normally numbered in numeric sequence (unless
rearranged for copy protection), but on fixed disks the order of the
sectors can be rearranged (interleaved), either for better performance or
to create timing differences for copy-protection purposes. The actual
interleave used on a fixed disk depends on the capabilities of the
disk-controller hardware. For example, the PC/XT's fixed disk has its
sectors interleaved so that logically consecutive sectors are physically
located six sectors apart.
N Sector Size (bytes) Sector Size (KB)
──────────────────────────────────────────────────────────────────────────
0 128 1/8
1 256 1/4
2 512 1/2
3 1024 1
──────────────────────────────────────────────────────────────────────────
Figure 10-4. The four standard sizes of the N size code.
To format a diskette track using service 05H, perform the following steps:
1. Call service 17H to inform the ROM BIOS what kind of diskette is to
be formatted. (See page 208 for more about service 17H.) This service
needs to be called only once.
2. Call service 18H to describe the diskette media to the ROM BIOS. (See
page 209.)
3. Create a table of address marks for the track. There must be a 4-byte
entry in the table for each sector. For example, for track 0, side 1
of a typical nine-sector DOS diskette, the table would contain nine
entries:
0 1 1 2 0 1 2 2 0 1 3 2 ... 0 1 9 2
4. Call service 05H to format the track.
The method for formatting a fixed-disk track is somewhat different. You
should omit the calls to services 17H and 18H (steps 1 and 2 above)
because there is no need to describe the disk media to the ROM BIOS. Also,
with a PC/AT or PS/2, the table whose address you pass in 3 step has a
format that consists only of alternating flag bytes (00H = good sector,
80H = bad sector) and sector number (R) bytes. With a PC/XT, you don't
need a table at all. Instead, you call service 05H with an interleave
value in AL, and the ROM BIOS does the rest.
You may want to verify the formatting process by following each call to
service 05H with a call to service 04H.
When a diskette track is formatted, the diskette drive pays attention to
the diskette's index hole and uses it as a starting marker to format the
track. The index hole is ignored in all other operations (read, write, or
verify), and tracks are simply searched for by their address marks.
Nothing in this format service specifies the initial data value written
into each formatted sector of a diskette. That is controlled by the
disk-base table. (See page 209.)
──────────────────────────────────────────────────────────────────────────
NOTE:
Service 05H should not be used with ESDI drives in PS/2s. Use service
1AH instead.
──────────────────────────────────────────────────────────────────────────
Using Service 05H for Copy Protection
Diskette tracks can be formatted in all sorts of ways, but DOS can only
read certain formats. Consequently, some copy-protection schemes are based
on an unconventional format that prevents the ROM BIOS or the operating
system from successfully reading and copying data. You can choose from
several different copy-protection methods:
■ You can rearrange the order of the sectors, which alters the access
time in a way that the copy-protection scheme can detect.
■ You can squeeze more sectors onto a track (10 is about the outside
limit for 512-byte sectors on a 360 KB diskette).
■ You can simply leave out a sector number.
■ You can add a sector with an oddball address mark (for example, you can
make C = 45 or R = 22).
■ You can specify one or more sectors to be an unconventional size.
Any of these techniques can be used either for copy protection or for
changing the operating characteristics of the diskette. Depending on what
options are used, a conventionally formatted diskette may have its
copy-protection characteristics completely hidden from DOS.
Service 06H (decimal 6): Format PC/XT Fixed-Disk Track
This service is provided only in the PC/XT fixed-disk ROM BIOS. This
service commands the XT's fixed-disk controller to format a track in which
the disk media is defective. The disk controller records which sectors are
defective in a table located in a reserved cylinder. The register
parameters are the same as those shown in Figure 10-3, except that
register AL contains a sector interleave value and no address need be
specified in ES:BX.
Service 07H (decimal 7): Format PC/XT Fixed Disk
This service, like service 06H, is supported only in the PC/XT fixed-disk
ROM BIOS. It formats the entire fixed-disk drive, starting at the cylinder
number specified in CH and CL. Register parameters for service 07H are the
same as for service 05H (Figure 10-3), except that register AL contains a
sector interleave value and no head number need be specified in register
DH.
Service 08H (decimal 8): Get Disk-Drive Parameters
In the PC/AT and PS/2 BIOS, service 08H (decimal 8) returns disk-drive
parameters for the drive whose number you specify in DL. DL reports the
number of disk drives attached to the disk controller, so diskette and
fixed-disk drive counts are reported separately. DH reports the maximum
head number, CH returns the maximum cylinder number, and CL returns the
highest valid sector number plus the 2 high-order bits of the maximum
cylinder number.
For diskette drives, the PC/AT ROM BIOS (after 1/10/84) and the PS/2 ROM
BIOS also report the drive type in BL: 01H = 360 KB, 5-1/4 inch; 02H = 1.2
MB, 5-1/4 inch; 03H = 720 KB, 3-1/2 inch; 04H = 1.44 MB, 3-1/2 inch.
Service 09H (decimal 9): Initialize Fixed-Disk Parameter Tables
Service 09H (decimal 9) establishes the disk-base tables for two
fixed-disk drives for the PC/AT or PS/2 ROM BIOS. Call this service with a
valid fixed-disk drive number in DL and with the interrupt 41H and 46H
vectors containing the addresses of disk-base tables for two different
fixed-disk drives. Because fixed disks are nonremovable, this service
should only be used to install a "foreign" disk drive not recognized by
the ROM BIOS or the operating system. For more details, see the IBM BIOS
Interface Technical Reference Manual.
──────────────────────────────────────────────────────────────────────────
NOTE:
Do not use service 09H for PS/2 ESDI drives.
──────────────────────────────────────────────────────────────────────────
Service 0AH and 0BH (decimal 10 and 11): Read and Write Long
Service 0AH (decimal 10) reads, and service 0BH (decimal 11) writes,
"long" sectors on PC/AT or PS/2 fixed disks. A long sector consists of a
sector of data plus a 4- or 6-byte error correction code (ECC) that the
fixed-disk controller uses for error checking and error correction of the
sector's data. These services use the same register parameters as parallel
services 02H and 03H.
──────────────────────────────────────────────────────────────────────────
NOTE:
The IBM BIOS Interface Technical Reference Manual states that services
0AH and 0BH are "reserved for diagnostics," so stay away from these
services unless you have a very good reason for using them.
──────────────────────────────────────────────────────────────────────────
Service 0CH (decimal 12): Seek to Cylinder
Service 0CH (decimal 12) performs a seek operation that positions the disk
read/write heads at a particular cylinder on a fixed disk. Register DL
provides the drive ID, DH provides the head number, and CH and CL provide
the 10-bit cylinder number.
Service 0DH (decimal 13): Alternate Fixed-Disk Reset
For fixed-disk drives, this service is the same as service 00H (reset disk
system) except that the ROM BIOS does not automatically reset the
diskette-drive controller. This service is available only in the PC/AT and
PS/2 ROM BIOS; it should not be used with the PS/2 ESDI drives.
Service 10H (decimal 16): Test for Drive Ready
Service 10H (decimal 16) tests to see if a fixed-disk drive is ready. The
drive is specified in register DL and the status is returned in register
AH.
Service 11H (decimal 17): Recalibrate Drive
Service 11H (decimal 17) recalibrates a fixed-disk drive. The drive is
specified in register DL and the status is returned in register AH.
Service 15H (decimal 21): Get Disk Type
Service 15H (decimal 21) returns information about the type of disk drive
installed in a PC/AT or PS/2. Given the drive ID in register DL, it
returns in register AH one of four disk-type indicators. If AH = 00H, no
drive is present for the specified drive ID; if AH = 01H, a diskette drive
that cannot sense when the disk has been changed (typical of many PC and
PC/XT disk drives) is installed; if AH = 02H, a diskette drive that can
sense a change of disks (drives like the AT's high-capacity diskette
drives) is installed; finally, if AH = 03H, a fixed-disk drive is
installed. When the drive type is 3, the register pair CX:DX contains a
4-byte integer that gives the total number of disk sectors on the drive.
Service 16H (decimal 22): Diskette Change Status
In the PC/AT and PS/2 ROM BIOS, service 16H (decimal 22) reports whether
the diskette in the drive specified in DL was changed. The status is
reported in AH (Figure 10-5).
Remember several important points about service 16H. First, before you use
this ROM BIOS service, call service 15H to ensure that the diskette-drive
hardware can sense when a diskette is changed. Also, you should follow a
call to service 16H with a call to service 17H (Set Diskette Type)
whenever you detect a diskette change.
Keep in mind that the hardware can only detect whether the diskette-drive
door was opened; it cannot tell whether a different physical diskette was
placed in the drive. You must still read data from the diskette to
determine whether a different diskette is actually in the drive. Data such
as a volume label, the root directory, or a file allocation table can help
to uniquely identify a diskette.
Value Meaning
──────────────────────────────────────────────────────────────────────────
AH = 00H No diskette change.
AH = 01H Service called with invalid parameter.
AH = 06H Diskette has been changed.
AH = 80H Diskette drive not ready.
──────────────────────────────────────────────────────────────────────────
Figure 10-5. Status values returned in AH by diskette service 16H.
Service 17H (decimal 23): Set Diskette Type
In the PC/AT and PS/2 ROM BIOS, service 17H (decimal 23) describes the
type of diskette in use in a specified drive. Call this service with a
drive ID in register DL and a diskette-type ID in AL. (See Figure 10-6.)
The ROM BIOS resets the diskette change status if it was previously set.
It then records the diskette type in an internal status variable that can
be referenced by other ROM BIOS services.
Value Meaning
──────────────────────────────────────────────────────────────────────────
AL = 01H 320/360 KB diskette in 360 KB drive
AL = 02H 360 KB diskette in 1.2 MB drive
AL = 03H 1.2 MB diskette in 1.2 MB drive
AL = 04H 720 KB diskette in 720 KB drive (PC/AT or PS/2)
or 720 KB or 1.44 MB diskette in 1.44 MB drive
(PS/2)
──────────────────────────────────────────────────────────────────────────
Figure 10-6. Diskette-type ID values for diskette service 17H.
Service 18H (decimal 24): Set Media Type for Format
Service 18H (decimal 24) describes the number of tracks and sectors per
track to the ROM BIOS before it formats a diskette in a specified drive.
These values are placed in registers CH, CL, and DL when you call this
service (see Figure 10-3). This service is available only in the PC/AT
and PS/2 ROM BIOS.
Service 19H (decimal 25): Park Heads
Service 19H (decimal 25) parks the drive heads for the PS/2 fixed disk
whose drive ID you specify in register DL. Calling this function causes
the disk controller to move the drive heads away from the portion of the
disk media where data is stored. This is a good idea if you plan to move
the computer because it may prevent mechanical damage to the heads or to
the surfaces of the disk media. On the Reference Diskette that accompanies
every PS/2, IBM supplies a utility program that uses this ROM BIOS service
to park the heads.
Service 1AH (decimal 26): Format ESDI Unit
This service is provided only in the ROM BIOS of the ESDI (Enhanced Small
Device Interface) adapter for high-capacity PS/2 fixed disks. It formats a
fixed disk attached to this adapter. See the IBM BIOS Interface Technical
Reference Manual for more details.
Disk-Base Tables
As we mentioned near the beginning of this chapter, the ROM BIOS maintains
a set of disk-base tables that describe the capabilities of each diskette
drive and fixed-disk drive in the computer. During system startup, the ROM
BIOS associates an appropriate disk-base table with each fixed-disk drive.
(In the PC/AT and PS/2s, a data byte in the nonvolatile CMOS RAM
designates which of several ROM tables to use.) There is no reason to
change the parameters in the fixed-disk tables once they have been set up
by the ROM BIOS. Doing so may lead to garbled data on the disk.
The situation is different in the case of diskette drives. The parameters
in the disk-base table associated with a diskette drive may need to be
updated to accommodate different diskette formats. We'll spend the next
few pages describing the structure of a disk-base table for a diskette
drive and showing how a modified table can be useful.
The disk-base table comprises the 11 bytes shown in Figure 10-7.
Bytes 0 and 1 are referred to as the specify bytes. They are part of the
command strings sent to the diskette-drive controller, which in IBM's
technical reference manuals is also called the NEC (Nippon Electric
Company) controller. The 4 high-order bits of byte 0 specify the step-rate
time (SRT), which is the time the drive controller allows for the drive
heads to move from track to track. The default ROM BIOS SRT value for
diskette drives is conservative; for some drives, DOS reduces this value
to speed up drive performance.
Byte 2 specifies how long the diskette motor is to be left running after
each operation. The motor is left on in case the diskette is needed again.
The value is in units of clock ticks (roughly 18.2 ticks per second). All
versions of the table have this set to 37 (25H)──meaning that the motor
stays on for about 2 seconds.
Offset Use
──────────────────────────────────────────────────────────────────────────
00H Specify byte 1: step-rate time, head-unload time
01H Specify byte 2: head-load time, DMA mode
02H Wait time until diskette motor turned off
03H Bytes per sector: 0 = 128; 1 = 256; 2 = 512; 3 = 1024
04H Last sector number
05H Gap length between sectors for read/write operations
06H Data length when sector length not specified
07H Gap length between sectors for formatting operations
08H Data value stored in formatted sectors
09H Head-settle time
0AH Motor start-up time
──────────────────────────────────────────────────────────────────────────
Figure 10-7. The use of the 11 bytes in the disk-base table for a
diskette drive.
Byte 3 gives the sector length code──the same N code used in the format
operation. (See page 203 under service 05H.) This is normally set to 2,
representing the customary sector length of 512 bytes. In any read, write,
or verify operation, the length code in the disk base must be set to the
proper value, especially when working with sectors of unconventional
length.
Byte 4 gives the sector number of the last sector on the track.
Byte 5 specifies the gap size between sectors, which is used when reading
or writing data. In effect, it tells the diskette-drive controller how
long to wait before looking for the next sector's address marking so that
it can avoid looking at nonsense on the diskette. This length of time is
known as the search gap.
Byte 6 is called the data transfer length (DTL) and is set to FFH (decimal
255). This byte sets the maximum data length when the sector length is not
specified.
Byte 7 sets the gap size between sectors when a track is formatted.
Naturally, it is bigger than the search gap at offset 5. The normal format
gap-size value varies with the diskette drive. For example, the value is
54H for the PC/AT's 1.2 MB drive and 6CH for 3-1/2-inch PS/2 diskette
drives.
Byte 8 provides the data value stored in each byte of the sectors when a
diskette track is formatted. The default value is F6H, the division
symbol. You can change it to anything you want, if you can think of a good
reason to do so.
Byte 9 sets the head-settle time, which is how long the system waits for
vibration to end after seeking to a new track. This value also depends on
the drive hardware. On the original PC, the value was 19H (25
milliseconds), but the ROM BIOS default for the PC/AT 1.2 MB drive and the
PS/2 diskette drives is only 0FH (15 milliseconds).
Byte 0AH (decimal 10), the final byte of the disk-base table, sets the
amount of time allowed for the diskette-drive motor to get up to speed and
is measured in 1/8 seconds.
It's fun to tinker with the disk-base values; there are enough of them to
give you an opportunity for all sorts of excitement and mischief. To do
this, you need to write a program that builds your customized disk-base
table in a buffer in memory. Then tell the ROM BIOS to use your table by
carrying out the following steps:
1. Save the segmented address of the current disk base table. (This is
the value in the interrupt 1EH vector, 0000:0078H.)
2. Store the segmented address of your modified table in the interrupt
1EH vector.
3. Call ROM BIOS disk service 00H to reset the disk system. The ROM BIOS
will reinitialize the diskette-drive controller with parameters from
your table.
When you're finished, be sure to restore the address of the previous
disk-base table and reset the disk system again.
Comments and Examples
In the last chapter, where we covered the ROM BIOS video services, we
were able to recommend that you make direct use of the ROM BIOS services
when DOS or your programming language does not provide the support you
need. But in the case of the ROM BIOS disk services, things are different.
For the disk operations that a program would normally want performed, the
manipulation and supervision of disk input/output should be left to DOS
and performed either through the conventional file services of a
programming language or through the DOS services. (See Chapters 14
through 18.) There are several reasons for this. The main reason is that
it is far easier to let DOS do the work. The DOS facilities take care of
all fundamental disk operations, including formatting and labeling disks,
cataloging files, and basic read and write operations. Most of the time it
isn't necessary to go any deeper into the system software. However, there
are times when you may want to work with disk data in an absolute and
precise way, usually for copy protection. This is when you should use the
ROM BIOS services.
For our example, we'll use C to call a couple of subroutines that use ROM
BIOS functions 02H and 03H to read and write absolute disk sectors. We
start by defining how we want the interface to look from the C side, which
the following program illustrates. If you are not familiar with C and
don't want to decipher this routine, you can pass it by and still get the
full benefit by studying the assembly-language interface example that
follows it.
main()
{
unsigned char Buffer[512]; /* a 512-byte buffer for reading */
/* or writing one sector */
int Drive;
int C,H,R; /* address mark parameters */
int StatusCode; /* status value returned by BIOS */
StatusCode = ReadSector( Drive, C, H, R, (char far *)Buffer );
StatusCode = WriteSector( Drive, C, H, R, (char far *)Buffer );
}
This C fragment shows how you would call the ROM BIOS read and write
services from a high-level language. The functions ReadSector() and
WriteSector() are two assembly-language routines that use interrupt 13H to
interface with the ROM BIOS disk services. The parameters are familiar: C,
H, and R are the cylinder, head, and sector numbers we described earlier.
The C compiler passes the buffer address as a segment and offset because
of the explicit type cast (char far *).
The form of the assembly-language interface should be familiar if you read
the general remarks in Chapter 8 on page 161 or studied the example in
Chapter 9 on page 194. The assembly-language routines themselves copy the
parameters from the stack into the registers. The trick is in how the
cylinder number is processed: The 2 high-order bits of the 10-bit cylinder
number are combined with the 6-bit sector number in CL.
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT
PUBLIC _ReadSector
_ReadSector PROC near ; routine to read one sector
push bp
mov bp,sp ; address the stack through BP
mov ah,2 ; AH = ROM BIOS service number 02h
call DiskService
pop bp ; restore previous BP
ret
_ReadSector ENDP
PUBLIC _WriteSector
_WriteSector PROC near ; routine to write one sector
push bp
mov bp,sp
mov ah,3 ; AH = ROM BIOS service number 03h
call DiskService
pop bp
ret
_WriteSector ENDP
DiskService PROC near ; Call with AH = ROM BIOS service n
push ax ; save service number on stack
mov dl,[bp+4] ; DL = drive ID
mov ax,[bp+6] ; AX = cylinder number
mov dh,[bp+8] ; DH = head number
mov cl,[bp+10] ; CL = sector number
and cl,00111111b ; limit sector number to 6 bits
les bx,[bp+12] ; ES:BX -> buffer
ror ah,1 ; move bits 8 and 9
ror ah,1 ; of cylinder number
; to bits 6 and 7 of AH
and ah,11000000b
mov ch,al ; CH = bits 0-7 of cylinder number
or cl,ah ; copy bits 8 and 9
; of cylinder number
; to bits 6 and 7 of CL
pop ax ; AH = ROM BIOS service number
mov al,1 ; AL = 1 (# of sectors to read/writ
int 13h ; call ROM BIOS service
mov al,ah ; leave return status
xor ah,ah ; # in AX
ret
DiskService ENDP
_TEXT ENDS
Note how the code that copies the parameters from the stack to the
registers is consolidated in a subroutine, DiskService. When you work with
the ROM BIOS disk services, you'll find that you can often use subroutines
similar to DiskService because most of the ROM BIOS disk services use
similar parameter register assignments.
────────────────────────────────────────────────────────────────────────────
Chapter 11 ROM BIOS Keyboard Services
Accessing the Keyboard Services
Service 00H (decimal 0): Read Next Keyboard Character
Service 01H (decimal 1): Report Whether Character Ready
Service 02H (decimal 2): Get Shift Status
Service 03H (decimal 3): Set Typematic Rate and Delay
Service 05H (decimal 5): Keyboard Write
Service 10H (decimal 16): Extended Keyboard Read
Service 11H (decimal 17): Get Extended Keystroke Status
Service 12H (decimal 18): Get Extended Shift Status
Comments and Example
Although the ROM BIOS services for the keyboard are not as numerous or as
complicated as those for the display screen (Chapter 9) and for diskette
drives (Chapter 10), the ROM BIOS keyboard services are important enough
to warrant their own chapter. All other ROM BIOS services are gathered
together in Chapter 12.
Accessing the Keyboard Services
The keyboard services are invoked with interrupt 16H (decimal 22). As with
all other ROM BIOS services, the keyboard services are selected according
to the value in register AH. Figure 11-1 lists the ROM BIOS keyboard
services.
Service Description
──────────────────────────────────────────────────────────────────────────
00H Read Next Keyboard Character.
01H Report Whether Character Ready.
02H Get Shift Status.
03H Set Typematic Rate and Delay.
05H Keyboard Write.
10H Extended Keyboard Read.
11H Get Extended Keystroke Status.
12H Get Extended Shift Status.
──────────────────────────────────────────────────────────────────────────
Figure 11-1. The ROM BIOS keyboard services.
Service 00H (decimal 0): Read Next Keyboard Character
Service 00H (decimal 0) reports the next keyboard input character. If a
character is ready in the ROM BIOS keyboard buffer, it is reported
immediately. If not, the service waits until one is ready. As described on
page 134, each keyboard character is reported as a pair of bytes, which
we call the main and auxiliary bytes. The main byte, returned in AL, is
either 0 for special characters (such as the function keys) or else an
ASCII code for ordinary ASCII characters. The auxiliary byte, returned in
AH, is either the character ID for special characters or the standard
PC-keyboard scan code that identifies which key was pressed.
If no character is waiting in the keyboard buffer when service 00H is
called, the service waits──essentially freezing the program that called
it──until a character does appear. The service we'll discuss next allows a
program to test for keyboard input without the risk of suspending program
execution.
Contrary to what some versions of the IBM PC Technical Reference Manual
suggest, services 00H and 01H apply to both ordinary ASCII characters and
special characters, such as function keys.
Service 01H (decimal 1): Report Whether Character Ready
Service 01H (decimal 1) reports whether a keyboard input character is
ready. This is a sneak-preview or look-ahead operation: Even though the
character is reported, it remains in the keyboard input buffer of the ROM
BIOS until it is removed by service 00H. The zero flag (ZF) is used as the
signal: 1 indicates no input is ready; 0 indicates a character is ready.
Take care not to be confused by the apparent reversal of the flag values──
1 means no and 0 means yes, in this instance. When a character is ready
(ZF = 0), it is reported in AL and AH, just as it is with service 00H.
This service is particularly useful for two commonly performed program
operations. One is test-and-go, where a program checks for keyboard action
but needs to continue running if there is none. Usually, this is done to
allow an ongoing process to be interrupted by a keystroke. The other
common operation is clearing the keyboard buffer. Programs can generally
allow users to type ahead, entering commands in advance; however, in some
operations (for example, at safety-check points, such as "OK to end?")
this practice can be unwise. In these circumstances, programs need to be
able to flush the keyboard buffer, clearing it of any input. The keyboard
buffer is flushed by using services 00H and 01H, as this program outline
demonstrates:
call service 01H to test whether a character is available in the
keyboard buffer
WHILE (ZF = 0)
BEGIN
call service 00H to remove character from keyboard buffer
call service 01H to test for another character
END
Contrary to what some technical reference manuals suggest, services 00H
and 01H apply to both ordinary ASCII characters and special characters,
such as function keys.
Service 02H (decimal 2): Get Shift Status
Service 02H (decimal 2) reports the shift status in register AL. The shift
status is taken bit by bit from the first keyboard status byte, which is
kept at memory location 0040:0017H. Figure 11-2 describes the settings of
each bit. (See page 137 for information about the other keyboard status
byte at 0040:0018H.)
Bit
7 6 5 4 3 2 1 0 Meaning
──────────────────────────────────────────────────────────────────────────
X . . . . . . . Insert state: 1 = active
. X . . . . . . CapsLock: 1 = active
. . X . . . . . NumLock: 1 = active
. . . X . . . . ScrollLock: 1 = active
. . . . X . . . 1 = Alt pressed
. . . . . X . . 1 = Ctrl pressed
. . . . . . X . 1 = Left Shift pressed
. . . . . . . X 1 = Right Shift pressed
──────────────────────────────────────────────────────────────────────────
Figure 11-2. The keyboard status bits returned to register AL using
keyboard service 02H.
Generally, service 02H and the status bit information are not particularly
useful. If you plan to do some fancy keyboard programming, however, they
can come in handy. You'll frequently see them used in programs that do
unconventional things, such as differentiating between the left and right
Shift keys.
Service 03H (decimal 3): Set Typematic Rate and Delay
Service 03H (decimal 3) was introduced with the PCjr, but has been
supported in both the PC/AT (in ROM BIOS versions dated 11/15/85 and
later) and in all PS/2s. It lets you adjust the rate at which the
keyboard's typematic function operates; that is, the rate at which a
keystroke repeats automatically while you hold down a key. This service
also lets you to adjust the typematic delay (the amount of time you can
hold down a key before the typematic repeat function takes effect).
To use this service, call interrupt 16H with AH = 03H, and AL = 05H. BL
must contain a value between 00H and 1FH (decimal 31) that indicates the
desired typematic rate (Figure 11-3). The value in BH specifies the
typematic delay (Figure 11-4). The default typematic rate for the PC/AT
is 10 characters per second; for PS/2s it is 10.9 characters per second.
The default delay for both the PC/AT and PS/2s is 500 ms.
──────────────────────────────────────────────────────────────────────────
00H = 30.0 0BH = 10.9 16H = 4.3
01H = 26.7 0CH = 10.0 17H = 4.0
02H = 24.0 0DH = 9.2 18H = 3.7
03H = 21.8 0EH = 8.6 19H = 3.3
04H = 20.0 0FH = 8.0 1AH = 3.0
05H = 18.5 10H = 7.5 1BH = 2.7
06H = 17.1 11H = 6.7 1CH = 2.5
07H = 16.0 12H = 6.0 1DH = 2.3
08H = 15.0 13H = 5.5 1EH = 2.1
09H = 13.3 14H = 5.0 1FH = 2.0
0AH = 12.0 15H = 4.6 20H through FFH -
Reserved
──────────────────────────────────────────────────────────────────────────
Figure 11-3. Values for register BL in keyboard service 03H. The rates
shown are in characters per second.
──────────────────────────────────────────────────────────────────────────
00H = 250
01H = 500
02H = 750
03H = 1000
04H through FFH - Reserved
──────────────────────────────────────────────────────────────────────────
Figure 11-4. Values for register BH in keyboard service 03H. The delay
values shown are in milliseconds.
Service 05H (decimal 5): Keyboard Write
Service 05H (decimal 5) is handy because it lets you store keystroke data
in the keyboard buffer as if a key were pressed. You must supply an ASCII
code in register CL and a keyboard scan code in CH. The ROM BIOS places
these codes into the keyboard buffer following any keystroke data that may
already be present there.
Service 05H lets a program process input as if it were typed at the
keyboard. For example, if you call service 05H with the following data,
the result is the same as if the keys R-U-N-Enter were pressed:
CH = 13H, CL = 52H, call service 05H (the R key)
CH = 16H, CL = 55H, call service 05H (the U key)
CH = 31H, CL = 4EH, call service 05H (the N key)
CH = 1CH, CL = 0DH, call service 05H (the Enter key)
If your program did this when it detected that the F2 function key was
pressed, the result would be the same as if the word RUN followed by the
Enter key had been typed. (If you use BASIC, this should sound familiar.)
Beware: The keyboard buffer can hold only 15 character codes, so you can
call service 05H a maximum of 15 consecutive times before the buffer
overflows and the function fails.
Service 10H (decimal 16): Extended Keyboard Read
Service 10H (decimal 16) performs the same function as service 00H, but
lets you take full advantage of the 101/102-key keyboard: It returns ASCII
character codes and keyboard scan codes for keys that don't exist on the
older 84-key keyboard. For example, the extra F11 and F12 keys found on
the 101/102-key keyboard are ignored by service 00H but can be read using
service 10H.
Another example: On the 101/102-key keyboard, an extra Enter key appears
to the right of the numeric keypad. When this key is pressed, service 00H
returns the same character code (0DH) and scan code (1CH) as it does for
the standard Enter key. Service 10H lets you differentiate between the two
Enter keys because it returns a different scan code (E0H) for the keypad
Enter key.
Service 11H (decimal 17): Get Extended Keystroke Status
Service 11H (decimal 17) is analogous to service 01H, but it, too, lets
you use the 101/102-key keyboard to full advantage. The scan codes
returned in register AH by this service distinguish between different keys
on the 101/102-key keyboard.
Service 12H (decimal 18): Get Extended Shift Status
Like services 10H and 11H, service 12H (decimal 18) provides additional
support for the 101/102-key keyboard. Service 12H expands the function of
service 02H to provide information on the extra shift keys provided on the
101/102-key keyboard. This service returns the same value in register AL
as service 02H (Figure 11-2), but it also returns an additional byte of
flags in register AH (Figure 11-5).
This extra byte indicates the status of each individual Ctrl and Alt key.
It also indicates whether the Sys Req, Caps Lock, Num Lock, or Scroll Lock
keys are currently pressed. This information lets you detect when a user
presses any combination of these keys at the same time.
Bit Meaning
7 6 5 4 3 2 1 0
──────────────────────────────────────────────────────────────────────────
X . . . . . . . Sys Req pressed
. X . . . . . . Caps Lock pressed
. . X . . . . . Num Lock pressed
. . . X . . . . Scroll Lock pressed
. . . . X . . . Right Alt pressed
. . . . . X . . Right Ctrl pressed
. . . . . . X . Left Alt pressed
. . . . . . . X Left Ctrl pressed
──────────────────────────────────────────────────────────────────────────
Figure 11-5. Extended keyboard status bits returned in register AH by
keyboard service 12H.
Comments and Example
If you are in a position to choose between the keyboard services of your
programming language or the ROM BIOS keyboard services, you could safely
and wisely use either one. Although in some cases there are arguments
against using the ROM BIOS services directly, as with the diskette
services, those arguments do not apply as strongly to the keyboard
services. However, as always, you should fully examine the potential of
the DOS services before resorting to the ROM BIOS services; you may find
all you need there, and the DOS services are more long-lived in the
ever-changing environments of personal computers.
Most programming languages depend on the DOS services for their keyboard
operations, a factor that has some distinct advantages. One advantage is
that the DOS services allow the use of the standard DOS editing operations
on string input (input that is not acted on until the Enter key is
pressed). Provided that you do not need input control of your own, it can
save you a great deal of programming effort (and user education) to let
DOS handle the string input, either directly through the DOS services or
indirectly through your language's services. But if you need full control
of keyboard input, you'll probably end up using the ROM BIOS routines in
the long run. Either way, the choice is yours.
Another advantage to using the DOS keyboard services is that the DOS
services can redirect keyboard input so that characters are read from a
file instead of the keyboard. If you rely on the ROM BIOS keyboard
services, you can't redirect keyboard input. (Chapters 16 and 17 contain
information on input/output redirection.)
For our assembly-language example of the use of keyboard services, we'll
get a little fancier than we have in previous examples and show you a
complete buffer flusher. This routine will perform the action outlined
under keyboard service 01H, the report-whether-character-ready service.
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT
PUBLIC _kbclear
_kbclear PROC near
push bp
mov bp,sp
L01: mov ah,1 ; test whether buffer is empty
int 16h
jz L02 ; if so, exit
mov ah,0
int 16h ; otherwise, discard data
jmp L01 ; .. and loop
L02: pop bp
ret
_kbclear ENDP
_TEXT ENDS
The routine works by using interrupt 16H, service 01H to check whether the
keyboard buffer is empty. If no characters exist in the buffer, service
01H sets the zero flag, and executing the instruction JZ L02 causes the
routine to exit by branching to the instruction labeled L02. If the buffer
still contains characters, however, service 01H clears the zero flag, and
the JZ L02 instruction doesn't jump. In this case the routine continues to
the instructions that call service 00H to read a character from the
buffer. Then the process repeats because the instruction JMP L01 transfers
control back to label L01. Sooner or later, of course, the repeated calls
to service 00H empty the buffer, service 01H sets the zero flag, and the
routine terminates.
Among the new things this buffer-flusher routine illustrates is the use of
labels and branching. When we discussed the generalities of
assembly-language interface routines in Chapter 8, we mentioned that an
ASSUME CS statement is necessary in some circumstances, and you see one in
action here.
The ASSUME directive in this example tells the assembler that the labels
in the code segment (that is, labels that would normally be addressed
using the CS register) do indeed lie in the segment whose name is _TEXT.
This may seem obvious, since no other segments appear in this routine.
Nevertheless, it is possible to write assembly-language routines in which
labels in one segment are addressed relative to some other segment; in
such a case, the ASSUME directive would not necessarily reference the
segment within which the labels appear. In later chapters you'll see
examples of this technique, but here the only segment to worry about is
the _TEXT segment, and the ASSUME directive makes this fact explicit.
────────────────────────────────────────────────────────────────────────────
Chapter 12 Miscellaneous Services
RS-232 Serial Communications Services
Service 00H (decimal 0): Initialize Serial Port
Service 01H (decimal 1): Send Out One Character
Service 02H (decimal 2): Receive One Character
Service 03H (decimal 3): Get Serial Port Status
Service 04H (decimal 4): Initialize Extended Serial Port
Service 05H (decimal 5): Control Extended Communications Port
Miscellaneous System Services
Service 00H (decimal 0): Turn On Cassette Motor
Service 01H (decimal 1): Turn Off Cassette Motor
Service 02H (decimal 2): Read Cassette Data Blocks
Service 03H (decimal 3): Write Cassette Data Blocks
Service 21H (decimal 33): Read or Write PS/2 POST Error Log
Service 83H (decimal 131): Start or Cancel Interval Timer
Service 84H (decimal 132): Read Joystick Input
Service 86H (decimal 134): Wait During a Specified Interval
Service 87H (decimal 135): Protected-Mode Data Move
Service 88H (decimal 136): Get Extended Memory Size
Service 89H (decimal 137): Switch to Protected Mode
Service C0H (decimal 192): Get System Configuration Parameters
Service C1H (decimal 193): Get ROM BIOS Extended Data Segment
Service C2H (decimal 194): Pointing-Device Interface
Service C3H (decimal 195): Enable/Disable Watchdog Timer
Service C4H (decimal 196): Programmable Option Select
ROM BIOS Hooks
Service 4FH (decimal 79): Keyboard Intercept
Service 80H (decimal 128): Device Open
Service 81H (decimal 129): Device Close
Service 82H (decimal 130): Program Termination
Service 85H (decimal 133): Sys Req Keystroke
Service 90H (decimal 144): Device Busy
Service 91H (decimal 145): Interrupt Complete
Printer Services
Service 00H (decimal 0): Send 1 Byte to Printer
Service 01H (decimal 1): Initialize Printer
Service 02H (decimal 2): Get Printer Status
Other Services
Interrupt 05H (decimal 5): Print-Screen Service
Interrupt 11H (decimal 17): Equipment-List Service
Interrupt 12H (decimal 18): Memory-Size Service
Interrupt 18H (decimal 24): ROM BASIC Loader Service
Interrupt 19H (decimal 25): Bootstrap Loader Service
Interrupt 1AH (decimal 26): Time-of-Day Services
In this chapter, we'll be covering all ROM BIOS services that are either
not important enough or not complex enough to warrant their own chapters:
RS-232 serial communications services, system services, ROM BIOS hooks,
and printer services. We'll also cover some services that are odd enough
to be considered miscellaneous, even in a chapter of miscellany.
RS-232 Serial Communications Services
This section discusses the RS-232 asynchronous serial communications port
services in the ROM BIOS. Before we begin describing the ROM BIOS services
in detail, you need to know a few important things about the serial
communications port, particularly the terminology. We assume you have a
basic understanding of data communications, but if you discover that you
don't understand the following information, turn to one of the many
specialty books on communications for some background information.
Many words are used to describe the RS-232 data path in and out of the
computer. One of the most common is port. However, this use of the word
port is completely different from our previous use of the word. Throughout
most of this book, we have used port to refer to the addressable paths
used by the 8088 microprocessor to talk to other parts of the computer
within the confines of the computer's circuitry. All references to port
numbers, the BASIC statements INP and OUT, and the assembly-language
operations IN and OUT refer to these addressable ports. The RS-232
asynchronous serial communications port differs because it is a
general-purpose I/O path, which can be used to interconnect many kinds of
information-processing equipment outside the computer. Typically, the
serial ports are used for telecommunications (meaning a telephone
connection through a modem) or to send data to a serial-type printer.
Four serial communications services are common to all IBM models. These
services are invoked with interrupt 14H (decimal 20), selected through
register AH, and numbered 00H through 03H. (See Figure 12-1.) The PS/2
ROM BIOS contains two additional services that provide extended support
for the more capable PS/2 serial port.
The original design of the IBM personal computers allowed up to seven
serial ports to be added, although a computer rarely uses more than one or
two. The PS/2 ROM BIOS explicitly supports only four serial ports. No
matter how many serial ports exist, the serial port number is specified in
the DX register for all ROM BIOS serial communications services. The first
serial port is indicated by 00H in DX.
Service Description
──────────────────────────────────────────────────────────────────────────
00H Initialize Serial Port.
01H Send Out One Character.
02H Receive One Character.
03H Get Serial Port Status.
04H Initialize Extended Serial Port.
05H Control Extended Communication Port.
──────────────────────────────────────────────────────────────────────────
Figure 12-1. The RS-232 serial port services available through interrupt
14H (decimal 20).
Service 00H (decimal 0): Initialize Serial Port
Service 00H (decimal 0) sets the various RS-232 parameters and initializes
the serial port. It sets four parameters: the baud rate, the parity, the
number of stop bits, and the character size (also called the word length).
The parameters are combined into one 8-bit code, which is placed in the AL
register with the format shown in Figure 12-2. The bit settings for each
code are shown in Figure 12-3. When the service is finished, the
communication port status is reported in AX, just as it is for service
03H. (See service 03H for the details.)
Bit
7 6 5 4 3 2 1 0 Use
──────────────────────────────────────────────────────────────────────────
X X X . . . . . Baud-rate code
. . . X X . . . Parity code
. . . . . X . . Stop-bit code
. . . . . . X X Character-size code
──────────────────────────────────────────────────────────────────────────
Figure 12-2. The bit order of the serial port parameters passed in
register AL to service 00H.
──────────────────────────────────────────────────────────────────────────
NOTE:
Although it is painfully slow, 300 baud used to be the most commonly
used baud rate for personal computers using modems. A rate of 1200 baud
is now the most common, particularly for serious applications that
require faster transmission, but widespread use of at least 2400 baud
communications is inevitable.
──────────────────────────────────────────────────────────────────────────
BAUD RATE
Bit
7 6 5 Value Bits per Second
──────────────────────────────────────────────────────────────────────────
0 0 0 0 110
0 0 1 1 150
0 1 0 2 300
0 1 1 3 600
1 0 0 4 1200
1 0 1 5 2400
1 1 0 6 4800
1 1 1 7 9600
──────────────────────────────────────────────────────────────────────────
STOP BITS
Bit Value Meaning
2
──────────────────────────────────────────────────────────────────────────
0 0 One
1 1 Two
──────────────────────────────────────────────────────────────────────────
PARITY
Bit Value Meaning
4 3
──────────────────────────────────────────────────────────────────────────
0 0 0 None
0 1 1 Odd parity
1 0 2 None
1 1 3 Even parity
──────────────────────────────────────────────────────────────────────────
CHARACTER SIZE
Bit Value Meaning
1 0
──────────────────────────────────────────────────────────────────────────
0 0 0 Not used
0 1 1 Not used
1 0 2 7-bit☼
1 1 3 8-bit
──────────────────────────────────────────────────────────────────────────
Figure 12-3. The bit settings for the four serial port parameters for
service 00H.
Service 01H (decimal 1): Send Out One Character
Service 01H (decimal 1) transmits one character out the serial port
specified in DX. When you call service 01H, you place the character to be
transmitted in AL. When service 01H returns, it reports the status of the
communications port. If AH = 00H, then the service was successful.
Otherwise bit 7 of AH indicates that an error occurred, and the other bits
of AH report the type of error. These bits are outlined in the discussion
of service 03H, the status service.
The error report supplied through this service has one anomaly: Because
bit 7 reports that an error has occurred, it is not available to indicate
a time-out error (as the details in service 03H would suggest).
Consequently, when this service or service 02H reports an error, the
simplest and most reliable way to check the nature of the error is to use
the complete status report given by service 03H, rather than the
less-complete status code returned with the error through services 01H and
02H.
Service 02H (decimal 2): Receive One Character
Service 02H (decimal 2) receives one character from the communications
line specified in DX and returns it in the AL register. The service waits
for a character or any signal that indicates the completion of the
service, such as a time-out. AH reports the success or failure of the
service in bit 7, as explained in the discussion of service 01H. Again,
consider the advice under service 01H for error handling and see service
03H for the error codes.
Service 03H (decimal 3): Get Serial Port Status
Service 03H (decimal 3) returns the complete serial port status in the AX
register. The 16 status bits in AX are divided into two groups: AH reports
the line status (which is also reported when errors occur with services
01H and 02H), and AL reports the modem status, when applicable. Figure
12-4 contains the bit codings of the status bits. Some codes report
errors, and others simply report a condition.
──────────────────────────────────────────────────────────────────────────
NOTE:
One special bit of information about the time-out error (AH, bit 7) is
worth noting: The earliest version of the ROM BIOS for the original PC
had a programming error that caused a serial-port time-out to be
reported as a transfer-shift-register-empty/break-detect-error
combination (bits 01010000 rather than 10000000). This has been
corrected on all subsequent versions of the ROM BIOS, but it has caused
many communications programs to treat these error codes skeptically. You
may want to keep this in mind. See page 63 for details on identifying
the ROM BIOS version dates and machine ID codes.
──────────────────────────────────────────────────────────────────────────
Bit
7 6 5 4 3 2 1 0 Meaning (when set to 1)
──────────────────────────────────────────────────────────────────────────
AH Register (line status)
1 . . . . . . . Time-out error
. 1 . . . . . . Transfer shift register empty
. . 1 . . . . . Transfer holding register empty
. . . 1 . . . . Break-detect error
. . . . 1 . . . Framing error
. . . . . 1 . . Parity error
. . . . . . 1 . Overrun error
. . . . . . . 1 Data ready
AL Register (modem status)
1 . . . . . . . Received line signal detect
. 1 . . . . . . Ring indicator
. . 1 . . . . . Data-set-ready
. . . 1 . . . . Clear-to-send
. . . . 1 . . . Delta receive line signal detect
. . . . . 1 . . Trailing-edge ring detector
. . . . . . 1 . Delta data-set-ready
. . . . . . . 1 Delta clear-to-send
──────────────────────────────────────────────────────────────────────────
Figure 12-4. The bit coding for the status bytes returned in register AX
by service 03H.
Service 04H (decimal 4): Initialize Extended Serial Port
Service 04H (decimal 4) is available only in the PS/2 ROM BIOS. It expands
the capabilities of service 00H to provide support for the PS/2's improved
serial ports. If you compare service 04H with service 00H, you'll find
that the four serial port initialization parameters passed in AL in
service 00H are separated into four registers in service 04H (Figure
12-5). Also, service 04H returns both modem and line status in register
AX, exactly as service 03H does. Because service 04H has these expanded
capabilities, you should generally use it instead of service 00H for PS/2
serial port initialization.
BREAK (register AL)
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H No break
01H Break
──────────────────────────────────────────────────────────────────────────
PARITY (register BH)
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H None
01H Odd
02H Even
03H Stick parity odd
04H Stick parity even
──────────────────────────────────────────────────────────────────────────
BAUD RATE (register CL)
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H 110 baud
01H 150 baud
02H 300 baud
03H 600 baud
04H 1200 baud
05H 2400 baud
06H 4800 baud
07H 9600 baud
08H 19,200 baud
──────────────────────────────────────────────────────────────────────────
STOP BITS (register BL)
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H One
01H Two (for word length = 6, 7, or 8)
11/2 (for word length = 5)
──────────────────────────────────────────────────────────────────────────
WORD LENGTH (register CH)
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H 5 bits
01H 6 bits
02H 7 bits
03H 8 bits
──────────────────────────────────────────────────────────────────────────
Figure 12-5. Register values for serial port initialization with
interrupt 14H, service 04H. (Register DX contains a serial port number
between 0 and 3.)
Service 05H (decimal 5): Control Extended Communications Port
This service, provided only by the PS/2 ROM BIOS, lets you read from or
write to the modem control register of a specified serial communications
port. When you call service 05H with AL = 00H and a serial port number in
DX, service 05H returns with register BL containing the value in the modem
control register of the specified serial port. When you call service 05H
with AL = 01H, the ROM BIOS copies the value you pass in register BL into
the modem control register for the specified port. In both cases, service
05H returns the modem status and line status in registers AL and AH, as
does service 03H.
Miscellaneous System Services
The miscellaneous system services provided through interrupt 15H are
indeed miscellaneous. (See Figure 12-6.) Many are intended primarily for
writers of operating-system software. Most application programmers will
find little use for these services in their programs, because the
functions provided are better carried out by calls to the operating system
than they are through the ROM BIOS. Some of these services, such as the
pointing-device interface (subservice C2H), provide functionality not
otherwise available in the ROM BIOS or in DOS; others are obsolete and
virtually unusable.
╓┌─┌──────────────────┌──────────────────────────────────────────────────────╖
Service Description
──────────────────────────────────────────────────────────────────────────
00H Turn On Cassette Motor.
01H Turn Off Cassette Motor.
02H Read Cassette Data Blocks.
03H Write Cassette Data Blocks.
21H Read or Write PS/2 Power-On Self-Test Error Log.
4FH Keyboard Intercept.
80H Device Open.
81H Device Close.
Service Description
──────────────────────────────────────────────────────────────────────────
81H Device Close.
82H Program Termination.
83H Start or Cancel Interval Timer.
84H Read Joystick Input.
85H Sys Req Keystroke.
86H Wait During a Specified Interval.
87H Protected-Mode Data Move.
88H Get Extended Memory Size.
89H Switch to Protected Mode.
90H Device Busy.
91H Interrupt Complete.
C0H Get System Configuration Parameters.
C1H Get Extended BIOS Data Segment.
C2H Pointing-Device Interface.
C3H Enable/Disable Watchdog Timer.
C4H Programmable Option Select.
──────────────────────────────────────────────────────────────────────────
Figure 12-6. Miscellaneous system services available through interrupt
15H.
The four cassette tape services are used when working with the cassette
tape connection, which is a part of only two PC models: the original PC
and the now-defunct PCjr. The cassette port was created with the original
PC on the assumption that a demand might exist for it. None did, and it
has remained almost totally unused. Nevertheless, IBM does support the use
of the cassette port, both through the ROM BIOS services discussed here
and through BASIC, which lets you read and write either data or BASIC
programs on standard audio cassette tape.
The cassette port never proved worthwhile, however. Nobody sells PC
programs on tape, and nobody has found much use for the cassette port,
given the convenience of diskettes and hard disks.
Service 00H (decimal 0): Turn On Cassette Motor
Service 00H (decimal 0) turns on the cassette motor, which is not an
automatic operation of the ROM BIOS services as it is with the diskette
services. Any program that is using this service can expect a slight delay
while the motor starts.
Service 01H (decimal 1): Turn Off Cassette Motor
Service 01H (decimal 1) turns off the cassette motor. This is not an
automatic operation of the ROM BIOS services as it is with the diskette
services.
Service 02H (decimal 2): Read Cassette Data Blocks
Service 02H (decimal 2) reads one or more cassette data blocks. Cassette
data is transferred in standard-size 256-byte blocks, just as diskette
data normally uses a standard 512-byte sector. The number of bytes to be
read is placed in the CX register. Although data is placed on tape in
256-byte blocks, any number of bytes can be read or written. Consequently,
the number of bytes placed in the CX register need not be a multiple of
256. The register pair ES:BX is used as a pointer to the memory area where
the data is to be placed.
After the service is completed, DX contains the actual number of bytes
read, ES:BX points to the byte immediately after the last byte
transferred, and the carry flag (CF) is set or cleared to report the
success or failure of the operation. On failure, AH returns an error code.
(See Figure 12-7.)
Code Meaning
──────────────────────────────────────────────────────────────────────────
01H Cyclical redundancy check (CRC) error
02H Lost data transitions: bit signals scrambled
04H No data found on tape
──────────────────────────────────────────────────────────────────────────
Figure 12-7. The error code returned by service 02H in register AH if the
CF indicates a failure to read the data blocks.
Service 03H (decimal 3): Write Cassette Data Blocks
Service 03H (decimal 3) writes one or more cassette data blocks of 256
bytes each. (See service 02H.) As with service 02H, the CX register gives
the count of bytes requested, and ES:BX points to the data area in memory.
If the amount of data being written is not a multiple of 256 bytes, the
last data block is padded out to full size.
After the service is completed, CX should contain 00H, and ES:BX should
point just past the last memory byte that was written.
Curiously, no error signals are provided for this service, essentially
because a cassette tape recorder can't inform the computer of any
difficulties. This forces the ROM BIOS to write data in blind faith that
all is well. Needless to say, it would be a good idea to read back any
data written, just to check it.
Service 21H (decimal 33): Read or Write PS/2 POST Error Log
Service 21H (decimal 33) is used internally by the ROM BIOS power-on
self-test (POST) routines in PS/2s with the Micro Channel bus to keep
track of hardware initialization errors. You will rarely, if ever, find
use for this service in your own applications.
Service 83H (decimal 131): Start or Cancel Interval Timer
This service lets a program set a specified time interval and lets the
program check a flag to show when the interval expires. The program should
call this service with AL = 00H, with the address of a flag byte in
registers ES and BX, and with the time interval in microseconds in
registers CX and DX. The high-order 16 bits of the interval should be in
CX; the low-order 16 bits in DX.
Initially, the flag byte should be 00H. When the time interval elapses,
the ROM BIOS sets this byte to 80H. The program can thus inspect the flag
byte at its own convenience to determine when the time interval has
elapsed:
Clear the flag byte
Call service 83H to start the interval timer
WHILE (flag byte = 00H)
BEGIN
(do something useful)
END
The ROM BIOS interval timer uses the system time-of-day clock, which ticks
about 1024 times per second, so the timer's resolution is approximately
976 microseconds.
Service 84H (decimal 132): Read Joystick Input
Service 84H (decimal 132) provides a consistent interface for programs
that use a joystick or a related input device connected to IBM's Game
Control Adapter. When you call this service with DX = 00H, the ROM BIOS
reports the adapter's four digital switch input values in bits 4 through 7
of register AL. Calling service 84H with DX = 01H instructs the BIOS to
return the adapter's four resistive input values in registers AX, BX, CX,
and DX.
Service 84H is not supported on the IBM PC or in the original PC/XT BIOS
(dated 11/08/82). Be sure to check the computer's model identification and
ROM BIOS revision date before you rely on this BIOS service in a program.
Service 86H (decimal 134): Wait During a Specified Interval
Like service 83H, service 86H (decimal 134) lets a program set a specified
time interval to wait. Unlike service 83H, however, service 86H suspends
operation of the program that calls it until the specified time interval
has elapsed. Control returns to the program only when the wait has
completed or if the hardware timer is unavailable.
Service 87H (decimal 135): Protected-Mode Data Move
A program running in real mode can use service 87H to transfer data to or
from extended (protected-mode) memory on a PC/AT or PS/2 Model 50, 60, or
80. This service is designed to be used by a protected-mode operating
system. The IBM-supplied VDISK utility also uses this function to copy
data to and from a virtual disk in extended memory. See the IBM BIOS
Interface Technical Reference Manual for details.
Service 88H (decimal 136): Get Extended Memory Size
Service 88H (decimal 136) returns the amount of extended (protected-mode)
memory installed in a PC/AT or PS/2 Model 50, 60, or 80. The value, in
kilobytes, is returned in register AX.
The amount of extended memory is established by the ROM BIOS POST
routines. It includes extended memory installed beyond the first megabyte;
that is, memory starting at 10000:0000H. Lotus/Intel/Microsoft "expanded"
memory is not included in the value returned by service 88H.
Service 89H (decimal 137): Switch to Protected Mode
Service 89H (decimal 137) is provided by the ROM BIOS as an aid to
configuring an 80286-based computer (PC/AT, PS/2 Model 50 or 60) or an
80386-based computer (PS/2 Model 80) for protected-mode operation. This
ROM BIOS service is intended for operating systems that run in protected
mode. To use this service, you must be thoroughly acquainted with
protected-mode programming techniques. See the IBM BIOS Interface
Technical Reference Manual for details.
Service C0H (decimal 192): Get System Configuration Parameters
Service C0H (decimal 192) returns the address of a table of descriptive
information pertaining to the hardware and BIOS configuration of a PC/AT
(in ROM BIOS versions dated 6/10/85 and later) or PS/2. Figure 12-8 shows
the structure of the table. You can find the meaning of the model and
submodel bytes in Chapter 3, page 64.
Offset Size Contents
──────────────────────────────────────────────────────────────────────────
0 2 bytes Size of configuration information table
2 1 byte Model byte
3 1 byte Submodel byte
4 1 byte ROM BIOS revision level
5 1 byte Feature information byte:
Bit 7: Fixed-disk BIOS uses DMA Channel 3
Bit 6: Cascaded interrupt level 2 (IRQ2)
Bit 5: Real-time clock present
Bit 4: BIOS keyboard intercept implemented
Bit 3: Wait for external event supported
Bit 2: Extended BIOS data area allocated
Bit 1: Micro Channel bus present
Bit 0: (Reserved)
──────────────────────────────────────────────────────────────────────────
Figure 12-8. System configuration information returned by service C0H.
Service C1H (decimal 193): Get ROM BIOS Extended Data Segment
Service C1H (decimal 193) returns the segment address of the ROM BIOS
extended data area. The ROM BIOS clears the carry flag and returns the
segment value in register ES if an extended BIOS data segment is in use.
Otherwise, service C1H returns with the carry flag set.
The ROM BIOS uses the extended data area for transient storage of data.
For example, when you pass the address of a pointing-device interface
subroutine to the BIOS, the BIOS stores this address in its extended data
area.
Service C2H (decimal 194): Pointing-Device Interface
Service C2H (decimal 194) is the ROM BIOS interface to the built-in PS/2
pointing-device controller. This interface makes it easy to use an IBM
PS/2 mouse.
To use the interface, you must write a short subroutine to which the ROM
BIOS can pass packets of status information about the pointing device.
Your subroutine should examine the data in each packet and respond
appropriately, for example by moving a cursor on the screen. The
subroutine must exit with a far return without changing the contents of
the stack.
To use the ROM BIOS pointing-device interface, carry out the following
sequence of steps:
1. Pass the address of your subroutine to the BIOS (subservice 07H).
2. Initialize the interface (subservice 05H).
3. Enable the pointing device (subservice 00H).
At this point, the BIOS begins sending packets of status information to
your subroutine. The BIOS places each packet on the stack and calls your
subroutine with a far CALL so that the stack is formatted when the
subroutine gets control as in Figure 12-9. The low-order byte of the X
and Y data words contains the number of units the pointing device has
moved since the previous packet of data was sent. (The Z data byte is
always 0.) The status byte contains sign, overflow, and button
information. (See Figure 12-10.)
┌────────────────┐
│ Status │
├────────────────┤◄──── SP + 10
│ X data │
├────────────────┤◄──── SP + 8
│ Y data │
├────────────────┤◄──── SP + 6
│ Z data │
├────────────────┤◄──── SP + 4
│ │
│ Return address │
│ │
└────────────────┘◄──── SP
Figure 12-9. Pointing-device data packet.
Bit Meaning
──────────────────────────────────────────────────────────────────────────
0 Set if left button pressed
1 Set if right button pressed
2─3 (Reserved)
4 Set if X data is negative
5 Set if Y data is negative
6 Set if X data overflows
7 Set if Y data overflows
──────────────────────────────────────────────────────────────────────────
Figure 12-10. Status byte in pointing-device data packet.
When you use service C2H, the value you pass in register AL selects one of
eight available subservices. (See Figure 12-11.) The actual register
contents for each subservice are in Chapter 13, page 284.
Subservice Description
──────────────────────────────────────────────────────────────────────────
00H Enable/disable pointing device.
01H Reset pointing device.
02H Set sample rate.
03H Set resolution.
04H Get pointing-device type.
05H Initialize pointing device.
06H Extended commands.
07H Pass device-driver address to ROM BIOS.
──────────────────────────────────────────────────────────────────────────
Figure 12-11. Subservices available in the BIOS pointing-device interface
(interrupt 15H, service C2H).
Service C3H (decimal 195): Enable/Disable Watchdog Timer
Service C3H (decimal 195) provides a consistent interface to the watchdog
timer in the PS/2 models 50, 60, and 80. It lets an operating system
enable the watchdog timer with a specified timeout interval or disable the
timer. Because the watchdog timer is intended specifically for use in
operating-system software, this ROM BIOS service will rarely be useful in
your applications.
Service C4H (decimal 196): Programmable Option Select
Like many other interrupt 15H services, service C4H (decimal 196) is
intended for use by operating system software. This service provides a
consistent interface to the Programmable Option Select feature of the
Micro Channel architecture in the PS/2 models 50, 60, and 80.
ROM BIOS Hooks
The ROM BIOS in the PC/AT and in the PS/2s provides a number of hooks.
These hooks are implemented as interrupt 15H "services," but to use them
you must write an interrupt 15H handler that processes only these services
and passes all other interrupt 15H service requests to the ROM BIOS. (See
Figure 12-12.) This arrangement lets different components of the BIOS
communicate with each other and with operating-system or user-written
programs in a consistent manner.
The ROM BIOS hooks are intended primarily for use in operating systems and
in programs written to augment operating-system or BIOS functions.
However, neither DOS nor OS/2 uses these BIOS hooks, and few program
applications have reason to. Still, you might find it worthwhile to
examine what the ROM BIOS hooks do, if only to get an idea of how the ROM
BIOS is put together and how an operating system can interact with it.
. ─┐
. │
. │
mov ah,ServiceNumber │
int 15h ├─ Executed within ROM BIOS
. │
. │
. │
─┘ ─┐
Userhandler PROC far ; interrupt 15H vector │
; points here │
│
. │
. │
. │
cmp ah,ServiceNumber │
je Service ├─ User-written
jmp (to previous INT 15H handler) │ interupt 15H
│ handler
Service │
. │
. (do something useful) │
. │
iret │
Userhandler ENDP ─┘
Figure 12-12. How the ROM BIOS hooks can be used.
Service 4FH (decimal 79): Keyboard Intercept
In the PC/AT ROM BIOS (dated 06/10/85 and later) and in the PS/2 ROM BIOS,
the keyboard interrupt handler (that is, the handler for hardware
interrupt 09H) executes interrupt 15H with AH = 4FH and with AL equal to
the keyboard scan code. This action has little effect: The ROM BIOS
interrupt 15H, service 4FH (decimal 79) handler returns with the carry
flag set, and the interrupt 09H handler continues processing the
keystroke.
If you write an interrupt handler for interrupt 15H, however, you can hook
service 4FH and process keystrokes yourself. Install your handler by
storing its segmented address in the interrupt 15H vector. (Be sure to
save the previous contents of the interrupt 15H vector.) Your interrupt
15H handler would do the following:
IF (AH<>4FH)
jump to default interrupt 15H handler
ELSE
process keyboard scan code in AL
set or reset carry flag
exit from interrupt handler
If your handler processes the scan code in AL, it must either set or reset
the carry flag before it returns control to the ROM BIOS interrupt 09H
handler. Setting the carry flag indicates that the BIOS interrupt 09H
handler should continue processing the scan code in AL: Clearing the carry
flag causes the BIOS handler to exit without processing the scan code.
The problem with using the ROM BIOS keyboard intercept is that other
programs, including DOS itself, can and do process keystrokes before the
ROM BIOS interrupt 09H handler ever has a chance to issue interrupt 15H.
(These programs do this by pointing the interrupt 09H vector to their own
handlers instead of to the default ROM BIOS handler.) Because your program
can't determine if this is happening, you cannot rely on the ROM BIOS
keyboard intercept to be called for every keystroke.
Service 80H (decimal 128): Device Open
This hook lets programs determine when a particular hardware device is
available for input or output. An installable device driver can issue
interrupt 15H with AH = 80H to inform an operating system that the device
was opened. The operating system's interrupt 15H handler can inspect BX
for an identifying value for the device and CX for an ID value of the
program that opened the device.
Service 81H (decimal 129): Device Close
Like service 80H, this service is provided for programs that establish
input/output connections to hardware devices to communicate with an
operating system. Service 81H (decimal 129) is called by such a program
with a device ID value in register BX and a program ID value in CX. An
operating system's interrupt 15H handler can inspect these values to
determine that a particular device was closed for input/output by a
particular program.
Service 82H (decimal 130): Program Termination
Service 82H (decimal 130) is provided by the ROM BIOS so that a program
can signal its own termination to an operating system. When a program
executes interrupt 15H with AH = 82H and an ID value in BX, the operating
system can handle the interrupt and thus be informed that the program
terminated.
Service 85H (decimal 133): Sys Req Keystroke
When you press the Sys Req key on an 84-key keyboard or Alt-Sys Req on a
101/102-key keyboard, the ROM BIOS keyboard interrupt handler executes
interrupt 15H with AH = 85H. You can detect when this key is pressed by
hooking interrupt 15H and inspecting the value in AH.
When the Sys Req key is first pressed, the ROM BIOS issues interrupt 15H
with AH = 85H and AL = 00H. When the key is released, the BIOS executes
interrupt 15H with AH = 85H and AL = 01H. Thus the structure of an
interrupt 15H handler that detects Sys Req keystrokes would be as follows:
IF (AH<>85H)
jump to previous interrupt 15H handler
ELSE IF (AL = 00H)
process Sys Req keystroke
ELSE
process Sys Req key release
exit from interrupt handler
Service 90H (decimal 144): Device Busy
This service lets a device driver alert an operating system to the
beginning of an input or output operation. An operating system's interrupt
15H handler processes this information (for example) by preventing
subsequent input/output to the device until the device signals, with
service 91H, that it is no longer busy.
The ROM BIOS device drivers for disks, the keyboard, and the printer all
issue appropriate service 90H (decimal 144) interrupts. Each device is
identified by a value in register AL. (See Figure 12-13.) These ID values
are selected according to the following guidelines:
■ 00H─7FH: Non-reentrant devices that can process only one I/O request at
a time sequentially.
■ 80H─BFH: Reentrant devices that can handle multiple I/O requests at
once.
■ C0H─FFH: Devices that expect the operating system to wait for a
predetermined period of time before returning control to the device.
The operating system's interrupt 15H handler must set the carry flag to
indicate that the wait has been carried out.
Value Meaning
──────────────────────────────────────────────────────────────────────────
00H Fixed disk
01H Diskette
02H Keyboard
03H Pointing device
80H Network
FCH PS/2 fixed-disk reset
FDH Diskette-drive motor start
FEH Printer
──────────────────────────────────────────────────────────────────────────
Figure 12-13. Device identification values for interrupt 15H, services
90H and 91H.
Service 91H (decimal 145): Interrupt Complete
Devices that use service 90H to notify an operating system that they are
busy can subsequently use service 91H (decimal 145) to signal that an
input/output operation has been completed. The identification value passed
in AL should be the same as the value passed in service 90H.
Printer Services
The ROM BIOS printer services support printer output through the parallel
printer adapter. The three ROM BIOS printer services are invoked with
interrupt 17H (decimal 23), requested through the AH register, and
numbered 00H through 02H. (See Figure 12-14.) The general PC-family
design allows more than one printer to be installed, so a printer number
must be specified in register DX for all these services.
Service Description
──────────────────────────────────────────────────────────────────────────
00H Send One Byte to Printer.
01H Initialize Printer.
02H Get Printer Status.
──────────────────────────────────────────────────────────────────────────
Figure 12-14. The three ROM BIOS printer services invoked through
interrupt 17H (decimal 23).
Service 00H (decimal 0): Send 1 Byte to Printer
Service 00H (decimal 0) sends the byte you specify to the printer. When
the service is completed, AH is then set to report the printer status (see
service 02H), which can be used to determine the success or failure of
the operation. See the special notes on printer time-out under service
02H.
Service 01H (decimal 1): Initialize Printer
Service 01H (decimal 1) initializes the printer. To do this, the service
simply sends two control codes (08H and 0CH) to the printer control port.
As with the other two services, the printer status is reported in AH.
Service 02H (decimal 2): Get Printer Status
Service 02H (decimal 2) reports the printer status in the AH register. The
individual bit codes are shown in Figure 12-15.
The printer time-out has caused some difficulty in the IBM personal
computers. Any I/O driver needs to set a time limit for a response from
the device being controlled. Ideally, this time limit should be short
enough to ensure that an unresponsive device can be reported in a timely
manner. Unfortunately, one normal printer operation can take a
surprisingly long time: a page eject ("skip to the top of the next page").
The time allowed varies from version to version of the ROM BIOS. Treat a
time-out signal with care.
Bit
7 6 5 4 3 2 1 0 Meaning (when set to 1)
──────────────────────────────────────────────────────────────────────────
1 . . . . . . . Printer not busy (0 = busy)
. 1 . . . . . . Acknowledgment from printer
. . 1 . . . . . Out-of-paper signal
. . . 1 . . . . Printer selected
. . . . 1 . . . I/O error
. . . . . 1 . . Not used
. . . . . . 1 . Not used
. . . . . . . 1 Time-out
──────────────────────────────────────────────────────────────────────────
Figure 12-15. The printer status bits reported in the AH register by
services 00H, 01H, and 02H.
Other Services
We now come to the grab bag of all other ROM BIOS services. (See Figure
12-16.) Some of these services are intended for use in program
applications; others are more likely to be used in operating-system
software. The following sections describe these six service interrupts.
Interrupt Description
Hex Dec
──────────────────────────────────────────────────────────────────────────
05H 5 Print-Screen Service
11H 17 Equipment-List Service
12H 18 Memory-Size Service
18H 24 ROM BASIC Loader Service
19H 25 Bootstrap Loader Service
1AH 26 Time-of-Day Services
──────────────────────────────────────────────────────────────────────────
Figure 12-16. Six miscellaneous ROM BIOS services supported by IBM, and
their associated interrupts.
Interrupt 05H (decimal 5): Print-Screen Service
Interrupt 05H (decimal 5) activates the print-screen service: The keyboard
support routines generate interrupt 05H in response to the Shift-PrtSc
combination; any other programs that want to perform a print-screen
operation can safely and conveniently do so by generating interrupt 05H.
The print-screen service will maintain the current cursor position on the
screen and successfully print any printable characters from the screen in
either text or graphics mode. It uses both the standard video services
(those that waltz the cursor around the screen and read characters from
the screen buffer) and the standard printer services.
This service directs all its output to printer number 0, the default
printer. There are no input or output registers for this service. However,
a status code is available at low-memory location 0050:0000H. (See page
61.) If the byte at that location has a value of FFH (decimal 255), then
a previous print-screen operation was not completed successfully. A value
of 00H indicates that no error occurred, and that the print-screen
operation is ready to go. A value of 01H indicates that a print-screen
operation is currently in progress; any request for a second one will be
ignored.
The ROM BIOS print-screen routine cannot print images drawn on the screen
in graphics modes. If you want to produce a printed screen snapshot in
CGA-compatible graphics modes, use the DOS utility program GRAPHICS. This
program installs a memory-resident, graphics-mode print-screen routine
that hooks interrupt 05H. Once you execute GRAPHICS, pressing Shift-PrtSc
or executing interrupt 05H while in a graphics mode will cause the
graphics-mode print-screen routine to run.
Interrupt 11H (decimal 17): Equipment-List Service
Interrupt 11H (decimal 17) reports what equipment is installed in the
computer. This report contains the same information stored at low-memory
location 0040:0010H. (See Chapter 3, page 55.) The report is coded as
shown in Figure 12-17, in the bits of a 16-bit word, which is placed in
register AX. See interrupt 12H for a related service.
The equipment information is gathered on an as-accurate-as-possible basis
and may not be exactly correct. Different methods are used for acquiring
the information in the various models.
The equipment list is determined only once at power-up time and is then
left in memory. This means that you can change the equipment list under
software control. For example, you could take some equipment off line so
that it is not used. However, modifying the equipment list is risky
business──don't bet on its success. See interrupt 19H for comments on how
to modify the equipment list and get reliable results.
╓┌─┌───────────────────────────────────────────────────────┌─────────────────►
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Meaning
───────────────────────────────────────────────────────────────────────────
X X . . . . . . . . . . . . . . Number of printers
. . X . . . . . . . . . . . . . (Reserved)
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Meaning
───────────────────────────────────────────────────────────────────────────
. . X . . . . . . . . . . . . . (Reserved)
. . . X . . . . . . . . . . . . Game adapter: 1 = i
. . . . X X X . . . . . . . . . Number of RSS-232 s
. . . . . . . X . . . . . . . . (Not used)
. . . . . . . . X X . . . . . . Number of diskette
. . . . . . . . . . X X . . . . Initial video mode:
10 = 80-column colo
01 = 40-column colo
. . . . . . . . . . . . X X . . PC with 64 KB mothe
system board RAM (1
10 = 48 KB; 01 = 32
PC/AT: (Not used)
PS/2s: Bit 3 = (unu
pointing device ins
. . . . . . . . . . . . . . X . 1 if math co-proces
. . . . . . . . . . . . . . . X 1 if any diskette d
see bits 7 and 6)
───────────────────────────────────────────────────────────────────────────
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Meaning
───────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────────
Figure 12-17. The bit coding for the equipment list reported in register
AX and invoked by interrupt 11H (decimal 17).
The format of the equipment list was defined for the original IBM PC. As a
result, some parts of the list vary, depending on the PC model. For
example, bits 2 and 3 originally indicated the amount of RAM installed on
the motherboard. (Yes, in those days you could indeed have purchased a PC
with as little as 16 KB of RAM.) In PS/2s, these bits have a different
significance. (See Figure 12-17.)
Interrupt 12H (decimal 18): Memory-Size Service
Interrupt 12H (decimal 18) invokes the service that reports the available
memory size in kilobytes──the same information stored at low-memory
location 0040:0013H. (See page 55.) The value is reported in AX. The
memory-size value reflects only the amount of base memory available. In a
PC/AT or PS/2 with extended (protected-mode) memory, you must use
interrupt 15H, service 88H (Get Extended Memory Size), to determine the
amount of extended memory installed.
In the standard models of the PC, this value is taken from the setting of
the physical switches inside the system unit. These switches are supposed
to reflect the actual memory installed, although under some circumstances
they are set to less memory than is actually present. In the PC/AT and
PS/2s, the ROM BIOS POST determines the amount of memory in the system by
exploring available RAM to see what is installed. If the BIOS is using an
extended data area, this data area is allocated at the highest memory
address available, so the value returned by this service excludes the
amount of RAM reserved for the extended data area.
Interrupt 18H (decimal 24): ROM BASIC Loader Service
Interrupt 18H (decimal 24) is normally used to activate ROM BASIC. Any
program can activate BASIC (or whatever has replaced it) by generating
interrupt 18H. This can be done to intentionally bring up ROM BASIC or
also to abruptly shut down, or dead-end a program. However, see the next
interrupt, number 19H, for a better way to dead-end a program.
Interrupt 19H (decimal 25): Bootstrap Loader Service
Interrupt 19H (decimal 25) activates the standard bootstrap routine for
the computer (which produces a similar result to powering on and nearly
the same net result as the Ctrl-Alt-Del key combination). However, this
bootstrap interrupt bypasses the lengthy memory check of the power-on
routines as well as the reset operations of Ctrl-Alt-Del.
The bootstrap loader works by reading the first sector of the first track
(the boot sector) from the diskette in drive A into memory at 0000:7C00H.
If the ROM BIOS cannot read from the diskette, it reads the boot sector
from the hard disk in drive C instead. If both attempts fail, the BIOS
executes interrupt 18H to bring up ROM BASIC. If the BIOS reads a sector
from the disk but the sector doesn't contain an operating-system boot
record, the BIOS issues an error message and waits for you to reboot or
replace the offending diskette.
We know of two uses for this interrupt service. One is to immediately shut
down, or dead-end, the operation of the computer. This can be done by a
program when it encounters an "illegal" situation, for example, by a
copy-protected program that detects an apparent violation of copy
protection.
The other use for this service is to reboot the computer without going
through the reset and restart operations, which would, for example,
recalculate the memory size and equipment list reported by interrupts 11H
and 12H. This interrupt is particularly useful for any program that
modifies either of these two items. The reasoning is simple: If you want
to change the equipment list or the memory size (for example, to set aside
some memory for a RAM disk), you cannot reliably count on all programs──
including DOS──to check the actual memory or equipment specifications each
time they are used. But a program could set aside some memory, change the
memory specification, and then use this interrupt to reboot the system.
When that is done and DOS is activated, DOS would take its own record of
the available memory from the value set by your program. Neither DOS nor
any civilized DOS program would be aware of, or interfere with, the memory
area that was set aside.
To give you a brief example, here's a fragment of assembler code that will
change the ROM BIOS's record of the memory size and then use interrupt 19H
to reboot the computer:
mov ax,40H ; get BIOS data segment of hex 40...
mov es,ax ; ...into ES segment register
mov word ptr es:[13h],256 ; set memory to 256 KB
int 19h ; reboot system
Interrupt 1AH (decimal 26): Time-of-Day Services
Interrupt 1AH (decimal 26) provides the time-of-day services. Unlike other
interrupts covered in this section but like all other ROM BIOS services,
several services can be activated by this interrupt. When you execute
interrupt 1AH, you specify the service number, as usual, in register AH.
(See Figure 12-18.)
Service Description
──────────────────────────────────────────────────────────────────────────
00H Get Current Clock Count.
01H Set Current Clock Count.
02H Get Real-Time Clock Time.
03H Set Real-Time Clock Time.
04H Get Real-Time Clock Date.
05H Set Real-Time Clock Date.
06H Set Real-Time Clock Alarm.
07H Reset Real-Time Clock Alarm.
09H Get Real-Time Clock Alarm Time and Status.
──────────────────────────────────────────────────────────────────────────
Figure 12-18. The ROM-BIOS time-of-day services invoked by interrupt 1AH.
The ROM BIOS maintains a time-of-day clock based on a count of
system-clock ticks since midnight. The system clock "ticks" by generating
interrupt 8 at specific intervals. On each clock tick, the ROM BIOS
interrupt 08H service routine increments the clock count by 1. When the
clock count passes 24 hours' worth of ticks, the count is reset to 0 and a
record is made of the fact that midnight has been passed. This record is
not in the form of a count, so you can't detect if two midnights have
passed.
The clock ticks at a rate that is almost exactly 1,193,180 ÷ 64 KB, or
roughly 18.2 times a second. The count is kept as a 4-byte integer at
low-memory location 0040:006CH. The midnight count value, used to compare
against the rising clock count, is 1800B0H, or 1,573,040; when the clock
hits the midnight count value, the byte at location 0040:0070H is set to
01H and the count is reset. When DOS needs to know the time, it reads the
clock count through the time-of-day service and calculates the time from
this raw count. If it sees that midnight has passed, it also increments
the date.
You can use the following BASIC formulas to calculate the current time of
day from the clock count:
HOURS = INT(CLOCK / 65543)
CLOCK = CLOCK - (HOURS * 65543)
MINUTES = INT(CLOCK / 1092)
CLOCK = CLOCK - (MINUTES * 1092)
SECONDS = CLOCK / 18.2
In reverse, we use the following formula to calculate a nearly correct
clock count from the time:
COUNT = (HOURS * 65543) + (MINUTES * 1092) + (SECONDS * 18.2)
The ROM BIOS services in the PC/AT and PS/2s include time-of-day and date
services that perform some of these tasks automatically.
Service 00H (decimal 0): Get Current Clock Count
Service 00H (decimal 0) returns the current clock count in two registers:
the high-order portion in CX and the low-order portion in DX. AL = 00H if
midnight has not passed since the last clock value was read or set; and AL
= 01H if midnight has passed. The midnight signal is always reset when the
clock is read. Any program using this service must use the midnight signal
to keep track of date changes. DOS programs normally should not use this
service directly. If they do, they must calculate and set a new date.
──────────────────────────────────────────────────────────────────────────
NOTE:
It's curious that version 2.0 of DOS did not consistently update the
date on the midnight signal. The next version of DOS (2.1) and all other
versions of DOS do.
──────────────────────────────────────────────────────────────────────────
Service 01H (decimal 1): Set Current Clock Count
Service 01H (decimal 1) sets the clock count in location 0040:006CH using
the values you pass in registers CX and DX. This service automatically
clears the midnight flag at 0040:0070H.
Service 02H (decimal 2): Get Real-Time Clock Time
The PC/AT and the PS/2s have a real-time clock that maintains the current
date and time in nonvolatile memory. This clock runs in parallel to the
system timer referenced by services 00H and 01H. When you boot a PC/AT or
PS/2, the ROM BIOS initializes the system timer count with the time
indicated by the real-time clock.
You can access the real-time clock directly using service 02H (decimal 2).
This service returns the time in binary-coded decimal format (BCD) in
registers CH (hours), CL (minutes), and DH (seconds). If the real-time
clock is defective, the ROM BIOS sets the carry flag.
Service 03H (decimal 3): Set Real-Time Clock Time
This service complements service 02H. It lets you set the real-time clock
on a PC/AT or PS/2, using the same register assignments as service 02H.
Again, the hours, minutes, and seconds values are in BCD format.
Service 04H (decimal 4): Get Real-Time Clock Date
Service 04H (decimal 4) returns the current date as maintained by the
real-time clock in a PC/AT or PS/2. The ROM BIOS returns century (19 or
20) in register CH, the year in CL, the month in DH, and the day in DL.
Again, the values are returned in BCD format. As in service 02H, the ROM
BIOS sets the carry flag if the real-time clock is not operating.
Service 05H (decimal 5): Set Real-Time Clock Date
Service 05H (decimal 5) complements service 04H. This service sets the
real-time clock date, using the same registers as service 04H.
Service 06H (decimal 6): Set Real-Time Clock Alarm
Service 06H (decimal 6) lets you create an "alarm" program that executes
at a specific time. This alarm program must be memory-resident at the time
the alarm occurs. To use this service, make your alarm program
memory-resident using the DOS Terminate-and-Stay-Resident service (see
page 302), and be sure that interrupt vector 4AH (0000:0128H) points to
the start of your program. Then call service 06H to set the time for the
alarm to occur.
Service 06H uses the same register values as service 03H: CH contains
hours in BCD format, CL contains minutes, and DH contains seconds. The ROM
BIOS sets the carry flag when it returns from this service if the
real-time clock is not operating or if the alarm is already in use.
When the real-time clock time matches the alarm time, the BIOS executes
interrupt 4AH, which transfers control to your alarm program. Your program
can then take appropriate action (display a message, for example). Because
the ROM BIOS activates your alarm program by executing an
INT 4AH instruction, the program must exit with an IRET instruction.
Service 07H (decimal 7): Reset Real-Time Clock Alarm
Use service 07H (decimal 7) to disable the real-time clock alarm if it has
been set by a previous call to service 06H.
Service 09H (decimal 9): Get Real-Time Clock Alarm Time and Status
On PS/2 models 25 and 30, you can determine the current status of the
real-time alarm by executing interrupt 1AH, service 09H. This service
reports the alarm status in register DL. If DL = 01H, the alarm is active,
and the alarm time is returned in CH, CL, and DH. If DL = 00H, the alarm
isn't enabled.
Chapter 13 ROM BIOS Services Summary
────────────────────────────────────────────────────────────────────────────
Short Summary
Long Summary
This chapter summarizes the ROM BIOS service routines discussed in
Chapters 8 through 12 in order to provide you with a quick reference
guide.
You can use this chapter to locate the ROM BIOS functions you need and to
determine which registers they use. Where a particular service is very
detailed or tricky to use, we'll refer you to the discussions in the
chapters and to the IBM technical reference manuals.
Short Summary
In this section, we briefly list all the ROM BIOS services so that they
can be seen together, at a glance.
╓┌─┌────────────┌───┌───┌───────┌───────────────────────────┌────────────────╖
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Print screen 05H 5 N/A Send screen contents to
printer.
Video 10H 16 00H Set video mode.
Video 10H 16 01H Set cursor size.
Video 10H 16 02H Set cursor position.
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Video 10H 16 02H Set cursor position.
Video 10H 16 03H Read cursor position.
Video 10H 16 04H Read light-pen position.
Video 10H 16 05H Set active display page.
Video 10H 16 06H Scroll window up.
Video 10H 16 07H Scroll window down.
Video 10H 16 08H Read character and
attribute.
Video 10H 16 09H Write character and
attribute.
Video 10H 16 0AH Write character.
Video 10H 16 0BH Set 4-color palette.
Video 10H 16 0CH Write pixel.
Video 10H 16 0DH Read pixel.
Video 10H 16 0EH Write character in teletype
mode.
Video 10H 16 0FH Get current video mode.
Video 10H 16 10H EGA/VGA color palette
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Video 10H 16 10H EGA/VGA color palette
interface.
Video 10H 16 11H EGA/VGA character generator
interface.
Video 10H 16 12H EGA/VGA "alternate
select."
Video 10H 16 13H Write character string. PC/AT, PS/2, EGA,
VGA only
Video 10H 16 1AH Get/Set display combination PS/2 only
code.
Video 10H 16 1BH Functionality/State PS/2 only
information.
Video 10H 16 1CH Save/Restore video state. VGA only
Equipment 11H 17 N/A Get list of peripheral
equipment.
Memory 12H 18 N/A Get base memory size (in
KB).
Disk 13H 19 00H Reset disk system.
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Disk 13H 19 00H Reset disk system.
Disk 13H 19 01H Get disk status.
Disk 13H 19 02H Read disk sectors.
Disk 13H 19 03H Write disk sectors.
Disk 13H 19 04H Verify disk sectors.
Disk 13H 19 05H Format disk track.
Disk 13H 19 06H Format disk track and set PC/XT fixed disk
bad sector flags. only
Disk 13H 19 07H Format drive starting at PC/XT fixed disk
specified cylinder. only
Disk 13H 19 08H Get current drive
parameters.
Disk 13H 19 09H Initialize fixed-disk
parameter tables.
Disk 13H 19 0AH Read long.
Disk 13H 19 0BH Write long.
Disk 13H 19 0CH Seek to cylinder.
Disk 13H 19 0DH Alternate disk reset.
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Disk 13H 19 0DH Alternate disk reset.
Disk 13H 19 10H Test for drive ready.
Disk 13H 19 11H Recalibrate drive.
Disk 13H 19 14H Controller diagnostics.
Disk 13H 19 15H Get disk type.
Disk 13H 19 16H Change of diskette status.
Disk 13H 19 17H Set diskette type for
format.
Disk 13H 19 18H Set media type for diskette
format.
Disk 13H 19 19H Park heads. PS/2s only
Disk 13H 19 1AH Format ESDI unit. PS/2 models 50,
60, 80 only
Serial port 14H 20 00H Initialize serial port.
Serial port 14H 20 01H Send out one character.
Serial port 14H 20 02H Receive one character.
Serial port 14H 20 03H Get serial port status.
Serial port 14H 20 04H Extended serial port PS/2s only
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Serial port 14H 20 04H Extended serial port PS/2s only
initialize. Serial port
14H
20
05H
Extended serial
port control.
PS/2s only
System 15H 21 00H Turn on cassette motor.
System 15H 21 01H Turn off cassette motor.
System 15H 21 02H Read data blocks.
System 15H 21 03H Write data blocks.
System 15H 21 21H Read/write POST error log. PS/2 models 50,
60, 80 only
System 15H 21 4FH Keyboard intercept. PC/AT, PS/2s only
System 15H 21 80H Device open. PC/AT, PS/2s only
System 15H 21 81H Device close. PC/AT, PS/2s only
System 15H 21 82H Program termination. PC/AT, PS/2s only
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
System 15H 21 82H Program termination. PC/AT, PS/2s only
System 15H 21 83H Start/stop interval timer. PC/AT, PS/2s only
System 15H 21 84H Joystick support. PC/AT, PS/2s only
System 15H 21 85H Sys Req keystroke. PC/AT, PS/2s only
System 15H 21 86H Wait. PC/AT, PS/2s only
System 15H 21 87H Protected-mode data move. PC/AT, PS/2
models 50, 60, 80
System 15H 21 88H Get extended memory size. PC/AT, PS/2
models 50, 60, 80
System 15H 21 89H Switch to protected mode. PC/AT, PS/2
models 50, 60, 80
System 15H 21 90H Device busy. PC/AT, PS/2s only
System 15H 21 91H Interrupt complete. PC/AT, PS/2s only
System 15H 21 C0H Get system configuration
parameters.
System 15H 21 C1H Get extended BIOS data PS/2s only
segment.
System 15H 21 C2H Pointing-device interface. PS/2s only
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
System 15H 21 C2H Pointing-device interface. PS/2s only
System 15H 21 C3H Enable/disable watchdog PS/2 models 50,
timer. 60, 80
System 15H 21 C4H Programmable Option Select PS/2 models 50,
interface. 60, 80
Keyboard 16H 22 00H Read next keystroke.
Keyboard 16H 22 01H Report whether keystroke
ready.
Keyboard 16H 22 02H Get shift status.
Keyboard 16H 22 03H Set typematic rate and PC/AT, PS/2s only
delay.
Keyboard 16H 22 05H Write to keyboard buffer. PC/AT, PS/2s only
Keyboard 16H 22 10H Extended keyboard read. PC/AT, PS/2s only
Keyboard 16H 22 11H Extended keyboard status. PC/AT, PS/2s only
Keyboard 16H 22 12H Extended shift status. PC/AT, PS/2s only
Printer 17H 23 00H Send 1 byte to printer.
Printer 17H 23 01H Initialize printer.
Printer 17H 23 02H Get printer status.
Interrupt
Subject Hex Dec Service Description Notes
──────────────────────────────────────────────────────────────────────────
Printer 17H 23 02H Get printer status.
BASIC 18H 24 N/A Switch control to ROM
BASIC.
Bootstrap 19H 25 N/A Reboot computer.
Time 1AH 26 00H Read current clock count.
Time 1AH 26 01H Set current clock count.
Time 1AH 26 02H Read real-time clock. PC/AT, PS/2s only
Time 1AH 26 03H Set real-time clock. PC/AT, PS/2s only
Time 1AH 26 04H Read date from real-time PC/AT, PS/2s only
clock.
Time 1AH 26 05H Set date in real-time PC/AT, PS/2s only
clock.
Time 1AH 26 06H Set alarm. PC/AT, PS/2s only
Time 1AH 26 07H Reset alarm. PC/AT, PS/2s only
Time 1AH 26 09H Get alarm time and status. PS/2 Model 30
only
──────────────────────────────────────────────────────────────────────────
Long Summary
In this section, we expand the previous summary table to show the register
usage for input and output parameters. The preceding section is best used
to quickly find which service you need; this section is best used to
quickly find how to use each service.
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Print Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Print screen. 05H N/A N/A Send screen
contents to
printer. Status and
result byte at
0050:0000H.
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Set video mode. 10H AH = 00H None Video modes in AL:
AL = video mode 00H: 40 x 25
16-color text
(gray-scaled on
composite
monitors).
01H: 40 x 25
16-color text.
02H: 80 x 25
16-color text
(gray-scaled on
composite
monitors).
03H: 80 x 25
16-color text.
04H: 320 x 200
4-color graphics.
05H: 320 x 200
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
05H: 320 x 200
4-color graphics
(gray-scaled on
composite
monitors).
06H: 640 x 200
2-color graphics.
07H: 80 x 25
monochrome text
(MDA, EGA, VGA).
0DH: 320 x 200
16-color graphics
(EGA, VGA).
0EH: 640 x 200
16-color graphics
(EGA, VGA).
0FH: 640 x 350
monochrome graphics
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
monochrome graphics
(EGA, VGA).
10H: 640 x 350
16-color graphics
(EGA, VGA).
11H: 640 x 480
2-color graphics
(MCGA, VGA).
12H: 640 x 480
16-color graphics
(VGA).
13H: 320 x 200
256-color graphics
(MCGA, VGA).
──────────────────────────────────────────────────────────────────────────
Set cursor size. 10H AH = 01H None Useful values for
CH = starting CH and CL depend on
scan line video mode.
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
scan line video mode.
CL = ending
scan line
──────────────────────────────────────────────────────────────────────────
Set cursor 10H AH = 02H None
position. BH = display
page
DH = row
DL = column
──────────────────────────────────────────────────────────────────────────
Read cursor 10H AH = 03H CH = starting
position. BH = display scan line
page CL = ending scan
line
DH = row
DL = column
──────────────────────────────────────────────────────────────────────────
Read light-pen 10H AH = 04H AH = pen trigger
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Read light-pen 10H AH = 04H AH = pen trigger
position. signal
BX = pixel
column
CH = pixel row
(CGA and EGA
video modes 4,
5, and 6)
CX = pixel row
(EGA except
modes 4, 5, and
6)
DH = character
row
DL = character
column
──────────────────────────────────────────────────────────────────────────
Set active 10H AH = 05H None
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Set active 10H AH = 05H None
display page. AL = page number
──────────────────────────────────────────────────────────────────────────
Scroll 10H AH = 06H None
window up. AL = lines to
scroll up
BH = fill
attribute
CH = upper row
CL = left column
DH = lower row
DL = right
column
──────────────────────────────────────────────────────────────────────────
Scroll window 10H AH = 07H None
down. AL = lines to
scroll down
BH = fill
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
BH = fill
attribute
CH = upper row
CL = left column
DH = lower row
DL = right
column
──────────────────────────────────────────────────────────────────────────
Read character 10H AH = 08H AH = attribute
and attribute. BH = display AL = character
page
──────────────────────────────────────────────────────────────────────────
Write character 10H AH = 09H None
and attribute. AL = character
BH = display
page
BL = attribute
CX = number of
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CX = number of
characters to
repeat
──────────────────────────────────────────────────────────────────────────
Write character. 10H AH = 0AH None
AL = character
BH = page number
BL = color in
graphics mode
CX = number of
characters to
repeat
──────────────────────────────────────────────────────────────────────────
Set color 10H AH = 0BH None
palette. BH = palette
color ID
BL = color to be
used with
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
used with
palette ID
──────────────────────────────────────────────────────────────────────────
Write pixel. 10H AH = 0CH None
AL = color
BH = display
page
CX = pixel
column
DX = pixel row
──────────────────────────────────────────────────────────────────────────
Read pixel. 10H AH = 0DH AL = pixel value
BH = display
page
CX = pixel
column
DX = pixel row
──────────────────────────────────────────────────────────────────────────
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Write character 10H AH = 0EH None Display page number
in teletype AL = character required only for
mode. BH = display IBM PC ROM BIOS
page dated 10/19/81 and
BL = color for earlier.
graphics mode
──────────────────────────────────────────────────────────────────────────
Get current 10H AH = 0FH AH = width in
video mode. characters
AL = video mode
BH = display
page
──────────────────────────────────────────────────────────────────────────
Set one palette 10H AH = 10H None EGA, VGA.
register. AL = 00H
BH = palette
register value
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
register value
BL = palette
register number
──────────────────────────────────────────────────────────────────────────
Set border 10H AH = 10H None EGA, VGA.
register. AL = 01H
BH = border
color
──────────────────────────────────────────────────────────────────────────
Set all palette 10H AH = 10H None EGA, VGA.
registers. AL = 02H
ES:DX -> table
of palette
values
──────────────────────────────────────────────────────────────────────────
Select 10H AH = 10H None EGA, VGA.
background AL = 03H
intensity or To enable
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
intensity or To enable
blink attribute. background
intensity:
BL = 00H
To enable
blinking:
BL = 01H
──────────────────────────────────────────────────────────────────────────
Read one palette 10H AH = 10H BH = palette VGA only.
register. AL = 07H register value
BL = palette
register number
──────────────────────────────────────────────────────────────────────────
Read border 10H AH = 10H BH = border VGA only.
register. AL = 08H color
value
──────────────────────────────────────────────────────────────────────────
Read all palette 10H AH = 10H ES:DX -> table VGA only.
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Read all palette 10H AH = 10H ES:DX -> table VGA only.
registers. AL = 09H of palette
register values
──────────────────────────────────────────────────────────────────────────
Update one 10H AH = 10H None MCGA, VGA.
video DAC AL = 10H
color register. BX = color
register number
DH = red value
CH = green value
CL = blue value
──────────────────────────────────────────────────────────────────────────
Update block 10H AH = 10H MCGA, VGA.
of video DAC AL = 12H
color registers. BX = first
register to
update
CX = number of
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CX = number of
registers to
update
ES:DX -> table
of
red-green-blue
values
──────────────────────────────────────────────────────────────────────────
Set video DAC 10H AH = 10H None VGA only.
color page. AL = 13H
To select paging
mode:
BL = 00H
BH = 00H selects
4 pages of 64
registers, or
BH = 01H selects
16 pages of 16
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
16 pages of 16
registers
To select page:
BL = 01H
BH = page number
──────────────────────────────────────────────────────────────────────────
Read one video 10H AH = 10H DH = red value MCGA, VGA.
DAC color AL = 15H CH = green value
register. BX = color CL = blue value
register number
──────────────────────────────────────────────────────────────────────────
Read block of 10H AH = 10H Table at ES:DX MCGA, VGA.
video DAC color AL = 17H updated
registers. BX = first
register number
CX = number
of registers
ES:DX -> table
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
ES:DX -> table
of
red-green-blue
values
──────────────────────────────────────────────────────────────────────────
Get video DAC 10H AH = 10H None VGA only.
color page. AL = 1AH
BH = current
page
BL = current
paging mode
──────────────────────────────────────────────────────────────────────────
Sum video DAC 10H AH = 10H None MCGA, VGA.
color values to AL = 1BH
gray shades. BX = first color
register
CX = number of
color registers
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
color registers
──────────────────────────────────────────────────────────────────────────
Load 10H AH = 11H None EGA, MCGA, VGA.
user-specified AL = 00H
alphanumeric BH = bytes per
character set. character in
table
BL = character
generator RAM
block
CX = number of
characters
DX = first
character
ES:BP ->
character
definition table
──────────────────────────────────────────────────────────────────────────
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, VGA.
8 x 14 AL = 01H
alphanumeric BL = character
character set. generator RAM
block
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, MCGA, VGA.
8 x 8 AL = 02H
alphanumeric BL = character
character set. generator RAM
block
──────────────────────────────────────────────────────────────────────────
Select displayed 10H AH = 11H None EGA, MCGA, VGA.
alphanumeric AL = 03H
character sets. BL = character
block generator
RAM
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
RAM
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None MCGA, VGA.
8 x 16 AL = 04H
alphanumeric BL = character
character set. generator RAM
block
──────────────────────────────────────────────────────────────────────────
Load 10H AH = 11H None EGA, MCGA, VGA.
user-specified AL = 10H
alphanumeric BH = bytes per
character set character
and adjust definition
displayed BL = character
character generator RAM
height. block
CX = number of
characters
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
characters
DX = first
character
ES:BP ->
character
definition table
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, VGA.
8 x 14 AL = 11H
alphanumeric BL = character
character set generator RAM
and adjust block
displayed
character
height.
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, VGA.
8 x 8 AL = 12H
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
8 x 8 AL = 12H
alphanumeric BL = character
character set generator RAM
and adjust block
displayed
character
height.
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None VGA only.
8 x 16 AL = 14H
alphanumeric BL = character
character set generator RAM
and adjust block
displayed
character
height.
──────────────────────────────────────────────────────────────────────────
Load 10H AH = 11H None EGA, MCGA, VGA.
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Load 10H AH = 11H None EGA, MCGA, VGA.
user-specified AL = 20H Copies ES:BP into
8 x 8 ES:BP -> the interrupt 1FH
graphics character vector. Only
character set. definition table characters 80H
through FFH should
be defined.
──────────────────────────────────────────────────────────────────────────
Load 10H AH = 11H None EGA, MCGA, VGA.
user-specified AL = 21H
graphics CX = bytes per
character set. character
definition
ES:BP ->
character
definition table
User-specified
number of
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number of
character rows:
BL = 00H
DL = number of
character rows
14 character
rows:
BL = 01H
25 character
rows:
BL = 02H
43 character
rows:
BL = 03H
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, VGA.
8 x 14 graphics AL = 22H
character set. BL = (as for
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
character set. BL = (as for
AL = 21H)
DL = (as for
AL = 21H)
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None EGA, MCGA, VGA.
8 x 8 graphics AL = 23H
character set. BL = (as for
AL = 21H)
DL = (as for
AL = 21H)
──────────────────────────────────────────────────────────────────────────
Load ROM BIOS 10H AH = 11H None MCGA, VGA.
8 x 16 graphics AL = 24H
character set. BL = (as for
AL = 21H)
DL = (as for
AL = 21H)
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
AL = 21H)
──────────────────────────────────────────────────────────────────────────
Get character 10H AH = 11H CX = points EGA, MCGA, VGA.
generator AL = 30H DL = displayed
information. Contents of character rows -
interrupt 1FH 1
vector: ES:BP ->
BH = 00H character table
Contents of
interrupt 43H
vector:
BH = 01H
Address of ROM
8 x 14
characters:
BH = 02H
Address of ROM
8 x 8
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
8 x 8
characters:
1BH = 03H
Address of
second half of
ROM 8 x 8 table:
BH = 04H
Address of ROM
9 x 14 alternate
characters:
BH = 05H
Address of ROM
8 x 16
characters:
BH = 06H
Address of ROM
9 x 16 alternate
characters:
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
characters:
BH = 07H
──────────────────────────────────────────────────────────────────────────
Return video 10H AH = 12H BH = default EGA, VGA.
configuration BL = 10H BIOS video mode
information. (00H = color,
01H =
monochrome)
BL = amount of
video RAM (00H =
64 KB, 01H = 128
KB,
02H = 192 KB,
03H = 256 KB)
CH = feature
bits
CL =
configuration
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
configuration
switches
──────────────────────────────────────────────────────────────────────────
Select alternate 10H AH = 12H None EGA, MCGA, VGA.
print screen BL = 20H Updates INT 05H
routine. vector.
──────────────────────────────────────────────────────────────────────────
Select scan 10H AH = 12H AL = 12H VGA only.
lines for BL = 30H
alphanumeric 200 scan lines:
modes. AL = 00H
350 scan lines:
AL = 01H
400 scan lines:
AL = 02H
──────────────────────────────────────────────────────────────────────────
Select default 10H AH = 12H AL = 12H MCGA, VGA.
palette loading. BL = 31H
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
palette loading. BL = 31H
Enable default
palette loading:
AL = 00H
Disable default
palette loading:
AL = 01H
──────────────────────────────────────────────────────────────────────────
Enable/disable 10H AH = 12H AL = 12H MCGA, VGA.
video BL = 32H
addressing. Enable video
addressing:
AL = 00H
Disable video
addressing:
AL = 01H
──────────────────────────────────────────────────────────────────────────
Enable/disable 10H AH = 12H AL = 12H MCGA, VGA.
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Enable/disable 10H AH = 12H AL = 12H MCGA, VGA.
gray-scale BL = 33H
summing. Enable gray-
scale summing:
AL = 00H
Disable gray-
scale summing:
AL = 01H
──────────────────────────────────────────────────────────────────────────
Enable/disable 10H AH = 12H AL = 12H VGA only.
BIOS cursor BL = 34H
emulation. Enable cursor
emulation:
AL = 00H
Disable cursor
emulation:
AL = 01H
──────────────────────────────────────────────────────────────────────────
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Display switch 10H AH = 12H AL = 12H MCGA, VGA.
interface. BL = 35H
Initial adapter
video off:
AL = 00H
Initial planar
video on: AL =
01H
Switch active
video off: AL =
02H
Switch inactive
video on:
AL = 03H
ES:DX->128-byte
save area
──────────────────────────────────────────────────────────────────────────
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Enable/disable 10H AH = 12H AL = 12H VGA only.
video refresh. BL = 36H
Enable refresh:
AL = 00H
Disable refresh:
AL = 01H
──────────────────────────────────────────────────────────────────────────
Write string; 10H AH = 13H None PC/AT, EGA, MCGA,
don't move AL = 00H VGA.
cursor. BL = attribute
BH = display
page
DX = starting
cursor position
CX = length of
string
ES:BP -> start
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
ES:BP -> start
of string
──────────────────────────────────────────────────────────────────────────
Write string; 10H AH = 13H None PC/AT, EGA, MCGA,
move cursor AL = 01H VGA.
after string. BL = attribute
BH = display
page
DX = starting
cursor position
CX = length of
string
ES:BP -> start
of string
──────────────────────────────────────────────────────────────────────────
Write string of 10H AH = 13H None PC/AT, EGA, MCGA,
alternating AL = 02H VGA.
characters and BH = display
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
characters and BH = display
attributes; page
don't move DX = starting
cursor. cursor position
CX = length of
string
ES:BP -> start
of string
──────────────────────────────────────────────────────────────────────────
Write string of 10H AH = 13H None PC/AT, EGA, MCGA,
alternating AL = 03H VGA.
characters and BH = display
attributes; page
move cursor. DX = starting
cursor position
CX = length of
string
ES:BP -> start
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
ES:BP -> start
of string
──────────────────────────────────────────────────────────────────────────
Get display 10H AH = 1AH AL = 1AH MCGA, VGA.
combination AL = 00H BL = active Values returned in
code. display BL and BH:
BH = inactive 00H: no display.
display 01H: MDA or
compatible.
02H: CGA or
compatible.
04H: EGA with color
display.
05H: EGA with
monochrome display.
06H: Professional
Graphics
Controller.
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Controller.
07H: VGA with
monochrome display.
08H: VGA with color
display.
0BH: MCGA with
monochrome display.
0CH: MCGA with
color display.
FFH: unknown.
──────────────────────────────────────────────────────────────────────────
Set display 10H AH = 1AH AL = 1AH MCGA, VGA. See
combination AL = 01H table above for
code. BL = active values in BL and
display BH.
BH = inactive
display
──────────────────────────────────────────────────────────────────────────
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
BIOS 10H AH = 1BH AL = 1BH MCGA, VGA. See the
functionality/ BX = 00H Buffer at ES:DI IBM BIOS Interface
state ES:DI -> 64-byte updated Technical Reference
information. buffer Manual for table
format.
──────────────────────────────────────────────────────────────────────────
Return 10H AH = 1CH AL = 1CH VGA only. Use this
save/restore AL = 00H (if function service before
buffer size. CX = requested supported) saving the current
states BX = video state.
(bit 0 = video save/restore
hardware state; buffer size in
bit 1 = video 64-byte blocks
BIOS data area;
bit 2 = video
DAC and color
registers)
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
registers)
──────────────────────────────────────────────────────────────────────────
Save current 10H AH = 1CH VGA only. May
video state. AL = 01H disrupt current
CX = requested video state, so
states follow a call to
ES:BX -> this service with a
save/restore call to the
buffer "Restore Current
Video State"
service.
──────────────────────────────────────────────────────────────────────────
Restore current 10H AH = 1CH None VGA only.
video state. AL = 02H
CX = requested
states
ES:BX ->
save/restore
Video Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
save/restore
buffer
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Equipment-List Register
Service Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Get list of 11H None AX = equipment Bit settings in AX:
peripheral list, bit-coded 00 = diskette drive
attached installed.
equipment. 01 = math
coprocessor
installed.
02, 03 = system
board RAM in 16 KB
blocks (PCs with 64
KB motherboard
only).
Equipment-List Register
Service Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
only).
02 = pointing
device installed
(PS/2s only).
04, 05 = initial
video mode:
00 = unused;
01 = 40 x 25 color;
10 = 80 x 25 color;
11 = 80 x 25
monochrome.
06, 07 = number of
diskette drives -
1.
08 = (not used).
09, 10, 11 = number
of
RS-232 cards in
Equipment-List Register
Service Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
RS-232 cards in
system.
12 = game I/O
attached
(PC and PC/XT
only).
13 = internal modem
installed.
14, 15 = number of
parallel printers
attached.
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Memory Service Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Get base 12H None AX = memory size See also "Get
memory size. (KB) extended memory
Memory Service Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
memory size. (KB) extended memory
size" (INT 15H,
AH = 88H).
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Reset disk 13H AH = 00H CF = success/ See INT 13H,
system. DL = drive failure flag service 01H, for
number AH = status code status code values.
──────────────────────────────────────────────────────────────────────────
Get disk status. 13H AH = 01H AH = status code (F) = fixed disk
DL = drive Status values only.
number (hex): (D) = diskette
AH = 00H: no only.
error
AH = 01H: bad
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
AH = 01H: bad
command
AH = 02H:
address mark not
found
AH = 03H: write
attempted on
write-protected
disk (D)
AH = 04H: sector
not found
AH = 05H: reset
failed (F)
AH = 06H:
diskette removed
(D)
AH = 07H: bad
parameter table
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
parameter table
(F)
AH = 08H: DMA
overrun
AH = 09H: DMA
across 64 KB
boundary
AH = 0AH: bad
sector flag (F)
AH = 0BH: bad
cylinder (F)
AH = 0CH: bad
media type (D)
AH = 0DH:
invalid number
of sectors on
format (F)
AH = 0EH:
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
AH = 0EH:
control data
address mark
detected (F)
AH = 0FH: DMA
arbitration
level out of
range (F)
AH = 10H: bad
CRC or ECC
AH = 11H: ECC
corrected data
error (F)
AH = 20H:
controller
failed
AH = 40H: seek
failed
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
failed
AH = 80H: time
out (F) or drive
not ready (D)
AH = AAH: drive
not ready (F)
AH = BBH:
undefined error
(F)
AH = CCH: write
fault (F)
AH = E0H: status
error (F)
AH = FFH: sense
operation failed
(F)
──────────────────────────────────────────────────────────────────────────
Read disk 13H AH = 02H CF = success/ Status codes in AH:
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Read disk 13H AH = 02H CF = success/ Status codes in AH:
sectors. AL = number failure flag See INT 13H,
of sectors AH = status code service 01H.
CH = track AL = number of
number sectors read
CL = sector
number
DH = head
number
DL = drive
number
ES:BX = pointer
to buffer
──────────────────────────────────────────────────────────────────────────
Write disk 13H AH = 03H CF = success/ Status codes in AH:
sectors. AL = number of failure flag See INT 13H,
sectors AH = status code service 01H.
CH = track AL = number of
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CH = track AL = number of
number sectors written
CL = sector
number
DH = head
number
DL = drive
number
ES:BX = pointer
to buffer
──────────────────────────────────────────────────────────────────────────
Verify disk 13H AH = 04H CF = success/ Status codes in AH:
sectors. AL = number failure flag See INT 13H,
of sectors AH = status code service 01H.
CH = track AL = number of
number sectors verified
CL = sector
number
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number
DH = head number
DL = drive
number
──────────────────────────────────────────────────────────────────────────
Format disk 13H AH = 05H CF = success/ Status codes in AH:
track AL = interleave failure flag See INT 13H,
(cylinder). value (PC/XT AH = status code service 01H.
only) See Chapter 10 for
CH = cylinder contents
number (bits of table.
0─7)
CL = cylinder
number (bits
8─9)
DH = head number
DL = drive
number
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number
ES:BX -> table
of sector format
information
──────────────────────────────────────────────────────────────────────────
Format disk 13H AH = 06H CF = success/ PC/XT fixed disk
track and set AL = interleave failure flag only.
bad sector value AH = status code
flags. CH = cylinder
number (bits
0─7)
CL = cylinder
number (bits
8─9)
DH = head number
DL = drive
number
──────────────────────────────────────────────────────────────────────────
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Format drive 13H AH = 07H CF = success/ PC/XT fixed disk
starting at AL = interleave failure flag only.
specified value AH = status code
cylinder. CH = cylinder
number (bits
0─7)
CL = cylinder
number (bits
8─9)
DH = head number
DL = drive
number
──────────────────────────────────────────────────────────────────────────
Get current 13H AH = 08H CF = success/ Status codes in AH:
drive failure flag See INT 13H,
parameters. AH = status code service 01H.
DL = number of
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
DL = number of
drives
DH = max.
read/write head
number
CL (bits 6─7) =
max. cylinder
number
(bits 8─9)
CL (bits 0─5) =
max.
sector number
CH = max. number
of cylinders
(bits 0─7)
──────────────────────────────────────────────────────────────────────────
Initialize 13H AH = 09H CF = success/ Interrupt 41H
fixed-disk DL = drive failure flag points to table for
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
fixed-disk DL = drive failure flag points to table for
base tables. number AH = status code drive 0.
Interrupt 46H
points to table for
drive 1.
Status codes in AH:
See INT 13H,
service 01H.
──────────────────────────────────────────────────────────────────────────
Read long. 13H AH = 0AH CF = success/ Status codes in AH:
DL = drive failure flag See INT 13H,
number AH = status code service 01H.
DH = head
number
CH = cylinder
number
CL = sector
number
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number
ES:BX -> buffer
──────────────────────────────────────────────────────────────────────────
Write long. 13H AH = 0BH CF = success/ Status codes in AH:
DL = drive failure flag See INT 13H,
number AH = status code service 01H.
DH = head
number
CH = cylinder
number
CL = sector
number
ES:BX -> buffer
──────────────────────────────────────────────────────────────────────────
Seek to 13H AH = 0CH CF = success/ Status codes in AH:
cylinder. DL = drive failure flag See INT 13H,
number AH = status code service 01H.
DH = head
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
DH = head
number
CH = cylinder
number
──────────────────────────────────────────────────────────────────────────
Alternate 13H AH = 0DH CF = success/ Status codes in AH:
disk reset. DL = drive failure flag See INT 13H,
number AH = status code service 01H.
──────────────────────────────────────────────────────────────────────────
Test for 13H AH = 10H CF = success/ Status codes in AH:
drive ready. DL = drive failure flag See INT 13H,
number AH = status code service 01H.
──────────────────────────────────────────────────────────────────────────
Recalibrate 13H AH = 11H CF = success/ Status codes in AH:
drive. DL = drive failure flag See INT 13H,
number AH = status code service 01H.
──────────────────────────────────────────────────────────────────────────
Controller 13H AH = 14H CF = success/ Status codes in AH:
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Controller 13H AH = 14H CF = success/ Status codes in AH:
diagnostics. failure flag See INT 13H,
AH = status code service 01H.
──────────────────────────────────────────────────────────────────────────
Get disk type. 13H AH = 15H CF = success/ Disk types:
DL = drive failure flag AH = 00H: disk not
number AH = disk type there.
CX, DX = number AH = 01H: diskette,
of 512-byte no change detection
sectors present.
(fixed-disk AH = 02H: diskette,
only) change detection
present.
AH = 03H: fixed
disk.
──────────────────────────────────────────────────────────────────────────
Change of 13H AH = 16H AH = diskette
diskette status. DL = drive change status:
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
diskette status. DL = drive change status:
number 00H = no
diskette change
01H = invalid
parameter
06H = diskette
changed
80H = drive not
ready
──────────────────────────────────────────────────────────────────────────
Set diskette 13H AH = 17H CF = success/ Diskette type set
type for format. AL = diskette failure flag in AL:
type AH = status code AL = 01H: 360 KB
DL = drive diskette in 360 KB
number drive.
AL = 02H: 360 KB
diskette in 1.2 MB
drive.
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
drive.
AL = 03H: 1.2 MB
diskette in 1.2 MB
drive.
AL = 04H: 720 KB
diskette in 720 KB
drive.
──────────────────────────────────────────────────────────────────────────
Set media type 13H AH = 18H CF = success/ Only in PC/AT BIOS
for diskette CH = number of failure flag dated 11/15/85 and
format. tracks (bits AH = status code later, PC/XT BIOS
0─7) ES:DI -> 11-byte dated 1/10/86 and
CL (bits 6─7) = parameter table later, and PS/2s.
number of tracks (disk-base
(bits 8─9) table)
CL (bits 0─5) =
sectors per
track
Disk Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
track
DL = drive
number
──────────────────────────────────────────────────────────────────────────
Park heads. 13H AH = 19H CF = success/ PS/2s only.
DL = drive failure flag
number AH = status code
──────────────────────────────────────────────────────────────────────────
Format Unit. 13H AH = 1AH None For PS/2 fixed
disks used with IBM
Enhanced Small
Device Interface
(ESDI) adapter. See
the IBM BIOS
Interface
Technical Reference
Manual.
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Initialize 14H AH = 00H AX = serial port Serial port
serial port. AL = serial port status parameter
parameters bit settings:
DX = serial port bits 0─1 = word
number length:
10 = 7 bits; 11 = 8
bits.
bit 2 = stop bits:
0 = 1; 1 = 2.
bits 3─4 = parity:
00, 10 = none; 01 =
odd;
11 = even.
bits 5─7 = baud
rate:
000 = 110;
001 = 150;
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
001 = 150;
010 = 300;
011 = 600;
100 = 1200;
101 = 2400;
110 = 4800;
111 = 9600.
For PC/XT/AT family
only. For PS/2s,
use subservice 04H,
"Extended serial
port initialize."
See page 280.
──────────────────────────────────────────────────────────────────────────
Send one 14H AH = 01H AH = status code Status bit
character to AL = character settings: See INT
serial port. DX = serial port 14H, service 03H.
number
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number
──────────────────────────────────────────────────────────────────────────
Receive one 14H AH = 02H AH = status code Status bit
character from DX = serial port AL = character settings: See INT
serial port. number 14H, service 03H.
──────────────────────────────────────────────────────────────────────────
Get serial port 14H AH = 03H AX = status code Status code bit
status. DX = serial port settings:
number AH bit settings:
bit 0 = data ready.
bit 1 = overrun
error.
bit 2 = parity
error.
bit 3 = framing
error.
bit 4 = break
detected.
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
detected.
bit 5 =
transmission buffer
register empty.
bit 6 =
transmission shift
register empty.
bit 7 = time out.
AL bit settings:
bit 0 = delta
clear-to-send.
bit 1 = delta
data-set-ready.
bit 2 =
trailing-edge ring
detected.
bit 3 = change,
receive line signal
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
receive line signal
detected.
bit 4 =
clear-to-send.
bit 5 =
data-set-ready.
bit 6 = ring
detected.
bit 7 = receive
line signal
detected.
──────────────────────────────────────────────────────────────────────────
Extended serial 14H AH = 04H AH = line status PS/2s only. See
port initialize. AL = break AL = modem Chapter 12
BH = parity status for details.
BL = stop bit
CH = word length
CL = baud rate
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CL = baud rate
DX = serial port
number (0─3)
──────────────────────────────────────────────────────────────────────────
Extended serial 14H AH = 05H AH = line status For PS/2s only. See
port control. DX = serial port AL = modem Chapter 12 for
number status details.
(0, 1, 2, 3) If called with
To read modem AL = 00H:
control BL = modem
register: control register
AL = 00H value
To write modem
control
register:
AL = 01H
BL = value for
modem control
Serial Port Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
modem control
register
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Turn on cassette 15H AH = 00H AH = 00H IBM PC only.
motor. CF = 0
──────────────────────────────────────────────────────────────────────────
Turn off 15H AH = 01H AH = 00H IBM PC only.
cassette motor. CF = 0
──────────────────────────────────────────────────────────────────────────
Read cassette 15H AH = 02H ES:BX -> last IBM PC only.
data blocks. CX = number byte
of bytes read + 1
ES:BX -> data DX = number of
area bytes read
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
area bytes read
CF = 0 (no
error)
or 1 (error)
──────────────────────────────────────────────────────────────────────────
Write cassette 15H AH = 03H CF = success/ IBM PC only.
data blocks. CX = number of failure flag
bytes ES:BX -> last
ES:BX -> data byte
area written + 1
CX = 00H
──────────────────────────────────────────────────────────────────────────
Read/Write 15H AH = 21H AH = 00H PS/2 models 50, 60,
power-on To read error If called with 80.
self-test error log: AL = 00H:
log. AL = 00H BX = number of
To write error POST
log: error codes
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
log: error codes
AL = 01H logged
BH = device code ES:DI -> POST
BL = error code error log
If called with
AL = 01H:
CF = status (0:
no error; 1: log
full)
──────────────────────────────────────────────────────────────────────────
Keyboard 15H AH = 4FH See Chapter 12 for
intercept. details.
──────────────────────────────────────────────────────────────────────────
Device open. 15H AH = 80H See Chapter 12 for
details.
──────────────────────────────────────────────────────────────────────────
Device close. 15H AH = 81H See Chapter 12 for
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Device close. 15H AH = 81H See Chapter 12 for
details.
──────────────────────────────────────────────────────────────────────────
Program 15H AH = 82H See Chapter 12 for
termination. details.
──────────────────────────────────────────────────────────────────────────
Start/stop 15H AH = 83H If called with PC/AT and PS/2
interval timer To start AL = 00H: models 50, 60, 80.
(event wait). interval timer: CF = 0 (if timer At completion of
AL = 00H started) or specified interval,
CX,DX = time in CF = 1 (if timer the high-order bit
microseconds already running of the byte at
ES:BX -> 1-byte or ES:BX is set to 1.
flag function not
To stop interval supported)
timer: If called with
AL = 01H AL = 01H:
CF = 0 (if timer
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CF = 0 (if timer
canceled)
CF = 1 (if
function
not supported)
──────────────────────────────────────────────────────────────────────────
Joystick 15H AH = 84H If called with Not supported by PC
support. To read DX = 00H: or XT BIOS prior to
switches: AL = switch 01/10/86.
DX = 00H settings
To read (bits 4─7)
resistive CF = 0 (if
inputs: switches
DX = 00H successfully
read)
or CF = 1
(if
unsuccessful)
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
unsuccessful)
If called with
DX = 01H:
AX = stick A
x-value
BX = stick A
y-value
CX = stick B
x-value
DX = stick B
y-value
──────────────────────────────────────────────────────────────────────────
Sys Req 15H AH = 85H See Chapter 12 for
keystroke. AL = key status details.
──────────────────────────────────────────────────────────────────────────
Wait during 15H AH = 86H CF = 0 (if PC/AT and PS/2s
a specified CX,DX = time in successful) only.
interval. microseconds CF = 1 (timer
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
interval. microseconds CF = 1 (timer
already running
or
function not
supported)
──────────────────────────────────────────────────────────────────────────
Protected-mode 15H AH = 87H PC/AT and PS/2
data move. models 50, 60, 80.
See the IBM BIOS
Technical Reference
Manual for details.
──────────────────────────────────────────────────────────────────────────
Get extended 15H AH = 88H AX = memory size PC/AT and PS/2
memory size. (KB) models 50, 60, 80.
──────────────────────────────────────────────────────────────────────────
Switch to 15H AH = 89H PC/AT and PS/2
protected mode. models 50, 60, 80.
See the IBM BIOS
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
See the IBM BIOS
Technical Reference
Manual for details.
──────────────────────────────────────────────────────────────────────────
Device busy. 15H AH = 90H See Chapter 12 for
details.
──────────────────────────────────────────────────────────────────────────
Interrupt 15H AH = 91H See Chapter 12 for
complete. details.
──────────────────────────────────────────────────────────────────────────
Get system 15H AH = C0H AH = 0 See Chapter 12 for
configuration CF = 0 details. Not
parameters. ES:BX -> ROM supported in PC, XT
BIOS system BIOS prior to
configuration 01/10/86, or AT
parameters prior to 06/10/85.
──────────────────────────────────────────────────────────────────────────
Get extended 15H AH = C1H CF = 0 PS/2s only.
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Get extended 15H AH = C1H CF = 0 PS/2s only.
BIOS data ES = extended
segment. BIOS data
segment address
──────────────────────────────────────────────────────────────────────────
Enable/disable 15H AH = C2H CF = 0 if PS/2s only.
pointing device. AL = 00H successful;
To enable: 1 if error
BH = 00H AH = status:
To disable: 00H: no error
BH = 01H 01H: invalid
function call
02H: invalid
input
03H: interface
error
04H: resend
05H: no device
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
05H: no device
driver installed
──────────────────────────────────────────────────────────────────────────
Reset pointing 15H AH = C2H CF = 0 if PS/2s only.
device. AL = 01H successful;
1 if error
AH = status
(as above)
BH = 00H (device
ID)
BL = undefined
──────────────────────────────────────────────────────────────────────────
Set pointing- 15H AH = C2H CF = 0 if PS/2s only.
device sample AL = 02H successful;
rate. BH = sample 1 if error
rate: AH = status
00H: 10/second (as above)
01H: 20/second
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
01H: 20/second
02H: 40/second
03H: 60/second
04H: 80/second
05H: 100/second
06H: 200/second
──────────────────────────────────────────────────────────────────────────
Set pointing- 15H AH = C2H CF = 0 if PS/2s only.
device AL = 03H successful;
resolution. BH = resolution: 1 if error
00H: 1 AH = status
count/millimeter (as above)
01H: 2
count/millimeter
02H: 4
count/millimeter
03H: 8
count/millimeter
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
count/millimeter
──────────────────────────────────────────────────────────────────────────
Get 15H AH = C2H CF = 0 if PS/2s only.
pointing-device AL = 04H successful; 1 if
type. error
AH = status (as
above)
BH = device ID
──────────────────────────────────────────────────────────────────────────
Initialize 15H AH = C2H CF = 0 if PS/2s only.
pointing device. AL = 05H successful; 1 if
BH = data packet error
size (1─8 bytes) AH = status (as
above)
──────────────────────────────────────────────────────────────────────────
Extended 15H AH = C2H CF = 0 if PS/2s only. See
pointing-device AL = 06H successful; Chapter 12 for
commands. To get status: 1 if error contents of status
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
commands. To get status: 1 if error contents of status
BH = 00H AH = status (as bytes.
To set scaling above)
to 1:1: If called with
BH = 01H BH = 00H:
To set scaling BL = status byte
to 2:1: 1
BH = 02H CL = status byte
2
DL = status byte
3
──────────────────────────────────────────────────────────────────────────
Pass 15H AH = C2H CF = 0 if PS/2s only.
pointing-device AL = 07H successful;
driver address ES:BX -> device 1 if error
to BIOS. driver AH = status (as
above)
──────────────────────────────────────────────────────────────────────────
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Enable/disable 15H AH = C3H CF = 0 PS/2 models 50, 60,
watchdog timer. BX = timer count if successful 80 only.
(01H─FFH)
To enable:
AL = 01H
To disable:
AL = 00H
──────────────────────────────────────────────────────────────────────────
Programmable 15H AH = C4H If called with PS/2 models 50, 60,
Option Select To get POS AL = 00H: 80 only.
(POS) interface. register base AL = 00H
address: DX = base POS
AL = 00H register address
To enable slot If called with
for POS setup: AL = 01H:
AL = 01H AL = 01H
To enable an BL = slot number
System Register
Services☼ Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
To enable an BL = slot number
adapter: If called with
AL = 02H AL = 02H:
AL = 02H
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Read next 16H AH = 00H AH = scan code
keystroke. AL = ASCII
character code
──────────────────────────────────────────────────────────────────────────
Report whether 16H AH = 01H ZF = 0 if
keystroke ready. keystroke
available
AH = scan code
(if ZF = 0)
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
(if ZF = 0)
AL = ASCII
character code
(if ZF = 0)
──────────────────────────────────────────────────────────────────────────
Get shift 16H AH = 02H AL = shift Shift status bits:
status. status bits bit 7 = 1: Insert
state active
bit 6 = 1: Caps
Lock active
bit 5 = 1: Num Lock
active
bit 4 = 1: Scroll
Lock active
bit 3 = 1: Alt
pressed
bit 2 = 1: Ctrl
pressed
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
pressed
bit 1 = 1: left
Shift pressed
bit 0 = 1: right
Shift pressed
──────────────────────────────────────────────────────────────────────────
Set typematic 16H AH = 03H None PC/AT (BIOS dated
rate and delay. AL = 05H 11/15/85 and later)
BL = typematic and PS/2s only. See
rate Chapter 11 for
BH = delay value rate and values.
──────────────────────────────────────────────────────────────────────────
Write to 16H AH = 05H AL = 00H PC/XT (BIOS dated
keyboard buffer. CH = scan code (success); 01/10/86 and
CL = ASCII AL = 01H later), PC/AT (BIOS
character code (keyboard dated 11/15/85 and
buffer full) later), and PS/2s
only.
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
only.
──────────────────────────────────────────────────────────────────────────
Extended 16H AH = 10H AH = scan code PC/XT (BIOS dated
keyboard read. AL = ASCII 01/10/86 and
character code later), PC/AT (BIOS
dated 11/15/85 and
later), and PS/2s
only.
──────────────────────────────────────────────────────────────────────────
Extended 16H AH = 11H If no keystroke PC/XT (BIOS dated
keyboard status. available: 01/10/86 and
ZF = 1 later), PC/AT (BIOS
If keystroke dated 11/15/85 and
available: later), and PS/2s
ZF = 0 only.
AH = scan code
AL = ASCII
character code
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
character code
──────────────────────────────────────────────────────────────────────────
Extended shift 16H AH = 12H AL = shift PC/XT (BIOS dated
status. status 01/10/86 and
(as above) later), PC/AT (BIOS
AH = extended dated 11/15/85 and
shift later), and PS/2s
status: only.
bit 7: Sys Req
is pressed
bit 6: CapsLock
is pressed
bit 5: NumLock
is pressed
bit 4:
ScrollLock
is pressed
bit 3: right Alt
Keyboard Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
bit 3: right Alt
is pressed
bit 2: right
Ctrl
is pressed
bit 1: left Alt
is pressed
bit 0: left Ctrl
is pressed
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Printer Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Send 1 byte to 17H AH = 00H AH = success/ Status bit
printer. AL = character failure status settings:
DX = printer flags bit 7 = 1: not busy
number bit 6 = 1:
Printer Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number bit 6 = 1:
acknowledge
bit 5 = 1: out of
paper
bit 4 = 1: selected
bit 3 = 1: I/O
error
bit 2 = unused
bit 1 = unused
bit 0 = time out
──────────────────────────────────────────────────────────────────────────
Initialize 17H AH = 01H AH = status code Status code bit
printer. DX = printer settings as above.
number
──────────────────────────────────────────────────────────────────────────
Get printer 17H AH = 02H AH = status code Status code bit
status. DX = printer settings as above.
number
Printer Services Register
Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
number
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Miscellaneous Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Switch control 18H None N/A No return, so no
to possible output.
ROM BASIC.
──────────────────────────────────────────────────────────────────────────
Reboot computer. 19H None N/A No return, so no
possible output.
Miscellaneous Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
╓┌─┌────────────────┌───┌────────────────┌────────────────┌──────────────────╖
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
Read current 1AH AH = 00H AL > 00H if time Timer-tick
clock count. of day has frequency is about
passed 18.2 ticks/second,
midnight or about 65,543
CX = tick count, ticks/hour.
high word
DX = tick count,
low word
──────────────────────────────────────────────────────────────────────────
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────────
Set current 1AH AH = 01H None
clock count. CX = tick count,
high word
DX = tick count,
low word
──────────────────────────────────────────────────────────────────────────
Read real-time 1AH AH = 02H CH = hours PC/AT and PS/2s
clock. (in BCD) only. Daylight
CL = minutes savings option not
(in BCD) available in PC/AT
DH = seconds BIOS dated
(in BCD) 01/10/84.
CF = 1 if clock
not operating
DL = 01H if
daylight savings
time option set
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
time option set
──────────────────────────────────────────────────────────────────────────
Set real-time 1AH AH = 03H Input values in
clock. CH = hours BCD.
CL = minutes PC/AT and PS/2s
DH = seconds only. Daylight
DL = 01H for savings option not
automatic available in PC/AT
adjustment for BIOS dated
daylight savings 01/10/84.
time
──────────────────────────────────────────────────────────────────────────
Read date from 1AH AH = 04H DL = day (in PC/AT and PS/2s
real-time clock. BCD) only.
DH = month (in
BCD)
CL = year (in
BCD)
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
BCD)
CH = century (19
or 20 in BCD)
CF = 1 if clock
not operating
──────────────────────────────────────────────────────────────────────────
Set date in 1AH AH = 05H PC/AT and PS/2s
real-time clock. DL = day only.
(in BCD)
DH = month
(in BCD)
CL = year
(in BCD)
CH = century (19
or 20, in BCD)
──────────────────────────────────────────────────────────────────────────
Set alarm. 1AH AH = 06H CF = 1 if clock Place address for
CH = hours not operating or alarm routine in
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
CH = hours not operating or alarm routine in
(in BCD) alarm already interrupt 4AH
CL = minutes set vector before using
(in BCD) this service.
DH = seconds
(in BCD)
──────────────────────────────────────────────────────────────────────────
Reset alarm. 1AH AH = 07H None Disables alarm
previously set with
INT 1AH, service
06H.
──────────────────────────────────────────────────────────────────────────
Get alarm time 1AH AH = 09H CH = hours (in PS/2 models 25, 30
and status. BCD) only.
CL = minutes (in
BCD)
DH = seconds (in
BCD)
Time-of-Day Register
Services Int Input Output Notes
──────────────────────────────────────────────────────────────────────────
BCD)
DL = alarm
status:
00H: alarm not
enabled
01H: alarm
enabled
──────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────
Chapter 14 DOS Basics
The Pros and Cons of Using the DOS Services
DOS: A Disk-Service Cornucopia
DOS and Video: A Difficult Match
DOS Version Differences
Diskette Format Considerations
Comments
Chapters 15 through 18 focus on the program support services provided by
DOS. These DOS services are the entire set of operations that DOS provides
to programs. The last chapter in the series, Chapter 18, summarizes their
technical details. In this chapter, we introduce some of the main concerns
a programmer often faces when working with the DOS services.
Programs access DOS services through a set of interrupts. Interrupt
numbers 20H through 3FH (decimal 32 through 63) are reserved for use by
DOS. Although 10 of these interrupts can be used in programs, most DOS
services are invoked in much the same way as the ROM BIOS services:
through one umbrella interrupt, interrupt 21H (decimal 33). You can access
a variety of DOS functions by specifying a function number in register AH
at the time you call interrupt 21H.
The Pros and Cons of Using the DOS Services
The question of whether or not to use the DOS services arises naturally
during the design and development of sophisticated programs. Our general
advice, echoed throughout this book, is for you to use the highest
available services that will accomplish what you need. This means that,
whenever possible, you should use the built-in services of your
programming language first, resorting only when necessary to direct use of
the DOS services or the ROM BIOS services, and resorting only in extreme
circumstances to direct programming of the computer's hardware.
In practical terms, either a program can be written entirely within the
confines of the programming language's facilities or nearly all of its I/O
work must be done outside the programming language, at a lower level. When
a lower level of programming is needed, with very few exceptions the DOS
services are best suited for disk operations. When you are working with
the keyboard or other I/O devices, either the DOS routines or the ROM BIOS
routines will be adequate, depending on the application. But for low-level
video-display programming, the situation is more complex. Satisfactory
screen output almost always seems to call for the ROM BIOS services and
direct hardware programming, even though in some cases screen output is
best left in the hands of DOS. We'll see why in a moment.
DOS: A Disk-Service Cornucopia
When you inspect the full range of tools and services placed in your hands
by programming languages, by DOS, by the ROM BIOS, and by the computer's
hardware, it becomes quite clear that the richest concentration of
disk-oriented services exists at the DOS level. This almost goes without
saying, because DOS, as a disk operating system, is inherently strongest
in its support of disk operations.
As discussed in Chapters 16 and 17, the majority of services that DOS
performs are directly connected to the manipulation of disk files. Even
some services that are nominally controlled by a program, such as loading
and executing another program (interrupt 21H, function 4BH), involve
disk-file operations. From this perspective, DOS is not so much a disk
operating system as it is a system of disk services designed for use by
your programs. When you are developing programs for the IBM personal
computer family, you should approach DOS from this standpoint: Think of
DOS as a cornucopia of disk operations placed at your service.
DOS and Video: A Difficult Match
Unfortunately, DOS does not provide much in the way of video output
services. In fact, the available DOS services are limited to a
character-only, "glass teletype" interface that is rapidly becoming an
anachronism in these days of high-resolution color graphics.
To achieve attractive, high-performance video output, you must rely on the
ROM BIOS or on direct programming of the video hardware. As we have seen,
IBM has maintained a fairly consistent programming interface to its video
hardware, so many programmers make a practice of bypassing DOS and using
lower-level video programming techniques.
But when you bypass DOS, you encounter a problem: Two different programs
can't reliably share the video hardware. Consider what can happen, for
example, if you write a program that configures the video hardware in a
way that conflicts with the configuration used by a memory-resident
"pop-up" program like SideKick. If your program runs in a video mode that
the pop-up program doesn't recognize, the pop-up program's output may
appear incomprehensible on the screen. Worse, the pop-up program may
reconfigure the video subsystem for its own purposes and leave your
program's video output in limbo.
The problem is amplified in multitasking operating environments, such as
Microsoft Windows or OS/2, where programs generally share access to the
screen. In these environments, a program can bypass the operating system
and gain complete control of the screen only if the operating system
suspends video output from all other concurrently executing programs. Thus
a program that ties up the video hardware can delay the multitasking
execution of background programs.
The designers of OS/2 and Microsoft Windows attacked this problem by
providing a sophisticated gamut of video output services. These video
output services not only resolve conflicts between programs that want to
access the video display, but they also provide very good performance. To
get the best video performance in the world of DOS, however, you must
either resort to ROM BIOS calls and direct hardware programming or else
rely on the video output services provided by your programming language
(which themselves bypass DOS).
When trying to decide which method to use, you should consider the
probable lifetime of your programs and the range of machines they might be
used on. For a PC-specific game program with an expected life of a few
months (common for games), you have little reason to worry about these
issues. This is not the case for a generalized business or professional
application, which should be usable for many years and in many
environments. Make your choice and place your bets.
DOS Version Differences
DOS has evolved since the release of version 1.0 in 1981. Even though each
new release has contained both improvements and bug-fixes, the driving
force behind each release has been a hardware change, and a hardware
change has usually involved a disk-drive change. (See Figure 14-1.)
In all but versions 2.1 and 3.1, changes to DOS involved significant
modifications to disk support (including new disk-storage formats). The
main change to 2.1 was relatively minor, but was disk-related: The
diskette control head-settle time was adjusted to allow for differences in
the performance of the half-height drives used in the PCjr and Portable
PC. Version 2.1 also corrected a few of the known bugs in 2.0. Version 3.1
incorporated networking functions that were designed for version 3.0, but
not ready when 3.0 was released. The following list summarizes the main
differences between these versions:
Version Release Date Hardware Change
──────────────────────────────────────────────────────────────────────────
1.0 August 1981 Original IBM PC (single-sided diskette
drive)
1.1 May 1982 Double-sided diskette drive
2.0 March 1983 PC/XT
2.1 October 1983 PCjr and Portable PC
3.0 August 1984 PC/AT
3.1 March 1985 PC Network
3.2 January 1986 Support for 3-1/2-inch diskette drives
3.3 April 1987 PS/2s
──────────────────────────────────────────────────────────────────────────
Figure 14-1. DOS releases and associated changes to hardware.
Version 1.0 supported the single-sided, 8-sector diskette format. All
basic DOS services were included in this release.
Version 1.1 added support for double-sided diskettes. The DOS services
remained the same.
Version 2.0 added support for 9-sector diskettes (both single- and
double-sided) and for the PC/XT fixed disk. The DOS services were enhanced
extensively in this version. (See Chapter 17.)
Version 2.1 added neither new disk formats nor new DOS services; it did,
however, adjust its disk operation timing to benefit the PCjr and the
Portable PC.
Version 3.0 added support for the PC/AT's 1.2 MB diskette drive and
additional fixed-disk formats. It also laid the groundwork for network
disks.
Version 3.1 added network disks, which include a file-sharing capability.
Version 3.2 introduced support for 3-1/2-inch diskette drives.
Version 3.3 was announced concurrently with IBM's introduction of the
PS/2s. Several new commands and functions were included specifically to
support the PS/2 hardware.
──────────────────────────────────────────────────────────────────────────
NOTE:
Each version of DOS is compatible with prior versions, except in some
very detailed respects (these sorts of details always seem to be
unavoidable).
──────────────────────────────────────────────────────────────────────────
With each release of DOS, there has been a question among software
developers about which version of DOS to target.
In particular, DOS versions 2.0 and later supported a much wider variety
of disk hardware and provided significantly more programming services than
did versions 1.0 and 1.1, so programs that used the more advanced features
of the later DOS versions wouldn't run at all on versions 1.0 and 1.1.
Fortunately, the number of people still using version 1.0 or 1.1 is very
small, so most software developers target their applications toward
versions 2.0 and later. The differences between these later DOS versions
are relatively minor and can usually be accommodated in software that
verifies which version of DOS is running.
Far-sighted software developers must also tackle the question of
compatibility with future versions of DOS. Both IBM and Microsoft are
looking toward OS/2 as the logical successor to DOS. In this view, DOS is
considered a "mature" product; that is, enhancements to future versions
aren't likely to affect existing DOS programs.
Microsoft has published guidelines to help DOS software developers write
programs that can later be converted for use under OS/2. In our
discussions of DOS services in the next few chapters, we'll point out
several techniques that can help ensure the future compatibility of your
DOS programs.
In any case, a program can detect which version of DOS it is running under
by using DOS function 30H (decimal 48). Unless you can be sure of your
audience, you should include this safeguard in your programs and always
check to be certain that the correct DOS version is installed.
Diskette Format Considerations
If you're planning to share or sell your programs, you must decide which
diskette format you'll use to distribute your software. Initially, most
software vendors used single-sided 5-1/4-inch diskettes with eight sectors
per track, because this format was the lowest common denominator that
could be read by all versions of DOS. Later, as single-sided diskette
drives became virtually extinct, software publishers adopted the
double-sided 5-1/4-inch diskette format as an acceptable medium.
If you sell software for both PCs and PS/2s, however, you must contend
with 3-1/2-inch as well as 5-1/4-inch diskette formats. In this case, you
should probably stick to the 720 KB format for 3-1/2-inch disks. You
should also offer a choice of diskette sizes, because both 5-1/4-inch and
3-1/2-inch formats are in widespread use and will be for some time to
come.
Comments
Technical information about DOS has become much easier to find since the
early days, when the only reliable sources of information were the DOS
technical reference manuals. Nowadays, many PC programming magazines
discuss DOS programming techniques. Several good reference books on
various detailed aspects of DOS programming, including memory-resident
programs, installable device drivers, and exception handlers, are also
available.
──────────────────────────────────────────────────────────────────────────
NOTE:
Two "official" sources of detailed information about DOS are the DOS
technical reference manuals distributed by IBM and The MS-DOS
Encyclopedia (published by Microsoft Press).
──────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────
Chapter 15 DOS Interrupts
The Five Main DOS Interrupts
Interrupt 20H (decimal 32): Program Terminate
Interrupt 21H (decimal 33): General DOS Services
Interrupts 25H and 26H (decimal 37 and 38): Absolute Disk Read and
Write
Interrupt 27H (decimal 39): Terminate and Stay Resident
The Multiplex Interrupt
The Three DOS Address Interrupts
Interrupt 22H (decimal 34): Terminate Address
Interrupt 23H (decimal 35): Ctrl-C Handler Address
Interrupt 24H (decimal 36): Critical Error-Handler Address
The DOS Idle Interrupt
The Program Segment Prefix (PSP)
The Internal Structure of the PSP
An Example
In this chapter we'll describe how to communicate with DOS through
interrupts. (See Figure 15-1.) DOS reserves all 32 interrupt numbers from
20H through 3FH (decimal 32 through decimal 63) for its own use. DOS
provides system services through five of these interrupts (20H, 21H, 25H,
26H, and 27H). These interrupts can be called directly from a program with
the INT instruction. DOS uses the interrupt vectors for four others (22H,
23H, 24H, and 28H) to contain the addresses of routines called by DOS
itself; you can substitute your own routines for the default DOS routines
by updating one of these interrupt vectors. Interrupt 2FH is reserved for
communication between memory-resident programs. The other 22 interrupts
reserved by DOS are not intended for use in your programs.
Interrupt Number
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
20H 32 Program Terminate
21H 33 General DOS Services
22H 34 Terminate Address
23H 35 Ctrl-C Handler Address
24H 36 Critical Error-Handler Address
25H 37 Absolute Disk Read
26H 38 Absolute Disk Write
27H 39 Terminate and Stay Resident
28H 40 DOS Idle Interrupt
2FH 47 Multiplex Interrupt
──────────────────────────────────────────────────────────────────────────
Figure 15-1. DOS interrupts.
──────────────────────────────────────────────────────────────────────────
NOTE:
You can use any of the 10 interrupts described in this chapter in your
programs. Nevertheless, there is some overlap between the services
provided through the separate interrupts described in this chapter and
the functions available through interrupt 21H. When you have a choice,
use the interrupt 21H functions described in Chapters 16 and 17. We'll
point out why as we describe each DOS interrupt.
──────────────────────────────────────────────────────────────────────────
The Five Main DOS Interrupts
Of the DOS interrupts described in this chapter, five have built-in
interrupt-handling programs, each of which performs a particular task.
Interrupt 20H (decimal 32): Program Terminate
Interrupt 20H (decimal 32) is used to exit from a program and pass control
back to DOS. It is similar to interrupt 21H, function 00H. (See page
325.) These services can be used interchangeably with any version of DOS
to end a program.
Interrupt 20H does not automatically close files opened with interrupt
21H, functions 0FH or 16H when it terminates a program, so you should
always use interrupt 21H, function 10H to close such files before
exiting. If a modified file is not formally closed, its new length will
not be recorded in the file directory.
A program can set three operational addresses through DOS interrupts 22H,
23H, and 24H, as we will see shortly. As part of the clean-up operations
performed by DOS for interrupt 20H, these addresses are restored to the
values they had before the program was executed. Resetting these addresses
is essential if the program that invoked interrupt 20H was executed as the
"child" of another program. It serves to protect the "parent" program from
using routines intended for the "child." (See DOS function 4BH [decimal
75] in Chapter 17.)
──────────────────────────────────────────────────────────────────────────
NOTE:
When DOS executes a program, it constructs a program segment prefix
(PSP), a 256-byte block of memory that contains control information
that, among other things, is referenced by DOS when a program is
terminated. (We discuss the PSP in detail at the end of this chapter.)
DOS depends on the CS register to point to the PSP when the interrupt
20H terminate service is invoked. If the CS register points elsewhere,
DOS may crash.
In practice, we recommend that you terminate your programs with
interrupt 21H, function 4CH, which is more flexible and less
restrictive than interrupt 20H. The only reason to use interrupt 20H is
to maintain compatibility with DOS version 1.0.
──────────────────────────────────────────────────────────────────────────
Interrupt 21H (decimal 33): General DOS Services
You can take advantage of a wide range of DOS functions through interrupt
21H (decimal 33). Each function has a unique number you specify when you
execute interrupt 21H. Chapters 16 and 17 cover the gamut of interrupt
21H services in detail.
Interrupts 25H and 26H (decimal 37 and 38): Absolute Disk Read and Write
Interrupt 25H (decimal 37) and its companion, interrupt 26H (decimal 38),
are used to read and write specific disk sectors. They are the only DOS
services that ignore the logical structure of a disk and work only with
individual sectors, paying no attention to the locations of files, file
directories, or the File Allocation Table.
Interrupts 25H and 26H are similar to the corresponding ROM BIOS disk
services, except that the sectors are located by a different numbering
method. With the ROM BIOS services, the sectors are selected by their
three-dimensional coordinate locations (cylinder, head, and sector),
whereas with interrupts 25H and 26H, the sectors are selected by their
sequential logical sector numbers. (DOS's sector-numbering system is
discussed on page 109.)
The following BASIC formula converts three-dimensional coordinates used by
the ROM BIOS to logical sector numbers used by DOS:
LOGICAL.SECTOR = (SECTOR - 1) + (HEAD * SECTORS.PER.TRACK) +
(CYLINDER * SECTORS.PER.TRACK * NUMBER.OF.HEADS)
And here are the formulas for converting logical sector numbers to
three-dimensional coordinates:
SECTOR = 1 + LOGICAL.SECTOR MOD SECTORS.PER.TRACK
HEAD = (LOGICAL.SECTOR \ SECTORS.PER.TRACK) MOD NUMBER.OF.HEADS
CYLINDER = LOGICAL.SECTOR \ (SECTORS.PER.TRACK * NUMBER.OF.HEADS)
──────────────────────────────────────────────────────────────────────────
NOTE:
Don't forget that the ROM BIOS counts heads and cylinders from 0 but
counts sectors from 1; DOS logical sectors are numbered from 0.
──────────────────────────────────────────────────────────────────────────
To use interrupt 25H or 26H to read or write a block of logical sectors,
load the necessary parameters into the CPU registers and execute the
interrupt. The number of sectors is specified in the CX register, the
starting sector number is specified in DX, and the memory address for data
transfer is specified in DS:BX. The disk drive is selected by placing a
number in the AL register: Drive A is 0, drive B is 1, and so on.
Although ROM BIOS services work with true physical drives, DOS services
work with logical drive numbers. DOS assumes every computer has at least
two logical drives. If no physical drive B exists, DOS will simulate it by
using the one physical drive as either A or B, whichever is needed. You
can then remap these logical drives by using the DOS ASSIGN command.
The results of interrupts 25H and 26H are reported in the carry flag (CF)
and the AL and AH registers. If no error occurred, CF = 0. If an error did
occur (CF = 1), AL and AH contain the error codes in two somewhat
redundant groups. The AL codes in Figure 15-2 are based on those used by
the DOS critical-error handler through interrupt 24H (see page 308), and
the AH codes in Figure 15-3 are based on the error codes reported by the
ROM BIOS (see page 201).
Error Code Meaning
Hex Dec
──────────────────────────────────────────────────────────────────────────
00H 0 Write-protect error: attempt to write on protected
diskette
01H 1 Unknown unit: invalid drive number
02H 2 Drive not ready (e.g. no disk, or door open)
04H 4 CRC (cyclical redundancy check) error: parity error
06H 6 Seek error: move to requested cylinder failed
07H 7 Unknown media: disk format not recognized
08H 8 Sector not found
0AH 10 Write error
0BH 11 Read error
0CH 12 General, nonspecific error
0FH 15 Invalid disk change
──────────────────────────────────────────────────────────────────────────
Figure 15-2. The error-code values and meanings returned in the AL
register following an error in a disk read or write through DOS interrupt
25H or 26H.
Error Code
Hex Dec Meaning
──────────────────────────────────────────────────────────────────────────
02H 2 Bad address mark: sector ID marking invalid or not
found
03H 3 Write-protect error: attempt to write on protected disk
04H 4 Bad sector: requested sector not on disk
08H 8 DMA (direct memory access) failure
10H 16 Bad CRC: read found invalid parity check of data
20H 32 Controller failed: disk drive controller malfunction
40H 64 Bad seek: move to requested track failed
80H 128 Time out: drive did not respond
──────────────────────────────────────────────────────────────────────────
Figure 15-3. The error-code values and meanings returned in the AH
register following an error in a disk read or write through DOS interrupt
25H or 26H.
Normally, interrupt handlers and other service routines leave the stack
clean when they exit, returning it to its original size and contents. DOS
interrupts 25H and 26H deliberately do not clean up the stack. Instead,
they finish and return to the program with one word left on the stack.
This word holds the contents of the flag register, showing how the flags
were set when the program invoked the service. This is purportedly done to
preserve the program's flag status before the service was used, because
interrupts 25H and 26H use the flags for their return codes. We think this
is a silly precaution because any program that needs to preserve the flags
can simply do what programs normally do when they need something saved:
PUSH them onto the stack themselves. Any program that uses interrupts 25H
and 26H should POP the two extra flag-status bytes off the stack after the
interrupt returns. These bytes can either be placed in the flags register
with a POPF command (which should be done after testing CF for an error)
or be discarded by incrementing the stack pointer register by 2 (ADD
SP,2).
Interrupt 27H (decimal 39): Terminate and Stay Resident
Interrupt 27H (decimal 39) invokes one of the most interesting of all the
services provided by DOS.
Like interrupt 20H, interrupt 27H ends a program, but does not erase it
from memory. Instead, it leaves a specified portion of the program in
memory (the program stays resident). The program and data that are made
resident using interrupt 27H become, in effect, an extension of DOS and
will not be overwritten by other programs.
──────────────────────────────────────────────────────────────────────────
NOTE:
As with interrupt 20H, DOS versions 2.0 and later provide a more
flexible alternative to interrupt 27H. This is interrupt 21H, function
31H, which we recommend instead of interrupt 27H unless you are
concerned about compatibility with DOS version 1.0. See Chapter 17 for
more about interrupt 21H, function 31H.
──────────────────────────────────────────────────────────────────────────
Interrupt 27H (or its function-call equivalent) is used by a number of
sophisticated "pop-up" programs like SideKick. Terminate-and-stay-resident
(TSR) programs typically use this service to establish new
interrupt-handling routines that are meant to stay in effect indefinitely.
Most often, these interrupt-handling routines replace existing DOS or ROM
BIOS interrupt handlers in order to change or extend their operation. But
the resident item is not limited to interrupt handlers and program
instructions; it could just as easily be data. For example, the same
programming technique could be used to load status information into a
common area that various programs would share, allowing them to
communicate indirectly.
Normally, a TSR program is designed in two parts: a resident portion that
remains in memory and a transient portion that installs the resident
portion by updating interrupt vectors, initializing data, and calling the
Terminate-and-Stay-Resident service. The transient portion does not remain
in memory after interrupt 27H is executed.
To accommodate this process, TSR programs are designed with the resident
portion first (that is, at lower addresses). The transient portion
computes the size of the resident portion and places this value in
register DX when it executes interrupt 27H. DOS then leaves the resident
portion in memory but reclaims the memory occupied by the transient
portion for executing other programs.
Anything left resident by this service normally remains resident as long
as DOS is also resident. It is not unusual for several different programs
to leave part of themselves resident. Programs that use this technique are
usually sophisticated and complicated, so it is also not unusual for them
to interfere with each other. To operate such a group of resident programs
successfully, you must sometimes load them in a particular order──an order
you may have to discover through experimentation (an unfair trick to play
on an unsuspecting user).
As with interrupt 20H, the ordinary terminate service, DOS resets the
address vectors for interrupts 22H through 24H when it performs this
terminate-and-stay-resident service. Therefore, you can't use this service
to create resident interrupt handlers for the address interrupts. Although
seemingly a limitation, this is actually fairly reasonable: The address
interrupts are not meant to be used globally; they are meant to be used
only by individual programs. (See the DOS address interrupts section that
follows for further discussion.)
The Multiplex Interrupt
The multiplex interrupt, interrupt 2FH (decimal 47), is used to
communicate with memory-resident programs. This interrupt wasn't used in
DOS version 1, but in version 2 the RAM-resident print spooler PRINT used
it. In DOS versions 3.0 and later, the protocol for using interrupt 2FH
was standardized to allow multiple memory-resident programs to share the
interrupt. (That's why this interrupt is called the multiplex interrupt.)
──────────────────────────────────────────────────────────────────────────
NOTE:
Most of the material in this chapter applies to all versions of DOS;
however, interrupt 2FH is available only with DOS versions 3.0 and
later.
──────────────────────────────────────────────────────────────────────────
To use the multiplex interrupt, you must write a memory-resident TSR
program that contains an interrupt handler for interrupt 2FH. (Use the DOS
Terminate-and-Stay-Resident service to do this.) The transient portion of
the TSR program must copy the address of the previous interrupt 2FH
handler from the interrupt 2FH vector (0000:00BCH) to a variable in the
resident portion. The transient portion then updates the interrupt 2FH
vector with the address of the resident portion's interrupt 2FH handler so
that when interrupt 2FH is subsequently executed, the TSR's handler gets
control.
When interrupt 2FH is executed, the resident interrupt 2FH handler does
the following:
IF AH=IDnumber
THEN process the value in AL
return from the interrupt (IRET)
ELSE jump to the previous interrupt 2FH handler
This simple logic lets several memory-resident programs use the multiplex
interrupt to communicate. The key is that every memory-resident program
must have a unique ID number. Your program's interrupt 2FH handler should
recognize one of the 64 values between C0H and FFH. (There are 256
possible ID numbers, of course, but Microsoft and IBM reserve numbers 00H
through BFH for use by DOS utilities.)
When your program's interrupt 2FH handler gains control, it must first
check the value in register AH. If the value in AH matches the program's
ID number, the handler looks in AL to decide what to do next. If the
values don't match, the handler simply jumps to the address of the
previous interrupt 2FH handler.
The interrupt 2FH handler considers the value in AL to be a function
number and processes it accordingly, as described in the following
paragraphs:
Function 00H has a special meaning. It instructs the interrupt handler to
return one of two values in AL:
■ A value of FFH indicates that an interrupt 2FH handler is resident in
memory and available to process other function numbers.
■ A value of 01H indicates that the ID number in AH is in use.
So, to detect whether a particular TSR program is installed in memory, a
program executes interrupt 2FH with the TSR's ID number in AH and with AL
= 00H. If the TSR is present in memory, it returns AL = FFH. If another
TSR is using the ID number for its own purposes, that TSR returns AL =
01H. Otherwise, any interrupt 2FH handlers in memory simply ignore the
interrupt, causing the interrupt to return AL = 00H.
The best-documented example of how to use the multiplex interrupt is the
PRINT program supplied with DOS versions 3.0 and later. By examining how
PRINT uses the multiplex interrupt, you can make better use of this
interrupt in your own memory-resident programs.
PRINT's multiplex ID number is 1. Any time interrupt 2FH is executed with
this ID number in AH, PRINT's memory-resident interrupt handler processes
the interrupt. Because six different functions are defined by PRINT (see
Figure 15-4), a call to PRINT consists of executing interrupt 2FH with AH
= 01H and a function number in AL.
Each time you run PRINT, the program executes interrupt 2FH with AH = 01H
and AL = 00H. The first time you run the program, the value returned in AL
by the interrupt is 00H, so the program installs itself in memory. When
you invoke PRINT a second time, the value returned in AL as a result of
executing the multiplex interrupt with AH = 01H is FFH. This value is
placed there by the memory-resident copy of PRINT, so the second
invocation of the program knows not to install itself in memory.
The second and subsequent invocations of PRINT can request any of five
different functions by passing a function number to the first,
memory-resident copy of the program. You could also use these functions in
your own programs by placing the value 01H (PRINT's multiplex ID) in
register AH, the function number in register AL, and then issuing
interrupt 2FH.
Function Number Description
──────────────────────────────────────────────────────────────────────────
00H Get installed status.
01H Submit file to print.
02H Remove file from print queue.
03H Cancel all files in print queue.
04H Hold print queue.
05H Release print queue.
──────────────────────────────────────────────────────────────────────────
Figure 15-4. PRINT functions defined through the multiplex interrupt.
Function 01H submits a file to the print spooler for printing. To tell
PRINT what is to be printed, you set the register pair DS:DX to point to a
5-byte area called a submit packet. The first byte of the submit packet is
a level code (which should be 0). The remaining 4 bytes of the submit
packet are the segmented address of an ASCIIZ string (see page 350) that
defines the pathname of the file to be printed. The pathname must be a
single file. The global filename characters * and ? are not allowed.
When a file is submitted using this function, it is added to the end of
the queue, or list, of files to be printed. The files are printed in turn
and are dropped from the queue after they're printed.
Function 02H cancels individual files queued for printing. The register
pair DS:DX points to the ASCIIZ string that defines which file is to be
removed from the queue. In this case, the global filename characters * and
? can be used. In function 02H, DS:DX points directly to the filename
string, rather than to a submit packet that points to the string.
Function 03H cancels all files queued for printing. For both functions 02H
and 03H, if the file currently being printed is canceled, PRINT stops
printing the file and prints a short message to that effect.
Function 04H gives programs access to the print queue so they can inspect
it. The queue is frozen when this function is requested, so you don't have
to worry about the list changing while you inspect it. Issuing any other
PRINT function call will unfreeze the queue. Function 04H returns a
pointer in the register pair DS:SI that points to a list of filenames
queued for printing. Entries in the list are strings with a fixed length
of 64 bytes. The end of the list is indicated by an entry that begins with
a zero byte.
The queue freeze imposed by function 04H doesn't need to halt the printing
operation. But function 04H will suspend the removal from the queue of a
file that is finished printing.
Function 05H is essentially a null function that does nothing but unfreeze
the queue of filenames frozen by function 04H. (The other four functions
can do this, too.)
The Three DOS Address Interrupts
DOS uses three interrupts, 22H through 24H (decimal 34 through 36), to
handle three exceptional circumstances: the end of a program, a "break"
keyboard action (Ctrl-Break or Ctrl-C on the standard PC keyboard), and
any "critical error" (usually a disk error of some kind). Your programs
can affect the action taken in each of these three circumstances by
changing the corresponding interrupt vector to point to any operation you
choose. This is why we call these interrupts the address interrupts.
DOS maintains a default address setting for each of these interrupts,
which is preserved at the beginning of a program's operation and restored
after the program is finished. This allows your programs to freely change
these vectors according to their needs without disturbing the operation of
subsequent programs or the operation of DOS itself.
Interrupt 22H (decimal 34): Terminate Address
The address associated with interrupt 22H (decimal 34) specifies where
control of the computer will be passed when a program's execution ends
with a call to DOS interrupt 20H or 27H, or with interrupt 21H, function
00H, 31H, or 4CH. Interrupt 22H isn't designed to be executed directly by
a program using the INT instruction. Instead, DOS uses the interrupt 22H
vector to store the address of its own program termination routine.
It's not a good idea to manipulate the DOS terminate address. The inner
workings of the default DOS program termination routine are not
documented, so writing a substitute routine that terminates a program
cleanly without confounding DOS is difficult. If you are qualified to use
this feature, then you probably understand it better than we can explain
it.
Interrupt 23H (decimal 35): Ctrl-C Handler Address
The address associated with interrupt 23H (decimal 35) points to the
interrupt-handling routine that DOS invokes in response to the Ctrl-C key
combination. Thus interrupt 23H is intended to be executed only by DOS,
not by an application program. A few old-fashioned programs, such as the
DOS editor EDLIN, use Ctrl-C as a command keystroke, but in most
applications the Ctrl-C combination signals that the user wants to
interrupt an ongoing process.
DOS is a bit quirky about when it will respond to a Ctrl-C keystroke.
Normally, DOS acts on a break only when it is reading from or writing to a
character I/O device (the screen, keyboard, printer, or communications
port). However, the BREAK ON command allows DOS versions 2.0 and later to
act on Ctrl-C at the time of most other DOS system calls.
DOS's default Ctrl-C handler terminates the program or batch file you are
executing. However, if your program provides its own interrupt 23H
handler, it can have DOS take any action you want.
In general, a Ctrl-C handler can take three different courses of action:
■ It can perform some useful action, such as setting a flag, and then
return to DOS with an interrupt return (IRET) instruction. In this
case, DOS picks up where it left off, without terminating your
program's execution.
■ It can set or clear the carry flag and then return to DOS with a far
return instruction (RET 2) that discards the flags pushed on the stack
when the interrupt 23H handler was called by DOS. If the carry flag is
set, DOS terminates the interrupted program. If the carry flag is
clear, DOS continues execution.
■ It can keep control without returning to DOS. This option is tricky,
however, because you don't usually know what was on the stack at the
moment DOS detected the Ctrl-C keystroke. An interrupt 23H handler that
doesn't return to DOS should generally restore the stack pointer
register (SP) to a predetermined value. It should also execute
interrupt 21H, function 0DH, to flush DOS file buffers so that the DOS
disk I/O system will be in a known state.
The usual reason to write your own Ctrl-C handler is to let your program
handle a keyboard break itself. Even if you want your program to terminate
immediately after Ctrl-C is pressed, you may still need to clean up before
your program terminates. For example, if you use interrupt 21H, functions
0FH or 16H, to open a file, you should write your own Ctrl-C handler to
close it because the default DOS Ctrl-C handler won't do so. Also, if you
installed your own interrupt handlers for ROM BIOS or hardware interrupts,
the DOS Ctrl-C handler won't restore them before it terminates your
program. Again, your Ctrl-C handler should do this if necessary.
If you do write your own Ctrl-C handler, don't forget the relationship
between Ctrl-C and the keyboard Ctrl-Break combination. When you press
Ctrl-Break, the ROM BIOS keyboard interrupt handler generates interrupt
1BH. DOS's interrupt 1BH handler inserts a Ctrl-C key code into the
keyboard input buffer. The next time DOS checks the keyboard buffer, it
finds Ctrl-C and executes interrupt 23H. Thus, in effect, pressing
Ctrl-Break has the same effect as pressing Ctrl-C, except that DOS detects
the break generated by Ctrl-Break without first processing the intervening
characters in the keyboard buffer.
Interrupt 24H (decimal 36): Critical Error-Handler Address
The address associated with interrupt 24H (decimal 36) points to the
interrupt-handling routine invoked whenever DOS detects a "critical
error"── an emergency situation that prevents DOS from continuing with
normal processing. Typically, the critical error is a disk error, but
other errors are also reported, as we'll see.
Like interrupt 23H, interrupt 24H is intended to be invoked only by DOS,
not by an application program. However, an application can substitute its
own interrupt 24H handler for the default DOS handler. The DOS default
handler produces a familiar message (shown on the following page).
Abort, Retry, Ignore? (in DOS versions prior to 3.3)
or
Abort, Retry, Fail? (in DOS versions 3.3 and later)
If you substitute a customized interrupt 24H handler for the one DOS
provides, you can tailor critical-error handling to the needs of your
program.
When DOS transfers control to a critical-error handler, it provides
several sources of information about the error itself, and about the state
of the system before the error occurred. These sources include the
register pair BP:SI, the stack, the AH register, and the DI register. We
will cover them one by one because this process is quite complicated.
If you are operating under DOS version 2.0 or later, the register pair
BP:SI is set to point to a device-header control block. Your
critical-error handler can inspect the device header to learn more about
the device (disk drive, printer, and so forth) that experienced the error.
(See the DOS technical reference manuals for more about the device
header.)
When the critical-error handler gains control, the stack contains the
complete register set of the program that issued the DOS function call
that ended in the critical error. This information can be quite useful to
an error handler that is intimately integrated with the active program.
The usual method of accessing the information on the stack is to address
the stack through register BP. You can access the stack as shown in Figure
15-5 on the following page if the first two instructions in your
critical-error handler are
PUSH BP
MOV BP,SP
DOS indicates the nature of a critical error primarily through a
combination of the high-order bit of the AH register and the low-order
byte of the DI register (a curious choice, for sure). If the high-order
bit of AH is 0, the error is related to a disk operation. If the same bit
(bit 7 of AH) is 1, the error is something other than a disk error, as we
shall discuss shortly.
When the error is a disk-device error (high-order bit of AH is 0),
register AL identifies the drive number (0 is drive A, 1 is drive B, and
so on). Bits 0 through 5 of AH indicate further information about the
error, as shown in Figure 15-6.
┌─────────────┐ ──┐
[BP + 30] │ Flags │ │
├─────────────┤ │
[BP + 28] │ CS │ ├── Flags and CS:IP pushed by the
├─────────────┤ │ interrupt 21H call that generated
[BP + 26] │ IP │ │ the error
├─────────────┤ ──┤
[BP + 24] │ ES │ │
├─────────────┤ │
[BP + 22] │ DS │ │
├─────────────┤ │
[BP + 20] │ BP │ │
├─────────────┤ │
[BP + 18] │ DI │ │
├─────────────┤ │
[BP + 16] │ SI │ ├── Registers at point of interrupt 21H call
├─────────────┤ │
[BP + 14] │ DX │ │
├─────────────┤ │
[BP + 12] │ CX │ │
├─────────────┤ │
[BP + 10] │ BX │ │
├─────────────┤ │
[BP + 8] │ AX │ │
├─────────────┤ ──┤
[BP + 6] │ Flags │ │
├─────────────┤ │
[BP + 4] │ CS │ ├── Flags and CS:IP pushed by DOS when it
├─────────────┤ │ called the critical-error handler
[BP + 2] │ IP │ │
├─────────────┤ ──┘
[BP] │ Previous BP │
└─────────────┘
Figure 15-5. Information passed on the stack to an interrupt 24H
(critical-error) handler.
Bit
5 4 3 2 1 0 Value Meaning
──────────────────────────────────────────────────────────────────────────
. . . . . 0 0 Read error
. . . . . 1 1 Write error
. . . 0 0 . 0 Error involved DOS system files
. . . 0 1 . 1 Error involved file allocation table
. . . 1 0 . 2 Error involved root directory
. . . 1 1 . 3 Error involved files area of disk
. . 1 . . . 1 Fail response allowed
. 1 . . . . 1 Retry response allowed
1 . . . . . 1 Ignore response allowed
──────────────────────────────────────────────────────────────────────────
Figure 15-6. The bit values and associated errors indicated in bits 0
through 5 of the AH register when DOS invokes interrupt 24H.
DOS returns additional information about the error in the low-order byte
of register DI (Figure 15-7). The error codes in DI cover a variety of
input/output devices, so you must rely on a combination of the information
in AH and in DI to determine the exact nature of the critical error.
If bit 7 of AH is set, the error is probably not a disk error, though it
may be disk related. One disk-related error normally reported when bit 7
of AH is set is an error in the disk's FAT. In DOS version 1, this is
always the case. For versions 2.0 and later, the error handler should
inspect bit 15 of the word that is offset 4 bytes into the device header
(BP: [SI + 4]). If this bit is clear, the device is a block device (disk)
and the error is a FAT error. If this bit is set, the device is a
character device, in which case the low-order byte of DI defines the exact
problem (the high-order byte should be ignored). DI error-code values
shown in Figure 15-7 are essentially the same as those reported in AL for
interrupts 25H and 26H (decimal 37 and 38).
You can use the following interrupt 21H functions in your critical-error
handler to report what's going on to the program's user:
■ Functions 01H through 0CH, which provide simple keyboard and display
services
■ Function 30H, which returns the DOS version number
■ Function 59H, which returns extended error information in DOS versions
3.0 and later
Error Code
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
00H 0 Write-protect error; attempt to write on protected
diskette
01H 1 Unknown unit (invalid drive number)
02H 2 Drive not ready (no diskette or drive door open)
03H 3 Unknown command requested
04H 4 Data error (CRC)
05H 5 Bad request structure length
06H 6 Seek error; move to requested cylinder failed
07H 7 Unknown disk format
08H 8 Sector not found
09H 9 Printer out of paper
0AH 10 Write fault
0BH 11 Read fault
0CH 12 General, nonspecific error
0FH 15 Invalid disk change (DOS version 3.0 or later)
──────────────────────────────────────────────────────────────────────────
Figure 15-7. Errors indicated in the low-order byte of register DI when
DOS invokes interrupt 24H.
Don't call other DOS services within your critical-error handler, however,
because other services may overwrite internal buffers or stacks that DOS
will need when the error handler returns.
Normally, an error-handler routine returns to DOS after doing whatever it
chooses to do. DOS can then take one of four courses of action: It can
ignore the error, try the operation again, terminate the program, or fail
the requested operation and return to the program (DOS versions 3.1 and
later). You tell DOS which course you want it to take by loading a value
from Figure 15-8 into the AL register before executing an IRET to return
to DOS.
If you use a custom critical-error handler, it will remain in effect only
as long as the program that installs it is running. When the program
terminates, DOS replaces the contents of the interrupt 24H vector with the
address of the default critical-error handler.
AL Description
──────────────────────────────────────────────────────────────────────────
00H Ignore the error and press onward.
01H Retry the operation.
02H Terminate the program.
03H Fail the operation (DOS versions 3.1 and later).
──────────────────────────────────────────────────────────────────────────
Figure 15-8. Values that can be returned to DOS in register AL by an
interrupt 24H (critical-error) handler.
The DOS Idle Interrupt
DOS executes interrupt 28H (decimal 40) within interrupt 21H services that
loop while waiting for an expected event, such as a keystroke. For
example, if you execute the DOS keyboard-input service (interrupt 21H,
service 01H), DOS executes interrupt 28H within an idle loop that waits
for the next keystroke.
The default DOS handler for interrupt 28H is merely an IRET instruction;
that is, executing interrupt 28H normally does nothing at all. You can
substitute your own interrupt 28H handler, however, that does something
useful while DOS is otherwise idle. In particular, a memory-resident
program can contain an interrupt 28H handler that is executed repeatedly
whenever DOS is waiting for keyboard output.
The biggest problem with installing your own interrupt 28H handler is that
the handler can execute interrupt 21H to access DOS services only under
very specific circumstances. Unfortunately, you must know many details
about the way DOS internally processes interrupt 21H requests in order to
use these safely within an interrupt 28H handler.
The Program Segment Prefix (PSP)
When DOS loads a program, it sets aside a 256-byte block of memory for the
program: the program segment prefix (PSP). The PSP contains a hodgepodge
of information that DOS uses to help run the program. A PSP is associated
with every DOS program, no matter what language the program is written in.
However, for programming purposes, the information stored in the PSP is
more relevant to programs written in assembly language than to programs
written in high-level languages. This is because with high-level
languages, the language is normally in charge of the program's working
environment, memory usage, and file control──all the information that the
PSP is concerned with. Therefore, you can normally make good use of the
PSP only if your program is assembly-language based.
Before we describe the different elements of the PSP, we need to look at
the relationship between the PSP and the program it supports.
DOS always builds a program's PSP in memory just below the memory area
allocated to the program itself. When the program receives control from
DOS, segment registers DS and ES point to the beginning of the PSP.
Because it sometimes needs to locate PSP information, DOS keeps a copy of
the PSP segment value internally.
The best way to explain how the PSP and the program work together is to
jump right into the PSP's internal structure. We will reveal the purpose
and potential use of each element as we explain it.
The Internal Structure of the PSP
As you will soon discover, the PSP contains a rather confusing mixture of
items. (See Figure 15-9.) The background and history of DOS pull it in
different directions──backward to the earlier CP/M system and forward to
UNIX-type operating environments. As a result, the PSP contains elements
that serve different purposes and are oriented to different programming
methods. We'll discuss the elements in the order in which they appear.
The field at offset 00H (2 bytes) contains bytes CDH and 20H, the
interrupt 20H instruction. As we saw in the discussion of interrupt 20H in
this chapter, this interrupt is only one of several standard ways for a
program to terminate. This instruction is placed at the beginning of the
PSP (at offset 00H) so that a program can end itself simply by jumping to
this location when the CS points to the PSP. As you might guess, this is
not the most sensible thing for a program to do; it's always best to go
through the appropriate interrupt or function call. This odd method of
terminating a program is a relic of the days when CP/M compatibility was
important.
Offset Length
Hex Dec (bytes) Description
──────────────────────────────────────────────────────────────────────────
00H 0 2 INT 20H instruction
02H 2 2 Size of memory (in paragraphs)
04H 4 1 (Reserved; normally 0)
05H 5 5 Call to DOS function dispatcher
0AH 10 4 Interrupt 22H (Terminate) address
0EH 14 4 Interrupt 23H (Ctrl-C) address
12H 18 4 Interrupt 24H (Critical Error)
address
16H 22 22 (Reserved)
2CH 44 2 Environment segment address
2EH 46 34 Reserved
50H 80 3 INT 21H, RETF instructions
53H 83 9 (Reserved)
5CH 92 16 FCB #1
6CH 108 20 FCB #2
80H 128 128 Command-line parameters and default
Disk Transfer Area (DTA)
──────────────────────────────────────────────────────────────────────────
Figure 15-9. The parts of the program segment prefix (PSP).
The field at offset 02H (2 bytes) contains the segment address of the last
paragraph of memory allocated to the program. DOS normally loads a program
in the first free area of memory large enough to contain the program. A
program can use this field to determine the actual size of the memory area
allocated to it.
In practice, there's a better way to determine the amount of memory
allocated to a program. Interrupt 21H, function 4AH can return the size
of any block of memory, not just the block into which a program is loaded.
(See Chapter 17 for more on this DOS service.)
The field at offset 05H (5 bytes) contains a long call to the DOS function
dispatcher, the internal DOS routine that examines the function number you
pass to DOS and executes the corresponding service routine. This field,
too, is a remnant of the days when CP/M compatibility was important to DOS
programmers. A program can make a near CALL to offset 05H in the PSP with
a function number in register CL and get the same result as if it had
loaded AH with the function number and executed interrupt 21H.
Needless to say, this technique is not very useful in real-world DOS
programs.
The fields at offsets 0AH, 0EH, and 12H (4 bytes each) contain the
segmented addresses of the default handlers for interrupt 22H (Terminate),
23H (Ctrl-C), and 24H (Critical Error). These addresses are stored in the
PSP for your convenience. If you substitute a customized interrupt handler
for one of the DOS handlers, you can restore the default handler by
copying its address from the PSP into the corresponding interrupt vector.
In DOS versions 2.0 and later, the field at offset 2CH (2 bytes) contains
the paragraph address of the program's environment block. The environment
block contains a list of ASCIIZ strings (strings of ASCII characters, each
terminated with a zero byte) that define various kinds of information. The
end of the environment block is marked by a zero-length string (a single
zero byte) where you would expect to find the first byte of the next
string. Environment blocks that begin with a zero-length string contain no
strings.
Each environment string is of the form NAME = value, where NAME is
capitalized and of any reasonable length and value can be almost anything.
The environment thus consists of a list of global variables, each of which
contains information that your program may be able to use. For example, if
the environment block contains the PATH environment variable (that is, a
string that starts with PATH=), any program──including DOS itself──can
examine its environment block to determine which directories to search for
executable files (and in what order). In this way the environment block
provides a simple means of passing information to any program that
examines it. (You can change the contents of the environment block with
the DOS SET command.)
DOS makes a copy of the environment block whenever it loads a program to
be executed, and places the copy's paragraph address (segment) in the
program's PSP. To obtain information from the environment block, a program
must first obtain its segment from the PSP and then examine each of the
zero-terminated strings. Some high-level languages contain functions that
do this for you. For example, in C, the getenv() library function does all
the work.
Many sophisticated DOS programs rely on information in the environment
block. Also, the concept of the environment is found in other powerful
operating systems, including UNIX and OS/2. Whenever you need to pass
user-configurable information to a program, we highly recommend the use of
the environment block.
The field at offset 50H contains two executable 8086 instructions: INT 21H
and RETF (far return).
This is another kludge that lets you invoke DOS functions somewhat
indirectly. To use this feature, set up everything necessary to invoke a
DOS interrupt 21H function (selecting the function in AH, and so forth).
Then, instead of bravely performing an interrupt 21H (a 2-byte
instruction), do a far call to offset 50H in the PSP (a 5-byte
instruction).
You might expect that this feature is another flash from the past, a bit
of CP/M compatibility, but actually it was introduced with DOS version 2.0
and will not work with previous versions of DOS. You might find that
making a far call to offset 50H in the PSP is handy if you intend to patch
the address of a different function dispatcher into your code, but in most
cases, a simple INT 21H will suffice.
The fields at offsets 5CH and 6CH support old-fashioned file processing,
using file control blocks, or FCBs. FCBs can be used for file I/O with any
version of DOS, but their use is discouraged with DOS versions 2.0 and
later, where more modern file I/O is available through the use of file
handles. See page 341 for more on file control blocks, and see page 350
for more on file handles.
This area of the PSP was designed to make life easier for programs that
receive one or two filenames as parameters. The basic idea, and a good one
we think, is to let DOS construct the necessary FCBs out of the first two
command-line parameters (the parameters given on the command line,
following the program name). If a program needs either or both FCBs, it
can open and use them without having to decode the command-line parameters
and construct the FCBs itself.
If you use this feature of the PSP, you should be aware of three potential
complications: First, the two FCBs overlap where they are placed. If your
program needs only the first, fine; but if it needs the second FCB as
well, one or both of them should be moved elsewhere before they are used.
Second, these FCBs can involve FCB extensions, a fact overlooked in most
DOS documentation for the PSP. Finally, if you use a DOS function that
requires an extended FCB, you should be careful to copy the default FCBs
to another area of memory where the FCB extensions won't overlap other
data in the PSP.
Keep in mind that the use of FCBs is considered obsolete, but if you want
to use them, this information should help.
The field at offset 80H serves two purposes: When DOS first builds the
PSP, it fills this field with the command-line parameters typed by the
user when the program was invoked. The length of the command line is in
the byte at offset 80H. A string containing the command-line parameters
follows at offset 81H.
This string has some peculiarities: It does not contain the name of the
program that was invoked. Instead, it begins with the character that
immediately follows the program name, which is usually a blank.
Separators, such as blanks or commas, are not stripped out or compressed.
If you use the command line, you have to be prepared to scan through it,
recognizing standard separators. Fortunately, high-level languages often
provide functions that parse the command parameter string for you. In C,
for example, the values argc and argv are passed to the main startup
routine in every C program. These two values contain the number of
command-line parameters and the address of a list of individual
parameters. It's usually easier to rely on your high-level language to
extract command-line parameters from the PSP than it is to do it yourself
in assembly language.
Starting with DOS version 2.0, the command line is modified in a
particular way: DOS strips any redirection parameters (such as < or >) and
reconstructs the parameter line as if these items were not there. As a
result of these two operations on the command string, a program can
neither find out if its standard I/O is being redirected nor find out its
own name.
The other purpose served by the field at offset 80H in the PSP is that of
the default Disk Transfer Area. This default buffer area is established by
DOS just in case you use a DOS service that calls for a DTA and haven't
yet set up your own DTA buffer. See Chapters 16 and 17 for descriptions
of the services that use or manipulate the DTA.
An Example
This chapter's interface example shows how you can use an interrupt
handler to process Ctrl-C keystrokes. The example consists of two
assembly-language routines.
The first routine, INT23Handler, gains control when DOS executes INT 23H
in response to a Ctrl-C keystroke. This handler simply increments the
value in a flag and then returns to DOS with an IRET instruction.
Note how the flag _INT23Flag is addressed through the segment group
DGROUP. In many languages, segments with different names are grouped
together in one logical group so that they can all be addressed with the
same segment register. In the case of Microsoft C, this group of segments
is named DGROUP, and it includes the data segment (_DATA) used by the
compiled C program.
The second assembly-language routine, _Install() is designed to be called
by a C program. This short routine calls a DOS interrupt 21H function that
updates the interrupt 23H vector with the address of the interrupt
handler. (The next few chapters contain more about this DOS function and
about interrupt 21H services in general.)
DGROUP GROUP _DATA
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT,ds:DGROUP
;
; the interrupt 23H handler:
INT23Handler PROC far
push ds ; preserve all registers us
push ax ; ... in this interrupt han
mov ax,seg DGROUP ; set DS to the segment whe
mov ds,ax ; ... the flag is located
inc word ptr _INT23flag ; increment the flag
pop ax ; restore regs and return
pop ds
iret
INT23Handler ENDP
;
; the C-callable installation routine:
PUBLIC _Install
_Install PROC near
push bp ; the usual C prologue
mov bp,sp
push ds ; preserve DS
push cs ; set DS:DX to point to ...
pop ds
mov dx,offset INT23Handler ; ... the interrupt handler
mov ax,2523h ; AH = DOS function number
; AL = interrupt number
int 21h ; call DOS to update the ..
; ... interrupt vector
pop ds
pop bp ; restore regs and return
ret
_Install ENDP
_TEXT ENDS
;
; the flag set by the interrupt 23H handler when Ctrl-C is pressed:
_DATA SEGMENT word public 'DATA'
PUBLIC _INT23flag
_INT23flag DW 0 ; flag (initial value = 0)
_DATA ENDS
The snippet of C code that follows shows how you could use this interrupt
23H handler in a program. This C program does nothing but wait for you to
press Ctrl-C. When you do, the assembly-language interrupt 23H handler
increments the flag. When the loop in the C program sees that the flag is
nonzero, it displays a message and decrements the flag.
extern int INT23flag; /* flag set when Ctrl-C is pr
main()
{
int KeyCode;
Install(); /* install the interrupt 23H
do
{
while( INT23flag > 0 )
{
printf( "\nCtrl-C was pressed" ); /* ... show a message ... *
--INT23flag; /* ... and decrement the fl
}
if( kbhit() ) /* look for a keypress */
KeyCode = getch();
else
KeyCode = 0;
}
while( KeyCode != 0x0D ); /* loop until Enter is pres
}
Although the C code is short, it suggests two important points. One is
that you must give DOS the chance to detect a Ctrl-C keystroke each time
you test your interrupt 23H flag. (Remember that DOS is guaranteed to
check for Ctrl-C only when it reads or writes to a character input/output
device.) In this program, C's kbhit() function calls DOS to check for
keyboard activity and, at the same time, lets DOS check for Ctrl-C as
well.
Also, note how the interrupt handler increments the flag instead of merely
setting it to "true" or "false." This lets the loop in the C program
process rapid, successive interrupts without losing track of how many
interrupts have occurred.
────────────────────────────────────────────────────────────────────────────
Chapter 16 DOS Functions: Version 1
Interrupt 21H Functions: DOS Version 1
Function 00H (decimal 0): Terminate
Function 01H (decimal 1): Character Input with Echo
Function 02H (decimal 2): Character Output
Function 03H (decimal 3): Auxiliary Input
Function 04H (decimal 4): Auxiliary Output
Function 05H (decimal 5): Printer Output
Function 06H (decimal 6): Direct Console Input/Output
Function 07H (decimal 7): Direct Console Input Without Echo
Function 08H (decimal 8): Console Input Without Echo
Function 09H (decimal 9): String Output
Function 0AH (decimal 10): Buffered Keyboard Input
Function 0BH (decimal 11): Check Keyboard Status
Function 0CH (decimal 12): Flush Keyboard Buffer, Read Keyboard
Function 0DH (decimal 13): Flush Disk Buffers
Function 0EH (decimal 14): Select Disk Drive
Function 0FH (decimal 15): Open File
Function 10H (decimal 16): Close File
Function 11H (decimal 17): Find First Matching Directory Entry
Function 12H (decimal 18): Find Next Matching Directory Entry
Function 13H (decimal 19): Delete File
Function 14H (decimal 20): Sequential Read
Function 15H (decimal 21): Sequential Write
Function 16H (decimal 22): Create File
Function 17H (decimal 23): Rename File
Function 19H (decimal 25): Get Current Disk
Function 1AH (decimal 26): Set Disk Transfer Area
Function 1BH (decimal 27): Get Default Drive Information
Function 1CH (decimal 28): Get Specified Drive Information
Function 21H (decimal 33): Read Random Record
Function 22H (decimal 34): Write Random Record
Function 23H (decimal 35): Get File Size
Function 24H (decimal 36): Set FCB Random Record Field
Function 25H (decimal 37): Set Interrupt Vector
Function 26H (decimal 38): Create New Program Segment Prefix
Function 27H (decimal 39): Read Random Records
Function 28H (decimal 40): Write Random Records
Function 29H (decimal 41): Parse Filename
Function 2AH (decimal 42): Get Date
Function 2BH (decimal 43): Set Date
Function 2CH (decimal 44): Get Time
Function 2DH (decimal 45): Set Time
Function 2EH (decimal 46): Set Verify Flag
The File Control Block
FCB Fields
Extended FCB Fields
An Example
The next three chapters describe the DOS functions accessed through
interrupt 21H. DOS version 1 had 42 interrupt 21H functions. This variety
of functions was strongly rooted in the 8-bit microcomputer tradition
typified by the CP/M operating system, whose services many of the DOS
functions resembled.
DOS version 1 was adequate for diskette-based microcomputers with
keyboards and video displays, but the advent of high-capacity fixed disks
and a wider variety of diskette formats called for a new set of
sophisticated disk file-management functions. These were supplied in DOS
version 2, and roughly patterned after the disk file-management services
used in the UNIX operating system. In version 3, DOS continued to evolve,
but offered only a few new functions, primarily in support of new hardware
such as the PC/AT, networks, and the PS/2s.
Although some interrupt 21H functions introduced in later versions of DOS
provide services similar to those in earlier versions, all version 1
functions continue to be supported in later versions. When you have a
choice between two similar functions, you should, in general, use the
higher-numbered, more recent function. We'll point out why as we go along.
Interrupt 21H Functions: DOS Version 1
All DOS function calls are invoked by interrupt 21H (decimal 33).
Individual functions are selected by placing the appropriate function
number in the AH register.
The interrupt 21H function calls in DOS version 1 are organized into the
logical groups shown in Figure 16-1. In an effort to make this figure as
clear as possible, we have organized and described these function calls in
a slightly different manner than does the DOS technical reference manual.
Figure 16-2 lists the individual function calls.
Function
Hex Dec Group
──────────────────────────────────────────────────────────────────────────
00H 0 Nondevice function
01H─0CH 1─12 Character device I/O
0DH─24H 13─36 File management
25H─26H 37─38 More nondevice functions
27H─29H, 2EH 39─41, 46 More file management
2AH─2DH 42─45 More nondevice functions
──────────────────────────────────────────────────────────────────────────
Figure 16-1. The logical groups of DOS version 1 function calls.
╓┌─┌──────────────┌──────────────┌───────────────────────────────────────────╖
Function
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
00H 0 Terminate
01H 1 Character Input with Echo
02H 2 Character Output
03H 3 Auxiliary Input
04H 4 Auxiliary Output
05H 5 Printer Output
06H 6 Direct Character Input/Output
07H 7 Direct Character Input Without Echo
08H 8 Character Input Without Echo
09H 9 String Output
0AH 10 Buffered Keyboard Input
0BH 11 Check Keyboard Status
0CH 12 Flush Keyboard Buffer, Read Keyboard
0DH 13 Flush Disk Buffers
0EH 14 Select Disk Drive
0FH 15 Open File
10H 16 Close File
11H 17 Find First Matching Directory Entry
Function
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
11H 17 Find First Matching Directory Entry
12H 18 Find Next Matching Directory Entry
13H 19 Delete File
14H 20 Sequential Read
15H 21 Sequential Write
16H 22 Create File
17H 23 Rename File
19H 25 Get Current Disk
1AH 26 Set Disk Transfer Area
1BH 27 Get Default Drive Information
1CH 28 Get Specified Drive Information
21H 33 Read Random Record
22H 34 Write Random Record
23H 35 Get File Size
24H 36 Set FCB Random Record Field
25H 37 Set Interrupt Vector
26H 38 Create New Program Segment Prefix
27H 39 Read Random Records
Function
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
27H 39 Read Random Records
28H 40 Write Random Records
29H 41 Parse Filename
2AH 42 Get Date
2BH 43 Set Date
2CH 44 Get Time
2DH 45 Set Time
2EH 46 Set Verify Flag
──────────────────────────────────────────────────────────────────────────
Figure 16-2. DOS version 1 functions available through interrupt 21H.
The design and organization of a few of these functions, particularly
numbers 01H through 0CH, are screwball──to put it mildly. They are this
way for historical reasons. Many details of DOS, and especially the
details of DOS function calls, were designed to closely mimic the services
provided by CP/M. This was an important and deliberate choice, made to
make it much easier for 8-bit CP/M software to be converted to the 16-bit
IBM PC and DOS. Although the creation of DOS provided a timely opportunity
to break with and clean up the mistakes of the past, the real departure
from the 8-bit tradition came with DOS version 2, as you will see in
Chapter 17.
The following pages describe the 42 original DOS function calls,
universally used in all versions of DOS.
Function 00H (decimal 0): Terminate
Function 00H (decimal 0) ends a program and passes control back to DOS. It
is functionally identical to DOS interrupt 20H, discussed on page 299.
Either can be used interchangeably to exit a program.
DOS versions 2.0 and later provide an enhanced terminate service through
function 4CH, which leaves a return code (an error code) in register AL
when a program ends. DOS batch files can act on the return codes with the
DOS subcommand ERRORLEVEL. Use function 4CH instead of function 00H if
you want to use a return code to record errors that occur when a program
ends. (See page 377.)
Like DOS interrupt 20H, function 00H does not close files opened with
functions 0FH or 16H. To ensure that the proper length of such files is
recorded in the file directory, use function 10H to close them before
calling function 00H. Also, as with interrupt 20H, you must be sure the
PSP segment address is in the CS register before exiting.
Function 01H (decimal 1): Character Input with Echo
Function 01H (decimal 1) waits for character input from the standard input
device and returns it in the AL register when available. This function
should be compared with the other keyboard function calls, particularly
functions 06H, 07H, and 08H.
──────────────────────────────────────────────────────────────────────────
NOTE:
In DOS version 1, the standard input device is always the keyboard; the
standard output device is always the video screen. In later DOS
versions, however, standard input and output can be redirected to other
devices such as files. DOS processes characters from the standard input
device without distinguishing whether the actual input source is the
keyboard or a stream of characters redirected from a file.
──────────────────────────────────────────────────────────────────────────
Here is how function 01H works: Keystrokes that result in an ASCII
character are returned as 1 byte in AL and immediately reported by this
function. The keystrokes that result in something other than an ASCII
character (see page 135) generate 2 bytes, which must be obtained through
two consecutive calls to this function.
The usual way to use this function is to test whether it returns 00H in
AL. If AL is not 00H, you have an ASCII character. If AL = 00H, you have a
non-ASCII keystroke (which should be recorded), and this function should
be repeated immediately to get the pseudo-scan code that represents the
special key action. (See page 135 for a list of the actions, codes, and
their meanings.) As with all the DOS keyboard input services, the scan
code for ASCII characters is not available, even if the corresponding ROM
BIOS keyboard services make it available. (See page 135.)
The various DOS keyboard service functions are distinguished primarily by
three criteria: whether they wait for input (or report no input when none
is available); whether they echo input onto the display screen; and
whether the standard break-key operation is active for that service.
Function 01H performs all three operations: It waits for input, echoes
input to the screen, and lets DOS execute interrupt 23H if Ctrl-C is
pressed.
Remember, function 01H always waits for the user to press a key before it
returns to a program. If you don't want to wait, either use function
0BH──before you call function 01H──to test whether a key was pressed, or
use function 06H. Also, see functions 08H and 0CH for related services.
Function 02H (decimal 2): Character Output
Function 02H (decimal 2) copies a single ASCII character from register DL
to the standard output device. In DOS version 1, the standard output
device is always the video screen; in later DOS versions, output can also
be redirected to a file.
In general, this function treats the ASCII control characters, such as
backspace or carriage return, as commands. In the case of the backspace
character, the display screen cursor is moved backward one column without
erasing the previous character.
Function 03H (decimal 3): Auxiliary Input
Function 03H (decimal 3) reads one character into AL from AUX the standard
auxiliary device. The default auxiliary device is COM1, the first RS-232
serial communications port. You can, however, use the DOS MODE command to
assign other devices, such as COM2, to the auxiliary device.
──────────────────────────────────────────────────────────────────────────
NOTE:
This function waits for input. It does not report status information
about the many miseries that a serial port can suffer. If you want to
know the status of the serial port, use the ROM BIOS communications-port
services.
──────────────────────────────────────────────────────────────────────────
Function 04H (decimal 4): Auxiliary Output
Function 04H (decimal 4) writes one character from register DL to the
standard auxiliary device. See the remarks under function 03H.
Function 05H (decimal 5): Printer Output
Function 05H (decimal 5) writes 1 byte from DL to the standard printer
device, which is normally known as PRN: or LPT1: (although printer output
can be redirected with the DOS MODE command to other devices). The default
standard printer is always the first parallel printer, even if a serial
port is used for printer output.
Function 06H (decimal 6): Direct Console Input/Output
Function 06H (decimal 6) is a complex function that combines the
operations of keyboard input and display output into one untidy package.
As with everything else in DOS versions 2.0 and later, the I/O is not
connected to the keyboard and display, but rather to the standard input
and output devices (which default to the keyboard and display).
Here is how this function works: The AL register is used for input and the
DL register for output. If you call function 06H with DL = FFH (decimal
255), the function performs input:
■ If a key was pressed, function 06H returns the corresponding ASCII code
in AL and clears the zero flag.
■ If no key was pressed, function 06H sets the zero flag.
If you call function 06H with any other value in DL, the function performs
output: The character in DL is copied to the standard output device.
Function 06H does not wait for keyboard input, and it does not echo input
to the display screen. In addition, function 06H does not interpret Ctrl-C
as a keyboard break; instead, it returns the value 03H (the ASCII value of
Ctrl-C) in AL.
Compare this function with functions 01H, 07H, and 08H. See function
0CH for a variation of this service.
Function 07H (decimal 7): Direct Console Input Without Echo
Function 07H (decimal 7) waits for character input from the standard input
device and returns it in the AL register when available. It does not echo
input to the display screen, and it does not recognize Ctrl-C as a
keyboard break character.
Function 07H works in the same way as function 01H: ASCII character key
actions are returned as single bytes in AL and are immediately reported by
this function. The non-ASCII function keystrokes (see page 135) generate
2 bytes, which must be obtained through two consecutive calls to function
07H.
Compare this function with functions 01H, 06H, and 08H. If you want to
use this function but don't want to wait when input is not ready, see
function 0BH, which reports whether or not input is ready. See function
0CH for a variation of this function.
Function 08H (decimal 8): Console Input Without Echo
Function 08H (decimal 8) waits for input, does not echo, and breaks on a
Ctrl-C. It is identical to function 01H, except it does not echo the input
to the display screen (or standard output device).
See the discussion under function 01H for a description of this function.
Compare this function with functions 01H, 06H, and 07H. If you want to
use this function but don't want to wait when input is not ready, see
function 0BH, which reports whether or not input is ready. See function
0CH for a variation of this function.
Function 09H (decimal 9): String Output
Function 09H (decimal 9) sends a string of characters to the standard
output device (which defaults to the display screen). The register pair
DS:DX provides the address of the string. A $ character, ASCII 24H
(decimal 36), marks the end of the string.
Although this function can be far more convenient than the byte-by-byte
display services (functions 02H and 06H), it is flawed by the use of a
real, displayable character, $, as its string delimiter. This is not a
recent mistake; it's another by-product of CP/M compatibility. You should
never use this function with programs that output dollar signs.
Function 0AH (decimal 10): Buffered Keyboard Input
Function 0AH (decimal 10) puts the power of the DOS editing keys to work
in your programs. The function gets a complete string of input, which is
presented to your programs whole, rather than character by character. If
you assume that the input is actually from live keyboard action and is not
redirected elsewhere, the full use of the DOS editing keys is available to
the person who is typing the input string. When the Enter key is pressed
(or a carriage return, ASCII 0DH (decimal 13), is encountered in the input
file), the input operation is complete and the entire string is presented
to your program.
This function provides many advantages, particularly to those programs
needing complete, coherent strings of keyboard input, rather than
byte-by-byte input. The two foremost benefits are that you are spared the
effort of writing detailed input-handling code, and your programs' users
are given a familiar set of input editing tools: the DOS editing
conventions.
To use this function, you must provide DOS with an input buffer area where
the input string will be built. The register pair DS:DX points to this
buffer when you call the function. The first 3 bytes of this buffer have
specific purposes:
■ The first byte indicates the working size of the buffer (the number of
bytes that DOS can use for input).
■ The second byte is updated by DOS to indicate the actual number of
bytes input.
■ The third byte is the beginning of the input string, which consists
entirely of ASCII characters. The end of the input string is signaled
by the carriage-return character, ASCII 0DH. Although the carriage
return is placed in the buffer, it is not included in the character
count that DOS returns in the second byte.
By these rules, the longest buffer you can give DOS is 255 working bytes,
and the longest string that DOS can return is 1 byte less than the working
length. Because the first 2 bytes of the buffer are used for status
information, the actual working size of the buffer is 2 bytes less than
the buffer's overall size. This may explain some of the mysteries of the
input conventions in both DOS and BASIC.
If input continues beyond what DOS can place in the buffer (which is 1
byte short of its working length), then DOS will discard any further
input, beeping all the while, until a carriage return is encountered.
See function 0CH for a variation of this function.
Function 0BH (decimal 11): Check Keyboard Status
Function 0BH (decimal 11) reports whether input is ready from the keyboard
(or standard input device). If a character is ready, AL = FFH (decimal
255). If no input is ready, AL = 00H.
DOS checks for Ctrl-C when you execute function 0BH, so a loop that
contains a call to this function can be interrupted by a keyboard break.
Function 0CH (decimal 12): Flush Keyboard Buffer, Read Keyboard
Function 0CH (decimal 12) clears the keyboard buffer in RAM and then
invokes one of five DOS functions: function 01H, 06H, 07H, 08H, or 0AH.
The AL register is used to select which of these functions will be
performed after the keyboard buffer is flushed. With the keyboard buffer
clear of extraneous characters, function 0CH forces the system to wait for
new input before it acts on the invoked function.
Because function 06H is supported, the follow-up service need not be
keyboard input: It can be display output.
Function 0DH (decimal 13): Flush Disk Buffers
Function 0DH (decimal 13) flushes (writes to disk) all internal DOS file
buffers. However, this function does not update directory entries or close
any open files. To ensure that the proper length of a changed file is
recorded in the file directory, use the close-file functions 10H or 3EH.
Function 0EH (decimal 14): Select Disk Drive
Function 0EH (decimal 14) selects a new current default drive. It also
reports the number of drives installed. The drive is specified in DL, with
00H indicating drive A, 01H drive B, and so on. The number of drives is
reported in AL.
Keep a few things in mind when using this function:
■ The drive IDs used by DOS are consecutively numbered.
■ If only one physical diskette drive exists, DOS will simulate a second
drive, drive number 1 (drive B). Thus the first fixed-disk drive is
always drive number 2, corresponding to drive letter C.
■ If you use the value in AL to determine the number of drives in your
system, beware: In DOS versions 3.0 and later, the minimum value
returned by this function is 05H.
Function 0FH (decimal 15): Open File
Function 0FH (decimal 15) opens a file using a file control block (FCB).
An FCB is a data structure used by DOS to track input and output for a
particular file. Among other things, an FCB contains a file's name and
disk drive number. (See page 341 in this chapter for details on the
contents of FCBs.)
──────────────────────────────────────────────────────────────────────────
NOTE:
Function 0FH is one of 15 DOS functions that use an FCB to track file
input and output. You should avoid the DOS functions that use FCBs.
These functions were made obsolete by the more powerful handle-based
file functions introduced in DOS version 2.0. Furthermore, unlike
handle-based functions, FCB-based functions are not supported in OS/2
protected mode. Use the FCB-based functions only if compatibility with
DOS version 1 is important.
──────────────────────────────────────────────────────────────────────────
To use an FCB to open a file, you must reserve memory for the FCB and
place the file's name and disk drive number in the proper fields in the
data structure. Then call function 0FH with the segmented address of
the FCB in the register pair DS:DX. DOS attempts to open the file, using
the drive and filename you specified in the FCB. If the file is opened, AL
= 00H; if the file cannot be opened, AL = FFH.
If the file is opened successfully, DOS initializes several fields in the
FCB, including the drive number field (with a value of 1 for drive A, 2
for drive B, and so on), the date and time fields, and the logical
record-size field (which is set to 128). You can either use this record
size or change it, depending on your application.
Function 10H (decimal 16): Close File
Function 10H (decimal 16) closes a file and updates the file's directory
entry. Call this function with the segmented address of the file's FCB in
DS:DX. DOS returns AL = 00H if the function successfully closed the file
or AL = FFH if an error occurred.
It is good practice to use function 10H to explicitly close all files you
opened with function 0FH or 16H. This ensures that the file contents are
updated from DOS internal file buffers and that the corresponding
directory entries are current.
Function 11H (decimal 17): Find First Matching Directory Entry
Function 11H (decimal 17) searches the current directory for a specified
directory entry. The name you specify to function 11H can contain the
wildcard characters ? and *. The ? character matches any single ASCII
character (as a wild card in a poker game matches any other card) and the
* matches any string of characters, so DOS can match a name that contains
one or more wildcard characters with several different directory entries.
If more than one directory entry matches, DOS reports only the first
match. You must then use function 12H to continue the search for
subsequent matching directory entries.
Before you call function 11H, store the address of an FCB in DS:DX. The
filename field of this FCB must contain the name you want DOS to search
for. DOS reports a successful match by returning AL = 00H; if no directory
entries match the specified name, DOS returns AL = FFH. When DOS finds a
matching directory entry, it creates a new FCB in the current disk
transfer area (DTA) and copies the matching name from the directory entry
into the new FCB's filename field.
If the FCB has an FCB extension (see page 344), then you can specify the
attributes of the file that you wish to search for. If you specify any
combination of the hidden, system, or directory attribute bits, the search
matches normal files and also any files with those attributes. If you
specify the volume-label attribute, this function searches only for a
directory entry with that attribute. With DOS versions prior to 2.0,
neither the directory nor the volume-label attributes can be used in the
file search operation. The archive and read-only attributes cannot be used
as search criteria in any DOS release.
Function 12H (decimal 18): Find Next Matching Directory Entry
Function 12H (decimal 18) finds the next of a series of files, following
the set-up preparation performed by function 11H. As with function 11H,
you must call function 12H with the address of an FCB in DS:DX. For
function 12H, the FCB should be the same as the one you used for a
successful call to function 11H.
DOS reports a successful match by returning AL = 00H; if no match exists,
DOS returns AL = FFH. This lets you combine functions 11H and 12H to
perform a complete directory search by using the following logic:
initialize FCB
call function 11H
WHILE AL = 0
use current contents of DTA
call function 12H
Function 13H (decimal 19): Delete File
Function 13H (decimal 19) deletes all files that match the name specified
in the FCB pointed to by the register pair DS:DX. The filename in the FCB
can contain wildcard characters so that multiple files can be deleted with
a single call to function 13H. The function returns AL = 00H if the
operation is a success and all matching file directory entries are
deleted. AL = FFH if the operation is a failure, meaning that no directory
entries matched.
Function 14H (decimal 20): Sequential Read
Function 14H (decimal 20) reads records sequentially from a file. To use
this function, open a file using function 0FH. Then initialize the
current-record and record-size fields of the FCB. For example, to read the
first 256-byte record from a file, set the record-size field to 100H
(decimal 256) and the current-record field to 00H before you call function
14H.
After the FCB is initialized, you can call function 14H once for each
record you want to read. Each time you call function 14H, pass the address
of the file's FCB in DS:DX. DOS reads the next record from the file and
stores the data in the current disk transfer area (DTA). At the same time,
DOS tracks its current position in the file by updating the current-block
and current-record fields in the FCB.
AL reports the results of the read. Complete success is signaled when AL =
00H; AL = 01H signals an end-of-file, indicating that no data was read; AL
= 02H signals that data could have been read, but wasn't, because
insufficient memory remained in the DTA segment; AL = 03H signals an
end-of-file with a partial record read (the record is padded with zero
bytes).
Function 15H (decimal 21): Sequential Write
Function 15H (decimal 21) writes a sequential record and is the companion
to function 14H. As with function 14H, DOS tracks its current position in
the file by updating the FCB whose address you pass in DS:DX. DOS copies
the data from the current DTA to the file and reports the status of the
write operation in AL.
If AL = 00H, the write operation was a success. If AL = 01H, the disk was
full and the record was not written. If AL = 02H, the amount of memory
remaining in the DTA's segment was less than the record size, so DOS
aborted the write operation.
It's important to note that data is logically written by this function,
but not necessarily physically written. DOS buffers output data until it
has a complete disk sector to write──only then does DOS actually transfer
the data to the disk.
Function 16H (decimal 22): Create File
Function 16H (decimal 22) opens an empty file with a specified name. If
the file exists in the current directory, function 16H truncates it to
zero length. If the file does not exist, function 16H creates a directory
entry for the new file. As with the other FCB-based file functions, you
call function 16H with DS:DX pointing to an FCB containing the name of the
file. The function returns AL = 00H to indicate successful operation. If
AL = FFH, the function failed, possibly because the filename you specified
in the FCB is not valid.
If you want to avoid inadvertently losing the contents of an existing
file, you should determine if the file already exists by calling function
11H before you use function 16H.
Function 17H (decimal 23): Rename File
Function 17H (decimal 23) renames files or subdirectories in a modified
FCB pointed to by DS:DX. For the rename operation, the FCB has a special
format. The drive and original name are located in their usual positions,
but the new name and extension are placed at offsets 11H through 1BH in
the FCB.
AL = 00H signals complete success, and AL = FFH signals that the original
name wasn't found or the new name is already in use.
If the new name contains wildcard characters (?), they are interpreted as
ditto-from-old-name, and the characters in the original name that
correspond to the positions of the wildcard characters are not changed.
Function 19H (decimal 25): Get Current Disk
Function 19H (decimal 25) reports the current drive number in AL, using
the standard numeric code of drive A = 00H, drive B = 01H, and so forth.
Function 1AH (decimal 26): Set Disk Transfer Area
Function 1AH (decimal 26) establishes the disk transfer area that DOS will
use for file I/O. The location of the DTA is specified by the register
pair DS:DX. Normally, you should specify a DTA address before you use any
of the interrupt 21H functions that access a DTA. If you do not, DOS uses
the default 128-byte DTA at offset 80H in the program segment prefix.
Function 1BH (decimal 27): Get Default Drive Information
Function 1BH (decimal 27) returns important information about the disk in
the current drive. Function 1CH performs the identical service for any
drive. Function 36H performs a nearly identical service. (See Chapter
17.)
The following information is returned through this function call:
■ AL contains the number of sectors per cluster.
■ CX contains the size, in bytes, of the disk sectors (512 bytes for all
standard PC formats).
■ DX contains the total number of clusters on the disk.
■ DS:BX points to a byte in DOS's work area containing the DOS media
descriptor. Prior to DOS version 2.0, the DS:BX register pair pointed
to the complete disk FAT (which could be guaranteed to be in memory,
complete), whose first byte would be the ID byte. In later DOS
versions, DS:BX points only to the single ID byte.
Beware: Function 1BH uses the DS register to return the address of the
media descriptor byte. If your program relies on the DS register to point
to data──and most high-level and assembly-language programs do──then you
should be careful to preserve the contents of the DS register while you
call function 1BH.
The following example shows how to do this:
push ds ; preserve DS
mov ah,1Bh
int 21h ; call function 1BH; DS:BX -> media descriptor
mov ah,[bx] ; get a copy of the media descriptor byte
pop ds ; restore DS
Function 1CH (decimal 28): Get Specified Drive Information
Function 1CH works in the same way as function 1BH except that it reports
on any drive, not only the current drive. Before calling this function,
set DL to the drive ID number, where 0 = the current drive, 1 = drive A, 2
= drive B, and so forth.
Function 21H (decimal 33): Read Random Record
Function 21H (decimal 33) reads one record from a random location in a
file. To use this function, open a file with an FCB. Then store the record
number of the record you want to read in the random-record field of the
FCB. When you call function 21H with DS:DX pointing to the FCB, DOS reads
the specified record into the DTA.
AL is set with the same codes as it is for a sequential read: AL = 00H
indicates a successful read; AL = 01H indicates end-of-file, with no more
data available; AL = 02H means that insufficient space exists in the DTA
segment; and AL = 03H indicates an end-of-file, with a partial data record
available.
Contrast this function with function 27H, which can read more than one
random record at a time, or with function 14H, which reads sequential
records. See function 24H for more on setting the random-record field.
Function 22H (decimal 34): Write Random Record
Function 22H (decimal 34) writes one record to a random location in a
file. As with function 21H, you must initialize the random-record field in
the file's FCB and then call this function with DS:DX pointing to the FCB.
DOS then writes data from the DTA to the file at the position specified in
the FCB.
AL is set with the same codes used for a sequential write: AL = 00H
indicates a successful write; AL = 01H means the disk is full; AL = 02H
indicates insufficient space in the DTA segment.
Contrast this function with function 28H, which can write more than one
random record, or with function 15H, which writes sequential records. See
function 24H for more on setting the random-record field.
Function 23H (decimal 35): Get File Size
Function 23H (decimal 35) reports the size of a file in terms of the
number of records in the file. DS:DX points to the FCB of the file you
want to know about. Before calling the function, the FCB should be left
unopened and the record-size field in the FCB filled in. If you set the
record size to 1, the file size is reported in bytes, which is most likely
what you want.
If the operation is successful, AL = 00H and the file size is inserted
into the FCB. If the file is not found, AL = FFH.
Function 24H (decimal 36): Set FCB Random Record Field
Function 24H (decimal 36) sets the random-record field to correspond to
the current sequential block and record fields in an FCB. This facilitates
switching from sequential to random I/O. The DS:DX registers point to the
FCB of an open file.
Function 25H (decimal 37): Set Interrupt Vector
Function 25H (decimal 37) sets an interrupt vector. Before you call
function 25H, place the segmented address of an interrupt handler in DS:DX
and an interrupt number in AL. DOS stores the segment and offset of your
interrupt handler in the proper interrupt vector.
When updating an interrupt vector, you should use function 25H instead of
simply computing the address of the vector and updating it directly. Not
only is it simpler to call this function than to do the work yourself, but
this function gives the operating system the chance to detect when an
important interrupt vector is modified.
To examine the contents of an interrupt vector, see function 35H in the
next chapter.
Function 26H (decimal 38): Create New Program Segment Prefix
Function 26H is used within a program to prepare for loading and executing
another subprogram, or overlay. When you call function 26H, DX must
contain the paragraph address of the start of the memory area where you
want DOS to build the new PSP. DOS builds a new PSP at the location you
specify. You can then load an executable program from a file into the
memory above the new PSP and transfer control to it.
──────────────────────────────────────────────────────────────────────────
NOTE:
Function 26H is obsolete. You should use function 4BH (Chapter 17) to
load and execute a new program from within another executing program.
──────────────────────────────────────────────────────────────────────────
Function 27H (decimal 39): Read Random Records
Unlike function 21H, function 27H reads one or more records, starting at a
random file location. DS:DX points to the FCB for the file to be read and
the random-record number is then taken from this FCB. CX contains the
number of records desired, which should be more than 0.
The return codes are the same as they are for function 21H: AL = 00H means
the read was successful; AL = 01H indicates end-of-file, with no more data
(if the records were read, the last record is complete); AL = 02H
indicates that the DTA segment was too small; and AL = 03H indicates the
end-of-file, where the last record read is incomplete and padded with
zeros.
No matter what the result, CX is set to the number of records read,
including any partial record, and the random-record field in the FCB is
set to the next sequential record.
Contrast this with function 21H, which reads only one record.
Function 28H (decimal 40): Write Random Records
Unlike function 22H, function 28H (decimal 40) writes one or more records,
starting at a specified random file location. DS:DX points to the FCB for
the file to be written, and the random record number is then taken from
this FCB. CX contains the number of records desired and in this case, CX
can be 00H. CX = 00H signals DOS to adjust the file's length to the
position of the specified random record. This adjustment makes it easier
for a program to manage random files: If you have logically deleted
records at the end of a file, this service allows you to truncate the file
at that point by setting the file's length in CX, thereby freeing disk
space.
The return codes are the same as they are for function 22H: AL = 00H
indicates a successful write; AL = 01H means that no more disk space is
available; and AL = 02H indicates that the DTA segment was too small. No
matter what the result, CX is always set to the number of records written.
Contrast this function with function 22H, which writes only one random
record.
Function 29H (decimal 41): Parse Filename
Function 29H (decimal 41) parses a string for a filename with the form
DRIVE:FILENAME.EXT. Call this function with DS:SI pointing to a text
string and ES:DI pointing to the drive-identifier byte in an unopened FCB.
Function 29H attempts to extract the drive and filename information from
the string, and to use it to initialize the drive and name fields of the
FCB. If the function executes successfully, it returns AL = 00H if the
string contains no wildcard characters or AL = 01H if the string contains
at least one * or ? wildcard character. If the drive letter specifies an
invalid drive, the function returns AL = FFH.
Function 29H also updates DS:SI to point to the byte after the end of the
filename in the string. This facilitates processing a string that contains
multiple filenames. Also, if the parsing was unsuccessful, the FCB
contains a blank filename.
Function 29H lets you control four different aspects of the filename
parsing. When you call the function, the 4 low-order bits of the value in
AL specify how function 29H parses the string:
■ If bit 0 is set, the function scans past separator characters (for
example, leading blank spaces) to find the file specification. If bit 0
is 0, the scan operation is not performed, and the file specification
is expected to start in the first byte of the string.
■ If bit 1 is set, then the drive byte in the FCB will be set only if it
is specified in the file specification being scanned. This allows the
FCB to specify a default drive.
■ If bit 2 is set, the filename in the FCB is changed only if a valid
filename is found in the string. This lets the FCB specify a default
filename, which can be overridden by the filename in the string.
■ If bit 3 is set, the filename extension in the FCB is changed only if a
valid extension is found in the file specification. This allows the FCB
to specify a default extension.
──────────────────────────────────────────────────────────────────────────
NOTE:
Although this service can be handy, it is intended for use only with
FCB-based file functions. You don't need this function if you rely on
the handle-based file functions described in Chapter 17.
──────────────────────────────────────────────────────────────────────────
Function 2AH (decimal 42): Get Date
Function 2AH (decimal 42) reports DOS's record of the current date. The
date is reported in CX and DX. DH contains the month number (1 through
12); DL contains the day of the month (1 through 28, 29, 30, or 31, as
appropriate); and CX contains the year (1980 through 2099).
This function reports the day of the week by returning a value from 0
through 6, which signifies Sunday through Saturday, in register AL. This
day-of-the-week feature is somewhat of an orphan. It has been present in
DOS since version 1.1, but was not even mentioned until DOS version 2.0.
In both the 2.0 and 2.1 manuals, it is incorrectly described as a part of
the get-time function and not as part of the get-date function. Starting
with DOS 3.0, the manual tells it as it is. Turn to the example on page
346 to see how this function can be used.
Function 2BH (decimal 43): Set Date
Function 2BH (decimal 43) sets DOS's record of the current date, using the
same registers as function 2AH. The date is set in CX and DX. DH contains
the month number (1 through 12); DL contains the day of the month (1
through 28, 29, 30, or 31, as appropriate); CX contains the year (1980
through 2099). This function returns AL = 00H if the date is successfully
updated, or AL = FFH if you specified an invalid date.
Starting in DOS version 3.3, this function also updates the real-time
clock/calendar in the PC/AT and PS/2. In earlier versions, you must still
use ROM BIOS interrupt 1AH services to change the real-time clock date.
Function 2CH (decimal 44): Get Time
Function 2CH (decimal 44) reports the time of day. The time is calculated
from the ROM BIOS timer-tick count. (See page 59.) DOS responds to the
ROM BIOS's midnight-passed signal and updates the date every 24 hours.
The timer-tick count is converted into a meaningful time and placed in
registers CX and DX. CH contains the hour (0 through 23, on a 24-hour
clock); CL contains the minutes (0 through 59); DH contains the seconds (0
through 59); and DL contains hundredths of seconds (0 through 99). This
function returns AL = 00H if the time is successfully updated, or AL = FFH
if you specified an invalid time.
The IBM PC timer ticks 18.2 times per second, so the time of day reported
by DOS is only as accurate as the timer tick──roughly 5.4 hundredths of a
second. Nevertheless, even with this relatively low accuracy, you can use
DOS function 2CH to measure time intervals in many applications.
Function 2DH (decimal 45): Set Time
Function 2DH (decimal 45) sets the time of day. The time is specified in
registers CX and DX. CH contains the hour (0 through 23, on a 24-hour
clock); CL contains the minutes (0 through 59); DH contains the seconds (0
through 59); DL contains hundredths of seconds (0 through 99).
Starting in DOS version 3.3, this function also updates the real-time
clock in the PC/AT and PS/2. In earlier versions, you must still use ROM
BIOS interrupt 1AH services to change the real-time clock time.
Function 2EH (decimal 46): Set Verify Flag
Function 2EH (decimal 46) controls verification of disk-write operations.
Call this function with AL = 01H to set DOS's internal verify flag and
enable verification; call it with AL = 00H to turn off the flag and
verification. Also, in DOS versions 1 and 2, you must zero DL before you
call function 2EH.
The disk-verify operation requires the disk controller to perform a
cyclical redundancy check (CRC) each time it writes data to the disk. This
process involves reading the data just written, which significantly
decreases the speed of disk writes.
With DOS versions 2.0 and later, function 54H can be used to report the
current setting of the verify flag. (See page 379.)
The File Control Block
As mentioned several times in this chapter, file control blocks and the
DOS functions that use them are obsolete. We recommend that you use the
handle-based file I/O functions introduced in DOS version 2.0 and
described in the next chapter. Usually, the only reason to concern
yourself with FCBs is when compatibility with DOS version 1 is an issue.
With that in mind, let's take a look at the structure of the FCB. The
usual FCB is a 37-byte data structure that contains a variety of
information DOS can use to control file input/output. (See Figure 16-3.)
A 44-byte, extended FCB is also used in some DOS functions: 7 extra bytes
are tacked onto the beginning of the usual FCB data structure. (See Figure
16-4.)
The situation with the FCB extension is more than a little peculiar. The
extension is used only when you work with the attribute field in a
directory entry in which read-only files, hidden files, system files,
volume labels, and subdirectories are identified. In general, you need to
use extended FCBs only if you are performing directory searches or
otherwise working with directory entries rather than the contents of
files. However, all FCB-based functions recognize the extended FCB format
if you should choose to use it.
Offset Field Width Description
──────────────────────────────────────────────────────────────────────────
00H 1 Drive identifier
01H 8 Filename
09H 3 File extension
0CH 2 Current-block number
0EH 2 Record size in bytes
10H 4 File size in bytes
14H 2 Date
16H 2 Time
18H 8 (Reserved)
20H 1☼ Current-record number
21H 4 Random-record number
──────────────────────────────────────────────────────────────────────────
Figure 16-3. Structure of a file control block.
Offset Field Width Description
──────────────────────────────────────────────────────────────────────────
00H 1 Extended FCB flag (always FFH)
01H 5 (Reserved)
06H 1 Attribute
07H 1 Drive identifier
08H 8 Filename
10H 3 File extension
13H 2 Current-block number
15H 2 Record size in bytes
17H 4 File size in bytes
1BH 2 Date
1DH 2 Time
1FH 8 (Reserved)
27H 1☼ Current-record number
28H 4 Random-record number
──────────────────────────────────────────────────────────────────────────
Figure 16-4. Structure of an extended file control block. The first three
fields distinguish this data structure from a normal FCB.
With two exceptions, all fields in an extended FCB are identical to those
in a normal FCB. Only the offsets are different: In an extended FCB, the
offset of a particular field is 7 bytes greater than the offset of the
same field in a normal FCB.
The following sections describe the fields in normal extended FCBs.
FCB Fields
Offset 00H. The first field in a normal (nonextended) FCB is the disk
drive identifier. Values for the drive identifier start at 1; a value of 1
indicates drive A, 2 indicates drive B, and so on. If this field contains
0 at the time an FCB is opened, DOS uses the current default drive and
updates this field with the corresponding drive identifier.
Offsets 01H and 09H. The two fields at offsets 01H and 09H contain an
8-byte name and a 3-byte extension. These fields are left-justified and
padded on the right with blanks. Following DOS convention, either upper-
or lowercase letters may be used. If the filename is a device name that
DOS recognizes, such as CON, AUX, COM1, COM2, LPT1, LPT2, PRN, or NUL, DOS
will use that device rather than a disk file.
──────────────────────────────────────────────────────────────────────────
NOTE:
This is a reasonably good place to point out that the FCB mechanism has
no provision for working with pathnames. Whenever you use FCBs, they
always apply to the current directory in any drive. For flexible use of
paths and subdirectories, see the new, extended functions in Chapter
17.
──────────────────────────────────────────────────────────────────────────
Offsets 0CH and 20H. For sequential file operations, the current-block and
current-record fields keep track of the location in the file. The use of
these fields is rather odd. Instead of using one integrated record number,
the record number is divided into a high and low portion, referred to as
the block number and record number. The record number is a 7-bit value, so
record numbers range from 0 through 127. Thus the first record in a file
is block 0, record 0; the 128th record is block 1, record 0.
Before you use the sequential read and write functions 14H and 15H, be
sure to initialize the current block and record fields to the desired
starting location in the file.
Offset 0EH. The record-size field contains a 2-byte value that specifies
the size, in bytes, of the logical records in the file. When DOS reads or
writes a record, the logical size of the record is the number of bytes
transferred between DOS's disk buffers and the DTA.
The same file data can be worked on using a variety of record sizes. When
a file is opened through functions 0FH or 16H, DOS sets the record size to
128 bytes by default. If you want another size, such as 1 for single-byte
operations, you must change the record-size field after the file is
opened.
Offset 10H. The file-size field at offset 10H indicates the file size in
bytes. The value is taken from a file's directory entry and is placed in
the FCB when DOS opens the file. For an output file, this field is changed
by DOS as the file grows. When the file is closed, the value is copied
from the FCB to the file's directory entry.
By changing this field, you can gain some last-minute control over the
size of an output file, but be careful when doing this. You can, for
example, truncate a file you have updated by decreasing the file-size
value in this field. Also, be careful not to use function 17H to rename an
open file: This function requires that you specify the file's new name in
the same part of the FCB used for the file size.
Offsets 14H and 16H. The 2-byte fields at offset 14H (date) and offset 16H
(time) record when a file was last updated. These fields use the same
format as the corresponding fields in a directory entry. (See Chapter 5.)
The initial values in these fields are copied from a file's directory
entry when the file is opened. They are subsequently updated each time you
write to the file. If the file was updated, DOS copies the values from the
FCB to the directory entry when the file is closed.
Offset 21H. The random-record field is used during random read and write
operations, just as the current record and block numbers are used during
sequential operations. This field is in the form of a 4-byte, 32-bit
integer. Records are numbered from 0, which makes it easy to calculate the
file offset to any record by multiplying the random-record number by the
record size. You must set this field before any random file operation. DOS
leaves it undisturbed.
Extended FCB Fields
An extended FCB has two additional fields not found in a normal FCB:
■ The first field of an extended FCB is a flag byte whose contents must
be FFH. DOS distinguishes between normal and extended FCBs by examining
this byte. (In a normal FCB, the first field is the disk-drive
specifier, which should never be FFH.)
■ Offset 06H in an extended FCB is a 1-byte field that consists of an
attribute byte whose bits signify file, volume label, and subdirectory
attributes. This byte's format is identical to the attribute byte in a
directory entry. (See Chapter 5.)
──────────────────────────────────────────────────────────────────────────
NOTE:
One rare situation in which you would use FCB-based functions instead of
handle-based functions is when you work with a disk's volume label. DOS
versions 2.0 and later do not provide any special services for
manipulating a volume label. You must use function 16H with an extended
FCB to create a volume label, function 17H to rename it, and function
13H to rename it.
──────────────────────────────────────────────────────────────────────────
An Example
For our assembly-language example in this section, we've chosen something
rather interesting. It's a routine used within the Norton Utility
programs, so you'll be seeing some actual production code.
The purpose of this routine is to calculate the day of the week for any
day within DOS's working range, which is stated to be from Tuesday,
January 1, 1980, through Thursday, December 31, 2099. Occasionally, it's
valuable for a program to be able to report the day of the week, either
for the current date or for any other date that may be in question. For
example, DOS keeps track of the date and time each file was last changed.
Because people often use this information to find out when they last
worked with a file, it can be handy to know the day of the week as well.
In fact, the day of the week is often more immediately meaningful than the
actual date.
Although several interesting and clever algorithms let you calculate the
day of the week, the actual work of writing a day-of-the-week program is
usually rather tedious. Beginning with version 1.10, DOS incorporated a
day-of-the-week calculation, which spared us the chore of writing our own.
DOS's routine is available only in a form that reports the current day of
the week, but that is no obstacle: We can temporarily change DOS's date to
the date we're interested in and then have DOS report the day of the week.
That is what the following assembly-language routine does for us.
Besides being slightly foxy, this routine is interesting because it
illustrates how three DOS function calls operate together to produce one
result. It also illustrates the minor intricacies involved in saving and
restoring things on the stack. As we will see here, stack use occasionally
has to be carefully orchestrated so that different values don't get in
each others' way.
This particular subroutine, named Weekday, is set up in the form needed
for use with the Microsoft C compiler. The routine is called with three
integer variables, which specify the month, day, and year we are
interested in. The routine returns the day of the week in the form of an
integer in the range of 0 through 6 (signifying Sunday through Saturday).
This conforms to the C language convention for arrays, providing an index
to an array of strings that give the names of the days. Therefore, we
could use this subroutine in this way:
DayName[ Weekday( month, day, year ) ]
It is important to note that this routine works blindly with the date,
checking neither for a valid date nor for the range of dates accepted by
DOS. Here is the subroutine:
_TEXT SEGMENT byte public 'CODE'
ASSUME cs:_TEXT
PUBLIC _Weekday
_Weekday PROC near
push bp ; establish stack addressing ..
mov bp,sp ; .. through BP
mov ah,2Ah ; get current date
int 21h
push cx ; save current date on the stack
push dx
mov cx,[bp+8] ; CX = year
mov dl,[bp+6] ; DL = day
mov dh,[bp+4] ; DH = month
mov ah,2Bh ; set the date specified
int 21h
mov ah,2Ah ; get the date back from DOS
int 21h ; (AL = day of the week)
pop dx ; restore the current date ..
pop cx ; .. in CX and DX
push ax ; save day of week on the stack
mov ah,2Bh ; set the current date
int 21h
pop ax ; AL = day of week
mov ah,0 ; AX = day of week
pop bp ; restore BP and return
ret
_Weekday ENDP
_TEXT ENDS
────────────────────────────────────────────────────────────────────────────
Chapter 17 DOS Functions: Versions 2.0 and Later
Enhancements in DOS Versions 2 and 3
Consistent Error Codes
ASCIIZ Strings
File Handles
Installable Device Drivers
Interrupt 21H Functions: DOS Versions 2.0 and Later
Function 2FH (decimal 47): Get DTA Address
Function 30H (decimal 48): Get DOS Version Number
Function 31H (decimal 49): Terminate and Stay Resident
Function 33H (decimal 51): Get/Set Ctrl-C Flag
Function 35H (decimal 53): Get Interrupt Vector
Function 36H (decimal 54): Get Disk Free Space
Function 38H (decimal 56): Get/Set Country-Dependent Information
Function 39H (decimal 57): Create Directory
Function 3AH (decimal 58): Remove Directory
Function 3BH (decimal 59): Change Current Directory
Function 3CH (decimal 60): Create File
Function 3DH (decimal 61): Open Handle
Function 3EH (decimal 62): Close Handle
Function 3FH (decimal 63): Read from File or Device
Function 40H (decimal 64): Write to File or Device
Function 41H (decimal 65): Delete File
Function 42H (decimal 66): Move File Pointer
Function 43H (decimal 67): Get/Set File Attributes
Function 44H (decimal 68): IOCTL──I/O Control for Devices
Function 45H (decimal 69): Duplicate Handle
Function 46H (decimal 70): Force Duplicate Handle
Function 47H (decimal 71): Get Current Directory
Function 48H (decimal 72): Allocate Memory Block
Function 49H (decimal 73): Free Memory Block
Function 4AH (decimal 74): Resize Memory Block
Function 4BH (decimal 75): EXEC──Load and Execute a Program
Function 4CH (decimal 76): Terminate with Return Code
Function 4DH (decimal 77): Get Return Code
Function 4EH (decimal 78): Find First Matching Directory Entry
Function 4FH (decimal 79): Find Next Matching Directory Entry
Function 54H (decimal 84): Get Verify Flag
Function 56H (decimal 86): Rename File
Function 57H (decimal 87): Get/Set File Date and Time
Function 58H (decimal 88): Get/Set Memory Allocation Strategy
Function 59H (decimal 89): Get Extended Error Information
Function 5AH (decimal 90): Create Temporary File
Function 5BH (decimal 91): Create New File
Function 5CH (decimal 92): Lock/Unlock File Region
Function 5EH (decimal 94): Network Machine Name and Printer Setup
Function 5FH (decimal 95): Network Redirection
Function 62H (decimal 98): Get PSP Address
Function 65H (decimal 101): Get Extended Country Information
Function 66H (decimal 102): Get/Set Global Code Page
Function 67H (decimal 103): Set Handle Count
Function 68H (decimal 104): Commit File
In this chapter we'll discuss the interrupt 21H functions introduced in
DOS versions 2.0 and later. These functions provide a wide range of
operating system services within a more sophisticated and flexible
framework than the original 42 functions we described in Chapter 16.
Almost every DOS upgrade has increased the number of services provided to
programmers. DOS 2.0 initiated the most dramatic changes: It added 33 new
functions to the existing 42; it changed the way you access file
information as a result of these new functions; and it made it possible to
adapt DOS to work with almost any hardware device through the use of
programs called installable device drivers. Before discussing the newer
DOS functions in detail, we'll briefly cover how some of these
enhancements can affect your programming practices.
Enhancements in DOS Versions 2 and 3
The services introduced with DOS versions 2 and 3 have three important new
features that directly affect the way you use the services:
■ Most of the functions return a set of consistent error codes in the AX
register.
■ All functions that use string input require a special string format
known as the ASCIIZ format──a string of ASCII characters terminated by
a single zero byte.
■ The newer DOS functions use a 16-bit number called a handle, instead of
an FCB, to identify the files and I/O devices that a program
communicates with.
We'll discuss each of these enhancements on the next few pages.
Consistent Error Codes
When you call an interrupt 21H function in DOS versions 2.0 and later, the
function returns an error status code in the AX register. These functions
also set the carry flag to signal that an error has occurred. You should
generally follow each call to these interrupt 21H functions with a test of
the carry flag; if the flag is set, the value in AX describes what caused
the error.
In DOS versions 3.0 and later, you can also use interrupt 21H, function
59H, to obtain extended error information from DOS. You can call function
59H after any interrupt 21H function reports an error; you can also use it
inside a critical-error (interrupt 24H) handler to determine the nature of
a DOS critical error. In both situations, function 59H returns an extended
error code and also suggests possible actions to alleviate the problem.
For a complete list of extended error codes and how to use them, see the
discussion of function 59H on page 381.
ASCIIZ Strings
Many interrupt 21H functions introduced in DOS versions 2 and 3 require
you to pass file and directory names in the form of ASCIIZ strings. An
ASCIIZ string is simply a string of ASCII characters terminated by a
single zero byte that marks the end of the string. For example, the ASCIIZ
representation of the pathname C:\COMMAND.COM would consist of the
following 15 hexadecimal bytes:
null
byte
│
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬▼─┐
Characters───────────►│C │: │\ │C │O │M │M │A │N │D │. │C │O │M │ │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
ASCII values (hex)───► 43 3A 5C 43 4F 4D 4D 41 4E 44 2E 43 4F 4D 00
The ASCIIZ string format is commonly used within the UNIX operating system
and the C programming language; it is only one of many new elements with a
C/UNIX flavor introduced in DOS version 2.0.
File Handles
The newer interrupt 21H functions in DOS versions 2.0 and later rely on
the notion of handles. Handles are 16-bit numbers that DOS uses to
identify open files. Handles can also identify other sources of input and
output for a program, including the keyboard and the video display.
(They're also another example of the UNIX influence: In UNIX, file handles
are called "file descriptors" but are used in essentially the same way as
they are in DOS.)
The use of handles allows DOS to be more flexible in its file management
services than it was with FCB-based file services. In particular,
capabilities such as file redirection and support for hierarchical
directories would have been very difficult to graft onto the fixed-format
FCB data structure. Furthermore, the use of handles actually simplifies
file management by making the mechanics of file input/output──parsing
filenames, keeping track of the current file position, and so on──the
responsibility of DOS instead of your programs.
DOS assigns a new handle number whenever you create or open a file. Five
standard handles, numbered 0 through 4, are automatically available to
every program. (See Figure 17-1.) Other handles, with higher handle
numbers, are issued by DOS as needed.
Handle Use Default Device
──────────────────────────────────────────────────────────────────────────
0 Standard input (normally keyboard input) CON
1 Standard output (normally screen output) CON
2 Standard error output (always to the CON
screen)
3 Standard auxiliary device (AUX device) AUX
4 Standard printer (LPT1 or PRN device) PRN
──────────────────────────────────────────────────────────────────────────
Figure 17-1. The five standard DOS handles.
──────────────────────────────────────────────────────────────────────────
NOTE:
Of the five standard DOS handles, only the first three are supported in
OS/2 protected mode. If you are programming with upward compatibility in
mind, you should avoid using handles 3 and 4 by default. Instead, open
the serial port and printer devices explicitly, as you would any other
file or input/output device.
──────────────────────────────────────────────────────────────────────────
DOS limits the use of handles in regard to how many files or devices your
program can open at one time:
■ DOS maintains an internal data structure that controls I/O for each
file or other input/output device associated with a handle. The default
size of this data structure allows for only 8 handles. Fortunately, the
FILES command in the CONFIG.SYS file lets you increase the number of
possible handles (99 in DOS versions prior to 3.0; 255 in versions 3.0
and later).
■ DOS uses a reserved area in each program's PSP to maintain a table of
handles associated with the program. This table has room for a maximum
of 20 handles. Thus, even if you specify FILES = 30 in your CONFIG.SYS
file, your programs will still be able to use only 20 handles at a
time. (DOS 3.3 provides a way around this through interrupt 21H,
function 67H. See page 392 for details.) Fortunately, few applications
require more than 20 different files to be open at once, so these
limitations are not usually important.
Installable Device Drivers
In DOS versions 2.0 and later, you can write a routine that provides a
consistent interface between DOS's handle-based I/O functions and almost
any I/O device that can input or output a stream of data. Such a routine
is called a device driver. DOS comes with several built-in device drivers
for the keyboard, video display, printer, communications port, and disks.
You can also install device drivers for other devices by including their
names in DEVICE commands in the CONFIG.SYS file.
DOS I/O device drivers allow handles to be associated not only with disk
files but with any input/output device. When you use a handle-based DOS
function to open a device, DOS searches its list of device drivers before
it searches for a disk filename. Familiar names like "CON", "LPT1", and
"NUL" are all part of the default list of device drivers. Opening a device
for input/output thus consists only of passing a name to a DOS function
and receiving a handle from DOS in return, regardless of whether the
device is a disk file or is associated with some other type of hardware.
──────────────────────────────────────────────────────────────────────────
NOTE:
Incidentally, this explains why you can't open a file named "CON" or
"PRN": DOS searches for device names before it searches for filenames,
so it always finds a named device before it finds a file with the same
name.
──────────────────────────────────────────────────────────────────────────
You don't have to know much about the implementation of device drivers to
use the handle-based DOS functions, so we will save a more detailed
discussion of device drivers for Appendix A. Keep in mind that by placing
the discussion of device drivers at the end of the book, we in no way mean
to diminish their importance. All programmers concerned with the range and
longevity of their programs should at least be familiar with the use and
operation of DOS device drivers.
Interrupt 21H Functions: DOS Versions 2.0 and Later
All DOS function calls described in this chapter are invoked through
interrupt 21H (decimal 33). The individual functions are selected by
placing the function number in the AH register. Any program that uses
these functions should test the DOS version number first to be sure the
functions are supported. (Function 30H provides this service.)
The functions can be organized into the groups shown in Figure 17-2. In
an effort to make the logical groupings of the function calls as clear as
possible, we organized and described them in a slightly different manner
than that in IBM's DOS technical reference manuals. You may want to
compare this organization with IBM's, to be sure you understand. Figure
17-3 lists the individual function calls.
Function
Hex Dec Group
──────────────────────────────────────────────────────────────────────────
2FH─38H 47─56 Miscellaneous functions
39H─3BH 57─59 Directory functions
3CH─46H 60─70 File-management functions
47H 71 Directory function
48H─4BH 72─75 Memory-management functions
4CH─5BH 76─91 Miscellaneous functions
5CH─5FH 92─95 Network support
62H─68H 98─104 Miscellaneous functions
──────────────────────────────────────────────────────────────────────────
Figure 17-2. The logical groups of extended DOS function calls.
╓┌─┌──────────────┌───────────────────────────────────────────┌──────────────╖
Function DOS
Hex Dec Description Version
──────────────────────────────────────────────────────────────────────────
2FH 47 Get DTA Address 2.0
30H 48 Get DOS Version Number 2.0
Function DOS
Hex Dec Description Version
──────────────────────────────────────────────────────────────────────────
30H 48 Get DOS Version Number 2.0
31H 49 Terminate and Stay Resident 2.0
33H 51 Get/Set Ctrl-C Flag 2.0
35H 53 Get Interrupt Vector 2.0
36H 54 Get Disk Free Space 2.0
38H 56 Get/Set Country-Dependent Information 2.0
39H 57 Create Directory 2.0
3AH 58 Remove Directory 2.0
3BH 59 Change Current Directory 2.0
3CH 60 Create File 2.0
3DH 61 Open Handle 2.0
3EH 62 Close Handle 2.0
3FH 63 Read from File or Device 2.0
40H 64 Write to File or Device 2.0
41H 65 Delete File 2.0
42H 66 Move File Pointer 2.0
43H 67 Get/Set File Attributes 2.0
44H 68 IOCTL──I/O Control for Devices 2.0
Function DOS
Hex Dec Description Version
──────────────────────────────────────────────────────────────────────────
44H 68 IOCTL──I/O Control for Devices 2.0
45H 69 Duplicate File Handle 2.0
46H 70 Force Duplicate File Handle 2.0
47H 71 Get Current Directory 2.0
48H 72 Allocate Memory Block 2.0
49H 73 Free Memory Block 2.0
4AH 74 Resize Memory Block 2.0
4BH 75 EXEC──Load and Execute a Program 2.0
4CH 76 Terminate with Return Code 2.0
4DH 77 Get Return Code 2.0
4EH 78 Find First Matching Directory Entry 2.0
4FH 79 Find Next Matching Directory Entry 2.0
54H 84 Get Verify Flag 2.0
56H 86 Rename File 2.0
57H 87 Get/Set File Date and Time 2.0
58H 88 Get/Set Memory Allocation Strategy 3.0
59H 89 Get Extended Error Information 3.0
5AH 90 Create Temporary File 3.0
Function DOS
Hex Dec Description Version
──────────────────────────────────────────────────────────────────────────
5AH 90 Create Temporary File 3.0
5BH 91 Create New File 3.0
5CH 92 Lock/Unlock File Region 3.0
5EH 94 Network Machine Name and Printer Setup 3.1
5FH 95 Network Redirection 3.1
62H 98 Get PSP Address 3.0
65H 101 Get Extended Country Information 3.3
66H 102 Get/Set Global Code Page 3.3
67H 103 Set Handle Count 3.3
68H 104 Commit File 3.3
──────────────────────────────────────────────────────────────────────────
Figure 17-3. Interrupt 21H functions available in DOS versions 2.0 and
later.
Function 2FH (decimal 47): Get DTA Address
Function 2FH (decimal 47) returns the address of the disk transfer area
(DTA) currently used by DOS. The address is returned in the register pair
ES:BX. Contrast this with function 1AH, discussed on page 335.
Function 30H (decimal 48): Get DOS Version Number
Function 30H (decimal 48) returns the DOS major and minor version numbers.
The major version number is in AL, and the minor version number is in AH;
BX and CX contain a serial number (0 in IBM's versions of DOS; other
possible values in non-IBM versions). For example, if you execute function
30H in DOS version 3.3, the function returns AL = 03H (the major version
number), AH = 1EH (30, the minor version number), BX = 00H, and CX = 00H.
In the OS/2 compatibility box, function 30H returns AL = 0AH; that is, the
major version number is 10.
In DOS version 1, function 30H was unsupported. Nevertheless, you can
still test for DOS version 1 by executing function 30H; in DOS version 1,
function 30H is guaranteed to return AL = 00H. Thus, a simple test of the
value returned in AL is sufficient to distinguish between version 1 and
later versions:
mov ah,30h ; AH = 30H (interrupt 21H function number)
int 21h ; get DOS version number
cmp al,2
jl EarlyVersion ; jump if DOS version 1
Any program that uses interrupt 21H functions with numbers above 2EH can
use function 30H to determine if the appropriate DOS version is being
used.
Function 31H (decimal 49): Terminate and Stay Resident
Function 31H (decimal 49) terminates a program and leaves part of the
program resident in memory. Except for the fact that function 31H lets you
reserve memory for a memory-resident program, its function is the same as
that of the program termination function (function 4CH). You call
function 31H with a return code value in AL and with the number of
paragraphs of memory to reserve for the program in DX.
Before you use function 31H, you should generally carry out the following
steps:
1. Call function 30H to verify that the DOS version is 2.0 or later.
Function 31H isn't supported in DOS version 1.
2. Call function 49H to free the memory allocated to the program's
environment block. (The word at offset 2CH in the program's PSP
contains the paragraph address of the environment block.)
3. Determine the amount of memory to reserve for the resident program.
This value must include the 16 paragraphs reserved for the program's
PSP in addition to contiguous memory reserved for the program itself.
This value does not include memory allocated dynamically by the
program using function 48H.
4. Call function 31H to terminate the program.
Like function 4CH, function 31H restores the interrupt vectors for
interrupts 22H (Terminate Address), 23H (Ctrl-C Handler), and 24H
(Critical Error Handler) to the DOS default values; therefore, you cannot
use this function to install memory-resident handlers for these
interrupts.
Function 31H is much more flexible than the Terminate-and-Stay-Resident
service supported through interrupt 27H. You should always use function
31H in your TSR programs unless you are concerned about maintaining
compatibility with DOS version 1.
Function 33H (decimal 51): Get/Set Ctrl-C Flag
Function 33H (decimal 51) lets you test or update DOS's internal Ctrl-C
flag. When you call function 33H with AL = 00H, DOS reports the current
state of the Ctrl-C flag in DL:
■ If the flag is clear (DL = 00H), DOS checks for Ctrl-C keystrokes only
when transmitting a character to or from a character device (interrupt
21H functions 00H through 0CH).
■ If the flag is set (DL = 01H), DOS also checks for Ctrl-C when it
responds to other service requests, such as file I/O operators.
When you call function 33H with AL = 01H, DOS expects DL to contain the
desired value for the break flag:
■ DL = 00H disables the break check.
■ DL = 01H enables the break check.
Function 35H (decimal 53): Get Interrupt Vector
Function 35H (decimal 53) returns the interrupt vector for the interrupt
number you specify in register AL. The vector is returned in the register
pair ES:BX.
Function 35H provides a complementary service to function 25H, which
updates an interrupt vector. (See Chapter 16.)
Function 36H (decimal 54): Get Disk Free Space
Function 36H (decimal 54) is similar to function 1CH (which gets disk
information), but also provides information about unused disk space, which
function 1CH does not. Before calling this function, select the drive that
you are interested in with the DL register: DL = 00H indicates the default
drive, DL = 01H indicates drive A, DL = 02H indicates drive B, and so on.
If you specify an invalid drive, function 36H returns FFFFH in the AX
register. Otherwise, AX contains the number of sectors per cluster, CX
contains the number of bytes per sector, BX contains the number of
available clusters, and DX contains the total number of clusters.
From these numbers you can make many interesting calculations, as follows:
CX * AX = bytes per cluster
CX * AX * BX = total number of free bytes
CX * AX * DX = total storage space in bytes
(BX * 100) / DX = percentage of free space
If S were the size of a file in bytes, you could calculate the number of
occupied clusters in this way:
(S + CX * AX - 1) \ (CX * AX)
Similar formulas would give you the number of sectors and the amount and
proportion of space allocated to a file but not used (the slack space).
Function 38H (decimal 56): Get/Set Country-Dependent Information
Function 38H (decimal 56) allows DOS to adjust to different international
currency and date format conventions. In DOS version 2, this function
reports a very small set of country-dependent information. In DOS version
3, function 38H reports a more detailed list of country-dependent items;
in this version of DOS, a program can also change the country-dependent
information with a call to function 38H.
To get country-dependent information from DOS, call function 38H with
DS:DX containing the address of a 32-byte buffer. (In DOS versions 3.0 and
later, the size of the buffer must be 34 bytes.) Register AL must be set
to 00H to get the current country information. For DOS versions 3.0 and
later, register AL can also be set to a predefined country code. (The
country code is the same 3-digit code used as the country's international
telephone access code.) To specify a country code of 255 or greater, AL
can be set to FFH (decimal 255), and the country code can be put into
register BX.
If the requested country code is invalid, DOS sets the carry flag (CF) and
places an error code in AX. Otherwise, register BX contains the country
code, and the buffer at DS:DX is filled in with the country-specific
information shown in Figures 17-4 and 17-5.
To set the current country code in DOS version 3, set DX equal to FFFFH
and call function 38H with AL equal to the country code (or if the code is
greater than 254, set AL equal to FFH and register BX equal to the country
code).
The country-dependent information is used by DOS utilities like DATE and
TIME. A program can call function 38H to obtain the information DOS uses
to configure itself for country-dependent conventions.
The date format is an integer word whose value specifies the display
format for the date. This word has three predefined values and three
corresponding date formats. (See Figure 17-6.) Room is reserved so that
others might be added in the future.
The currency symbol is the symbol used in displaying an amount of money:
In the United States, the currency symbol is a dollar sign ($); in the
United Kingdom, it's the pound symbol (j); in Japan, it's the yen symbol
(J). In DOS versions 2.0 and 2.1, the currency symbol can only be a single
character, but in DOS version 3, a string up to four characters in length
can be used. For example, one of the currency strings that could be used
in DOS version 3.3 is DKR, which stands for Danish kroner.
Offset Size
Hex Dec (bytes) Description
──────────────────────────────────────────────────────────────────────────
00H 0 2 Date format
02H 2 2 Currency symbol string (ASCIIZ format)
04H 4 2 Thousands separator string (ASCIIZ format)
06H 6 2 Decimal separator string (ASCIIZ format)
08H 8 24 (Reserved)
──────────────────────────────────────────────────────────────────────────
Figure 17-4. The country-dependent information reported by function 38H
in DOS version 2.
Offset Size
Hex Dec (bytes) Description
──────────────────────────────────────────────────────────────────────────
00H 0 2 Date format
02H 2 5 Currency symbol string (ASCIIZ format)
07H 7 2 Thousands separator string (ASCIIZ format)
09H 9 2 Decimal separator string (ASCIIZ format)
0BH 11 2 Date separator string (ASCIIZ format)
0DH 13 2 Time separator string (ASCIIZ format)
0FH 15 1 Currency symbol location
10H 16 1 Currency decimal places
11H 17 1 Time format: 1 = 24-hour clock; 0 = 12-hour
12H 18 4 Extended ASCII map call address
16H 22 2 List separator string (ASCIIZ format)
18H 24 10 (Reserved)
──────────────────────────────────────────────────────────────────────────
Figure 17-5. The country-dependent information returned by function 38H
in DOS version 3.
Value Use Date
──────────────────────────────────────────────────────────────────────────
00H American month day year
01H European day month year
02H Japanese year month day
──────────────────────────────────────────────────────────────────────────
Figure 17-6. The three predefined date formats returned by function 38H.
The thousands separator is the symbol used to punctuate the thousands mark
in numbers. The U.S. uses a comma as a thousands separator, as in the
number 12,345; other countries use a period or a blank.
The decimal separator is the symbol used to punctuate decimal places. The
U.S. uses a period as a decimal separator, as in 3.0; other countries use
a comma.
The date separator and time separator are the punctuation used in
displaying the date (for example, ─ as in 7─4─1988) and in displaying the
time (for example, : as in 12:34).
The currency symbol location indicates where the currency symbol should be
placed. A value of 00H places the currency symbol immediately before the
amount (J1500); 01H places the symbol immediately after the amount (15¢);
02H places the symbol before the amount with an intervening space (FFr
15); 03H places the symbol after the amount with an intervening space (15
DKR); and 04H replaces the decimal separator with the currency symbol.
The currency decimal places value specifies how many decimal places are
used in the currency. For example, the value would be 02H for U.S.
currency (dollars and cents) and 00H for Italian currency (lire).
The time format field specifies whether time appears in a 12-hour or
24-hour format. Only the low-order bit (bit 0) is currently used; if the
bit is set to 0, a 12-hour clock is used; if it is set to 1, a 24-hour
clock is used.
The extended ASCII map call address is the segmented address of a routine
that maps ASCII characters 80H through FFH to characters in the range 00H
through 7FH. Not all printers or plotters can display extended ASCII
characters in the range 80H─FFH, so the routine at this address is called
when it is necessary to map such characters into the usual range of ASCII
characters (00H─7FH).
The list separator indicates the symbol used to separate items in a list,
such as the commas in the list A, B, C, and D.
Function 39H (decimal 57): Create Directory
Function 39H (decimal 57) creates a subdirectory, just as the DOS command
MKDIR does. To invoke this service, create an ASCIIZ string containing the
pathname of the new directory. The register pair DS:DX contains the
address of the ASCIIZ string. If an error occurs, function 39H sets the
carry flag and returns an error code in AX. The possible error codes are
03H (path not found) and 05H (access denied).
Function 3AH (decimal 58): Remove Directory
Function 3AH (decimal 58) removes (deletes) a subdirectory exactly as the
DOS command RMDIR does. To invoke this function, create an ASCIIZ string
containing the pathname of the directory you want to remove. The register
pair DS:DX points to the ASCIIZ string. If an error occurs, function 3AH
sets the carry flag and returns an error code in AX. The possible error
codes are 03H (path not found), 05H (access denied), and 10H (attempt to
remove current directory).
Function 3BH (decimal 59): Change Current Directory
Function 3BH (decimal 59) changes the current directory exactly as the DOS
command CHDIR does. To invoke this function, create an ASCIIZ string
containing the pathname of the new directory. DS:DX contains the address
of the ASCIIZ string. If an error occurs, function 3BH sets the carry flag
and returns an error code in AX. The one possible error code is 03H (path
not found).
Function 3CH (decimal 60): Create File
Function 3CH (decimal 60) opens an empty file using a specified name. If
the file exists, function 3CH truncates it to zero length. If the file
does not exist, function 3CH creates a new file. This function parallels
function 16H (discussed on page 334).
To invoke this function, create an ASCIIZ string containing the pathname
and filename. The register pair DS:DX contains the address of the ASCIIZ
string. CX contains the file attribute. (See page 113 for more on file
attributes and attribute bit settings.) When function 3CH executes
successfully, it clears the carry flag and returns a handle in AX.
Otherwise, this function sets the carry flag and leaves an error code in
AX. Possible error codes are 03H (path not found), 04H (no handle
available), and 05H (access denied). Code 05H can indicate either that
there is no room for a new directory entry or that the existing file is
marked read-only and can't be opened for output.
Be aware that by using function 3CH you can accidentally truncate an
existing file to zero length. The best way to avoid this mistake is simply
to call function 4EH to search the directory for an existing file before
you call function 3CH. Or, if you are using DOS 3.0 or later, you have two
other alternatives: You can call function 5BH, which works like function
3CH but won't open an existing file, or you can use function 5AH to
create a temporary file with a unique filename.
Function 3DH (decimal 61): Open Handle
Function 3DH (decimal 61) opens an existing file or device. You provide
the pathname and filename in the form of an ASCIIZ string. As with all
other file I/O functions, DS:DX points to this string. You also indicate
how you want to use the file by placing a file-access code in register AL.
The 8 bits of AL are divided into the four fields shown in Figure 17-7 on
the following page.
Bit
7 6 5 4 3 2 1 0 Use
──────────────────────────────────────────────────────────────────────────
I . . . . . . . Inheritance flag (DOS version 3 only)
. S S S . . . . Sharing mode (DOS version 3 only)
. . . . R . . . (Reserved)
. . . . . A A A Access code
──────────────────────────────────────────────────────────────────────────
Figure 17-7. File-access and sharing codes for function 3DH.
The file-access code for DOS version 2 is simple: Only the access bits
(bits 0─2) are used; all other bits are set to 0. The three access-code
settings are defined in Figure 17-8.
Bit
2 1 0 Use
──────────────────────────────────────────────────────────────────────────
0 0 0 Read (only) access
0 0 1 Write (only) access
0 1 0 Read or write access
──────────────────────────────────────────────────────────────────────────
Figure 17-8. File-access modes for function 3DH.
DOS version 3 uses the inheritance and sharing codes as well as the access
code. The inheritance and sharing codes give you control over how
different programs access the same file at the same time.
Bit 7, the inheritance bit, indicates whether or not a child process can
inherit the use of this file. (For more about parent and child processes,
see the discussion of function 4BH later in this chapter.) When a child
process inherits a handle, it inherits the file's access and sharing
codes: If bit 7 = 0, a child process can use the same handle to access the
file as the parent process; if bit 7 = 1, the child process must itself
open the file to obtain a different handle.
Bits 4 through 6, the sharing-mode bits (SSS in Figure 17-7), define what
will happen when more than one program tries to open the same file. There
are five sharing modes: compatibility mode (SSS = 000), deny read/write
mode (SSS = 001), deny write mode (SSS = 010), deny read mode (SSS = 011),
and deny none mode (SSS = 100). When a second attempt is made to open the
file, DOS compares the file's sharing code with the access requested in
the second open operation. DOS allows the second open operation to succeed
only if the sharing mode and the requested access mode are compatible.
──────────────────────────────────────────────────────────────────────────
NOTE:
DOS performs this file-sharing validation only if it is running on a
network or if the SHARE utility is installed. See the DOS technical
reference manual for more details on networking and the SHARE utility.
──────────────────────────────────────────────────────────────────────────
Bit 3, marked as "Reserved" in Figure 17-7, should be set to 0.
Like function 3CH, function 3DH clears the carry flag and returns a handle
in AX when it successfully opens a file or device. Otherwise, this
function sets the carry flag and leaves an error code in AX. The possible
return codes from function 3DH are 02H (file not found), 03H (path not
found), 04H (no handles available), 05H (access denied), and 0CH (invalid
access code).
If SHARE or network file sharing is in force in DOS version 3, DOS signals
a sharing violation by executing interrupt 24H.
Function 3EH (decimal 62): Close Handle
Function 3EH (decimal 62) closes a file or device associated with the
handle in BX. This function flushes all file buffers and updates the
directory if necessary. The only error code this function can return is
06H (invalid handle).
Function 3FH (decimal 63): Read from File or Device
Function 3FH (decimal 63) reads the file or device associated with the
handle in BX. The CX register specifies the number of bytes to read; DS:DX
points to the buffer where data that is read will be placed. If the read
operation is successful, function 3FH clears the carry flag and returns
the number of bytes read in AX. If this value is 0, the function has tried
to read from the end of a file. If the read operation fails, this function
sets the carry flag and leaves an error code in AX. The possible error
codes are 05H (access denied) and 06H (invalid handle).
Function 40H (decimal 64): Write to File or Device
Function 40H (decimal 64) writes to the file or device associated with the
handle in BX. CX specifies the number of bytes to be written; DS:DX points
to the address of the data bytes.
When the write operation is complete, function 40H updates the file
pointer to point past the data just written.
You must examine both the carry flag and the value in AX returned by
function 40H to determine the success of the write operation:
■ If the carry flag is clear and AX = CX, the operation completed
successfully.
■ If the carry flag is clear but AX < CX, then the output was written to
a disk file that had insufficient disk space to complete the write
operation.
■ If the carry flag is set, AX contains an error code of 05H (access
denied) or 06H (invalid handle).
The fact that function 40H updates the file pointer has an interesting
side effect: You can set the size of a file to any arbitrary value by
executing function 40H with CX = 00H. The usual technique is to call
function 42H to set the file pointer location and then to immediately call
function 40H with CX = 00H to update the file size.
Function 41H (decimal 65): Delete File
Function 41H (decimal 65) deletes the directory entry of a file. The file
is specified by an ASCIIZ string containing the path and filename. The
register pair DS:DX points to the string. Unlike function 13H, function
41H does not support wildcard characters in the file specification: With
function 41H you can delete only one file at a time.
You cannot delete read-only files with this function. To delete a
read-only file, first remove the read-only attribute using function 43H,
and then use function 41H.
Function 41H can return three error codes in AX: 02H (file not found), 03H
(path not found), and 05H (access denied).
Function 42H (decimal 66): Move File Pointer
Function 42H (decimal 66) changes the logical read/write position in a
file. To invoke this service, load BX with a handle and then specify the
new pointer location by placing a reference location in AL and an offset
relative to the reference location in register pair CX:DX. The byte offset
in CX:DX is a 32-bit, long integer. CX is the high-order part of the
offset (which is 0, unless the offset amount is more than 65,535) and DX
is the low-order part.
You can specify the reference location in AL in three different ways: If
AL = 00H, the offset is taken relative to the beginning of the file and
the file pointer is moved CX:DX bytes from that point; if AL = 01H, the
offset is taken relative to the current file pointer location; if AL =
02H, the offset is taken from the current end of file.
If the function executes successfully, it clears the carry flag and
returns in the register pair DX:AX the current file pointer location
relative to the beginning of the file. The pointer is returned as a 32-bit
long integer, with the high-order part in DX and the low-order part in AX.
If the function fails, it sets the carry flag and returns an error code in
AX. Possible error codes are 01H (invalid function number, which means AL
did not contain 00H, 01H, or 02H) and 06H (invalid handle).
You can use function 42H in several different ways:
■ To place the file pointer at an arbitrary location in the file, call
function 42H with AL = 00H and CX:DX specifying the desired offset
relative to the start of the file.
■ To position the file pointer at the end of the file, call function 42H
with AL = 02H and 00H in CX:DX.
■ To determine the current location of the file pointer, use AL = 01H and
00H in CX:DX; the value returned in DX:AX is the current file pointer
location.
DOS does not validate the resulting location of the file pointer. In
particular, you can end with a negative file pointer offset (that is, a
file pointer at a position before the logical start of the file). However,
it's not a good idea to use negative file pointers for two reasons: If you
perform a subsequent read or write operation, you'll be in error; and your
program will be harder to adapt for OS/2, where an attempt to move a file
pointer to a negative offset generates an error.
──────────────────────────────────────────────────────────────────────────
NOTE:
The operation of moving a logical file pointer to a specified location
in a file is sometimes called a "seek," but the same word is also used
in the sense of moving the read/write heads of a disk drive to a
specified cylinder on a disk. The two operations aren't the same.
──────────────────────────────────────────────────────────────────────────
Function 43H (decimal 67): Get/Set File Attributes
Function 43H (decimal 67) gets or sets the attributes of a file. (See page
113 for details about file attributes.) DS:DX points to an ASCIIZ string
that specifies the file in question. (Global filename characters ? and *
cannot be used.) Calling function 43H with AL = 00H returns the file's
attributes in CX; AL = 01H sets the attribute values you specify in CX.
If function 43H fails, the carry flag is set and AX contains one of four
error codes: 01H (invalid function), 02H (file not found), 03H (path not
found), and 05H (access denied).
Function 44H (decimal 68): IOCTL──I/O Control for Devices
Function 44H (decimal 68) performs input/output control operations, mostly
for devices. (See Figure 17-9.) AL selects one of 16 subfunctions,
numbered 00H through 0FH; some of these subfunctions have sub-subfunctions
you specify with a "minor code" in CL.
The main purpose of the IOCTL function is to provide a consistent
interface between DOS programs and device drivers. In general, you
shouldn't use IOCTL calls unless you know something about how device
drivers are structured──a topic we'll cover in Appendix A. A few IOCTL
calls, however, are useful even if you don't understand the details of
device-driver operations. We'll point these out as we summarize the
various IOCTL calls.
──────────────────────────────────────────────────────────────────────────
NOTE:
Not all IOCTL subfunctions are supported in earlier versions of DOS.
Figure 17-9 indicates the DOS versions in which the various IOCTL
subfunctions were introduced.
──────────────────────────────────────────────────────────────────────────
Subfunction DOS
Hex Dec Description Version
──────────────────────────────────────────────────────────────────────────
00H 0 Get device data. 2.0
01H 1 Set device data. 2.0
02H 2 Receive control data from character device. 2.0
03H 3 Send control data to character device. 2.0
04H 4 Receive control data from block device. 2.0
05H 5 Send control data to block device. 2.0
06H 6 Check input status. 2.0
07H 7 Check output status. 2.0
08H 8 Check if block device is removable. 3.0
09H 9 Check if block device is remote. 3.1
0AH 10 Check if handle is remote. 3.1
0BH 11 Change sharing retry count. 3.0
0CH 12 Generic I/O control for handles. 3.2
0DH 13 Generic I/O control for block devices. 3.2
0EH 14 Get logical drive map. 3.2
0FH 15 Set logical drive map. 3.2
──────────────────────────────────────────────────────────────────────────
Figure 17-9. Subfunctions available under interrupt 21H, function 44H
(IOCTL).
Subfunctions 00H and 01H. These subfunctions get and set device
information formatted in DX by a complicated set of bit coding. Bit 7 is
set to 1 for devices and to 0 for disk files. For devices, bits 0 through
5 are specified as shown in Figure 17-10. For disk files, bits 0 through
5 provide the disk-drive number: A value of 0 represents drive A, a value
of 1 represents drive B, and so on. Both subfunctions should be called
with a file or device handle in BX. Subfunction 00H can be called for both
disk files and devices; subfunction 01H can be called only for character
devices.
╓┌─┌──────────────────────────────────────────────────────────────┌──────────►
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Use
───────────────────────────────────────────────────────────────────────────
. . . . . . . . . . . . . . . X 1 = standard
. . . . . . . . . . . . . . X . 1 = standard
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Use
───────────────────────────────────────────────────────────────────────────
. . . . . . . . . . . . . . X . 1 = standard
. . . . . . . . . . . . . X . . 1 = null dev
. . . . . . X . . . . . X . . . 1 = clock de
. . . . . . . . . . . X . . . . (Reserved)
. . . . . . . . . . X . . . . . 1 = data os
control-char
= data is "c
control-char
. . . . . . . . . X . . . . . . 0 = end of f
file (for in
. . . . . . . . X . . . . . . . 1 = device;
. . . . . . . R . . . . . . . . (Reserved)
. . . . . . R . . . . . . . . . (Reserved)
. . . . . R . . . . . . . . . . (Reserved)
. . . . R . . . . . . . . . . . (Reserved)
. . . R . . . . . . . . . . . . (Reserved)
. . R . . . . . . . . . . . . . (Reserved)
. X . . . . . . . . . . . . . . 1 = device c
Bit
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Use
───────────────────────────────────────────────────────────────────────────
. X . . . . . . . . . . . . . . 1 = device c
strings tran
subfunctions
R . . . . . . . . . . . . . . . (Reserved)
───────────────────────────────────────────────────────────────────────────
Figure 17-10. The bit settings of the device data word DX for subfunction
00H or 01H of interrupt 21H, function 44H.
You can modify how DOS processes I/O for the CON device (the keyboard/
video display combination) by setting "raw" input/output mode for the
device. Do this by clearing bit 5 of the device data word in DX and
calling subfunction 01H:
mov ax,4400h ; AH = 44H (interrupt 21H function number)
; AL = 0 (subfunction number)
mov bx,0 ; BX = 0 (handle for CON device)
int 21h ; get device data into DX
or dx,0020h ; set bit 5 ("raw" mode)
and dx,00FFh ; zero reserved bits 8─15
mov ax,4401h ; set up for subfunction 1
mov bx,0 ; BX = CON device handle
int 21h ; set device data for CON
After you execute this sequence of code, DOS no longer recognizes Ctrl-P
and Ctrl-S characters, nor does it expand tabs on output.
Subfunctions 02H through 05H. These subfunctions transfer control data
between your program and a device driver. Subfunctions 02H and 03H get and
send control data for character-oriented devices; subfunctions 04H and 05H
get and send control data for block-oriented devices. In all four
subfunctions you specify the subfunction number in AL, the address of a
data buffer in DS:DX, and the number of bytes to transfer in CX. For
subfunctions 02H and 03H, you must specify a handle in BX; for
subfunctions 04H and 05H, you must specify a drive number in BL (00H =
default drive, 01H = drive A, and so on).
The control data you transfer to or from a device driver is not
necessarily part of the device's input/output data stream: The control
data is often used to obtain the device status or to control
hardware-specific features such as printer font characteristics or tape
drive rewind.
These subfunctions can be used only if the device can process control
strings. This capability is indicated by bit 14 in the device data word
returned by subfunction 00H.
Subfunctions 06H and 07H. These subfunctions return the current input or
output status of a device or file. Call them with a handle in BX:
Subfunction 06H returns the current input status; subfunction 07H returns
the current output status.
Both of these subfunctions use the carry flag to indicate a successful
call. If the carry flag is clear, AL contains the status: AL = 00H means
the device is not ready for input or output; AL = FFH means the device is
ready. (For a file, input status AL = 00H means end-of-file; output status
is always "ready" regardless of the value in AL.) If the carry flag is
set, AX contains an error code: 01H (invalid function), 05H (access
denied), 06H (invalid handle), or 0DH (invalid data).
Subfunction 08H. This subfunction, supported only in DOS versions 3.0 and
later, indicates whether a block-oriented device has removable media or
not. (The floppy diskettes in a diskette drive are removable; the fixed
disk in a fixed-disk drive is not.) Subfunction 08H can be extremely
useful because it lets a program know if it has to check for a disk change
or if it can rely on the same disk always being there. Call subfunction
08H with a drive number in BL (00H = default drive, 01H = drive A, and so
on). The subfunction clears the carry flag on a successful return and
leaves AX = 00H if the storage medium is removable or AX = 01H if the
storage medium is nonremovable. If the carry flag is set, AX contains an
error code: 01H (invalid function) or 0FH (invalid drive).
Subfunction 09H. In a network configuration, this subfunction determines
whether a particular block device is local (attached to the computer
running the program) or remote (redirected to a network server). You must
specify a drive number in BL when you call this subfunction.
Subfunction 09H clears the carry flag to indicate a successful call. In
this case, bit 12 of the value in DX indicates whether the device is
remote (bit 12 = 1) or local (bit 12 = 0). If the carry flag is set, AX
contains an error code: 01H (invalid function) or 0FH (invalid drive).
Subfunction 09H is available in DOS 3.1 and later.
Subfunction 0AH (decimal 10). This subfunction is similar to subfunction
09H but is used with a device handle instead of a drive number. Specify
the handle in BX when you call this subfunction.
Like subfunction 09H, subfunction 0AH clears the carry flag and returns a
value in DX that indicates whether the device is local or remote. Bit 15
of DX indicates whether the device is remote (bit 15 = 1) or local (bit 15
= 0). If an error occurs, the function sets the carry flag and returns an
error code in AX: 01H (invalid function) or 06H (invalid handle).
Subfunction 09H is available in DOS 3.1 and later.
Subfunction 0BH (decimal 11). This subfunction, which is supported only in
DOS versions 3.0 and later, controls the way DOS attempts to resolve
file-sharing conflicts. Because some programs lock files only briefly,
file-sharing conflicts can be very transitory. DOS can try more than once
to gain access to a shared file before reporting a conflict, in the hope
that the lock condition goes away in the meantime.
Subfunction 0BH can help you empirically tune a network in which you
expect transient file-sharing conflicts to occur. Call this subfunction
with DX containing the number of times you want DOS to retry access to a
shared file before it gives up and reports an error. CX should specify the
delay value between retries. DOS creates a delay by executing an empty
loop 65,536 times; the value in CX indicates the number of times you want
DOS to execute the empty delay loop. (The DOS defaults are three retries
and one delay loop between retries.)
If the subfunction executes successfully, it clears the carry flag. If the
carry flag is set, AX contains an error code of 01H (invalid function).
Subfunction 0CH (decimal 12). This subfunction provides miscellaneous
control functions for character-oriented devices. Each control function is
designated by a minor code in CL and a major code (also called a category
code) in CH. The various major and minor codes are listed in Figure 17-11.
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
Major Code (specified in CH)
00H 0 Unknown
01H 1 Serial port (COM1, COM2, COM3, COM4)
03H 3 Console (CON)
05H 5 Printer (LPT1, LPT2, LPT3)
Minor Code (specified in CL)
45H 69 Set iteration count.
4AH 74 Select code page.
4CH 76 Start code page preparation.
4DH 77 End code page preparation.
65H 101 Get iteration count.
6AH 106 Query selected code page.
6BH 107 Query prepare list.
──────────────────────────────────────────────────────────────────────────
Figure 17-11. Major and minor codes for IOCTL subfunction 0CH (generic
I/O control for handles).
Minor codes 45H and 65H were introduced in DOS version 3.2. They apply
only to print devices (major code 05H). They deal with the number of times
DOS attempts to send a character to a printer before it assumes the
printer is busy. The remaining minor codes were introduced in DOS version
3.3. They provide detailed support for defining and loading code pages for
output devices that can use multiple character sets or fonts.
For details on the use of the services provided in this IOCTL subfunction,
see the DOS technical reference manual.
Subfunction 0DH (decimal 13). Subfunction 0DH provides six generic
services for block-oriented devices. Each service is designated by a major
code in CH and a minor code in CL. (See Figure 17-12.) In general, these
services are similar to services provided by the ROM BIOS for diskettes
and fixed disks, but these IOCTL services provide a consistent interface
to any block-oriented device with a device driver that supports these
IOCTL calls.
Subfunction 0DH is available in DOS 3.2 and later. See the DOS technical
reference manual for details on subfunction 0DH services.
Hex Dec Description
──────────────────────────────────────────────────────────────────────────
Major Code (specified in CH)
08H 8 Disk drive
Minor Code (specified in CL)
40H 64 Set parameters for block device.
41H 65 Write track on logical drive.
42H 66 Format and verify track on logical drive.
60H 96 Get parameters for block device.
61H 97 Read track on logical drive.
62H 98 Verify track on logical drive.
──────────────────────────────────────────────────────────────────────────
Figure 17-12. Major and minor codes for IOCTL subfunction 0DH (generic
I/O control for block devices).
Subfunctions 0EH and 0FH (decimal 14 and 15). These two subfunctions
relate logical mapping of drive letter assignments to physical drives. For
example, in systems with only one diskette drive, DOS maps drive letter B
to physical drive A.
Call these subfunctions with a logical drive ID in BL (01H represents
drive A, 02H represents drive B, and so on). Subfunction 0EH returns a
logical drive ID that is currently mapped to the drive you specified in
BL. Subfunction 0FH also updates DOS's internal logical map so that the
drive ID you specified becomes the new logical drive ID. Both subfunctions
use AL to return the logical drive ID; if AL = 00H, only one logical drive
is associated with the drive ID you specified in BL. If an error occurs,
the carry flag is set and AX contains an error code: 01H (invalid
function) or 0FH (invalid drive).
For example, if you execute the following instructions on a system with
only one diskette drive, DOS associates drive B with the diskette drive:
mov bl,2 ; BL = logical drive number
mov ax,440Fh ; set logical drive map
int 21h ; update the logical drive ID
; (DOS returns AL = 02H)
Function 45H (decimal 69): Duplicate Handle
Function 45H (decimal 69) duplicates an open file handle and returns a new
handle number that refers to the same file or device. All actions
performed with one handle will be reflected in the other handle──the new
handle does not act independently in any way.
Call function 45H with an open handle in BX. If the function executes
successfully, it clears the carry flag and leaves a new handle number in
AX. If an error occurs, the carry flag is set and AX contains an error
code: 04H (no more handles) or 06H (invalid handle).
You can use function 45H along with function 46H to implement
input/output redirection. You can also use it to commit an open file to
disk by duplicating the open file's handle and then closing the duplicate
handle. This has the effect of flushing the file's disk buffers and
updating the directory, without the overhead of closing the file,
reopening it (which involves a directory search), and repositioning the
file pointer:
mov bx,Handle ; BX = handle of open file
mov ah,45h
int 21h ; get duplicate handle into AX
jc Error
mov bx,ax ; BX = duplicate handle
mov ah,3Eh
int 21h ; close duplicate handle
; (original handle remains open)
Function 46H (decimal 70): Force Duplicate Handle
Function 46H (decimal 70) has a somewhat misleading name because it really
does not create a duplicate handle as does function 45H. Instead, function
46H associates an existing open handle with a different device. This is
the key to implementing input/output redirection in DOS.
Call function 46H with an open handle in BX and a second handle in CX.
When function 46H returns, the handle in CX is associated with the same
device as the open handle in BX. If the handle in CX was previously
associated with an open device, function 46H closes the device (which
might otherwise be without a handle). If no errors occur, the function
clears the carry flag. Otherwise, the carry flag is set, and AX contains
an error code: 04H (no more handles) or 06H (invalid handle).
To see how function 46H works, consider how you would redirect output from
the standard output device (the video screen) to a file:
mov bx,stdout ; BX = handle of standard output device
mov ah,45h ; AH = function number ("Duplicate Handle")
int 21h ; get duplicate handle into AX
jc Error ; (trap errors)
mov stdoutDup,ax ; save the duplicate handle in a memory variable
mov bx,FileHandle ; BX = handle of open file
mov cx,stdout ; CX = handle to be redirected
mov ah,46h ; AH = function number ("Force Duplicate Handle")
int 21h ; redirect stdout to the file
jc Error
; at this point, all output to stdout
; goes into the file
To undo this redirection, associate the standard output device with the
saved duplicate:
mov bx,stdoutDup ; BX = duplicate of previous stdout
mov cx,stdout ; CX = handle to be redirected
mov ah,46h ; AH = function number ("Force Duplicate Handle")
int 21h ; restore stdout to what it was
jc Error
mov bx,stdoutDup ; BX = duplicate
mov ah,3Eh ; AH = function number ("Close")
int 21h ; discard duplicate handle
Function 47H (decimal 71): Get Current Directory
Function 47H (decimal 71) reports the current directory in the form of an
ASCIIZ string. Call function 47H with a drive number in DL (00H = default
drive, 01H = drive A, and so on) and the address of a 64-byte buffer in
DS:SI. The function normally clears the carry flag and fills the buffer
with an ASCIIZ string indicating the path from the root to the current
directory. If you specify an invalid drive number, the function sets the
carry flag and returns an error code of 0FH in AX.
Because the path returned by this function starts at the root directory,
the string at DS:SI includes neither the drive letter (as in A:) nor the
start-from-the-root backslash (as in A:\). By these rules, if the current
directory is the root directory, then this function returns a null string.
If you want an intelligible display of the current directory, you can
prefix the information returned by this function with the drive-and-root
indicators (as in A:\).
Function 48H (decimal 72): Allocate Memory Block
Function 48H (decimal 72) dynamically allocates memory. You request the
number of paragraphs (16-byte units) you want allocated in BX. On return,
AX contains the segment of the allocated memory block.
If an error occurs, the carry flag is set and AX contains an error code:
07H (memory control blocks destroyed) or 08H (insufficient memory). If
there is insufficient memory to satisfy your request, BX contains the
size, in paragraphs, of the largest available block of memory.
Memory blocks allocated to a program using function 48H are freed by DOS
when the program terminates with function 00H or 4CH, but they remain
allocated to a memory-resident program that terminates with the
Terminate-and-Stay-Resident function, 31H.
Function 49H (decimal 73): Free Memory Block
Function 49H (decimal 73) frees a block of memory for subsequent reuse by
DOS or by other programs. Call function 49H with ES containing the
paragraph address (segment) of the start of the memory block. If the
memory is successfully freed, the function clears the carry flag.
Otherwise, the carry flag is set, and AX contains an error code: 07H
(memory control blocks destroyed) or 09H (invalid memory-block address).
Although function 49H is usually used to free memory previously allocated
through function 48H, it will free any memory block. For example, a
Terminate-and-Stay-Resident program can free its environment block by
calling function 49H with ES containing the paragraph address of the
environment block. (See the discussion of function 31H in this chapter.)
Function 4AH (decimal 74): Resize Memory Block
Function 4AH (decimal 74) is used to increase or decrease the size of a
block of memory that was allocated by function 48H. Register ES contains
the segment address of the block that will be changed. Register BX
contains the desired size of the block in paragraphs (units of 16 bytes).
The function clears the carry flag if the memory block can be resized as
requested. If an error occurs, the carry flag is set, and AX contains an
error code: 07H (memory control blocks destroyed), 08H (insufficient
memory), or 09H (invalid memory-block address). If DOS reported that there
was insufficient memory to increase the size of a memory block, BX
contains the maximum size, in paragraphs, of the memory block.
Function 4BH (decimal 75): EXEC──Load and Execute a Program
Function 4BH (decimal 75) lets a parent program load a "child" program
into memory and execute it. This function can also be used to load
executable code or data into memory without executing it. In both cases,
you call function 4BH with DS:DX pointing to an ASCIIZ string with the
path and fi