Copyright 1985 by ABComputing March 15, 1985 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º The World of Device-Drivers º º º º by º º º º Don Buresh º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Welcome to the mysterious world of device-drivers. Of all the programs a person could write, a device-driver is probably the most difficult and obscure. It's difficult because there are no good examples showing how to create a device-driver. The PC-DOS 3.0 diskette contains a .LST file for a disk device-driver, VDISK. It weighs in at 50+ pages when printed in condensed mode; hardly recommended reading for beginners. To fill this void, I've provided a device-driver shell that can be used to create your own drivers. It's in DRIVER1.ASM on Diskette B. We will discuss it in this article. Device-drivers are obscure because they're hard to classify. o A driver isn't a true COM module because it does not have a Program Segment Prefix. In other words, it uses ORG 0, not ORG 100h. But it's like a COM module in that it must fit into one segment and EXE2BIN is required to create it. Even the PC-DOS manuals seem confused on this point. The PC-DOS 2.0 manual refers to drivers as COM modules, but the PC-DOS 3.0 Technical Reference manual changed all such references to EXE modules! o Drivers aren't invoked from the command-line. They are installed when the system is booted. If the configuration file, CONFIG.SYS, contains the statement "device=TESTDEV", the device TESTDEV is installed at boot-time. Once installed, the device silently, cautiously, restlessly waits for PC-DOS to request help through one of 16 service requests. o Device-drivers are called twice by PC-DOS to perform one service! PC-DOS first calls a "Strategy" routine contained in the driver, passing it information in a request-header. (The request-header is used by PC-DOS to communicate with the device-driver, and vice versa.) The Strategy routine saves the address of the request- header and returns control to PC-DOS which immediately calls another routine in the driver, the Interrupt routine. This routine gets the address of the request- header saved by the Strategy routine, studies the command-code field of the request-header, and implements the requested service. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Backtracking ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Let's start at the beginning of the device-driver story. If the ROM-BIOS could support every device from a xylophone to a widget, there would be no need for device-drivers. Obviously, the ROM-BIOS can not offer that level of support so PC-DOS 2.x offered an alternate entrance into the system, via installable device-drivers. This wasn't a back-door entrance. PC-DOS 2.x officially recognized the device-driver format and later releases of PC-DOS should too. Upon power-up, PC-DOS includes a number of standard device- drivers, for example, CON, PRN, and A:. You may not regard these as being device-drivers since they are so friendly and familiar, but they are. "Real" device-drivers supposedly are hostile beings from another planet. In truth, drivers aren't hostile, but they reflect the complexity of the devices they control. User-written drivers are normally installed to communicate with optional, or non-standard devices, like modems, special-configuration disk drives, and RAM disks. If anything, it is the device itself that is complex, and this makes the driver seem complex. Device drivers have two main functions: o Emulation of a physical device o Translation of data Using a driver to change the standard keyboard-layout to a foreign-language layout is an example of emulation. Converting command-line information into a form that a device can understand is translation. There are two types of device-drivers: o CHARACTER device-drivers serially input and output characters. These devices have names such as CON, or PRN. They can support only one physical device at a time. For example, if a PC has 3 serial ports, there must be 3 character device-drivers, one for each port. Their names would probably be COM1, COM2, and COM3. o BLOCK device-drivers are really disk-drivers for fixed or floppy disks. Instead of having names like character devices, block devices are mapped to drive letters (A, B, C, etc.) and can support more than one physical unit. For example, suppose that HARDDISK.SYS is the first block-device listed in the CONFIG.SYS file and that it controls 2 hard-disk drives. If a PC has two floppy drives and a hard disk (A:, B:, and C:), HARDDISK.SYS is responsible for units D: and E:. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Device-Driver Format ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ All device-drivers consist of three main sections: o The Device Header o The Strategy Routine o An Interrupt routine and subordinate modules Note: The term "Interrupt routine" is misleading because it is not really an interrupt; it is an ordinary subroutine. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Device Header ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The device Header is located at the very beginning of the device-driver source code; that is, at relative offset zero (ORG 0). The device Header has the following fields: o A doubleword pointer to the Header of the next device (or -1 if this is the last device in the driver source file) o A device attribute word o A word pointer to the device Strategy routine o A word pointer to the device Interrupt routine o Eight bytes for the device name (character devices) or the number of units supported (block devices) When PC-DOS is booted, it creates a chain of devices from the "device=" statements found in the CONFIG.SYS file. The chain is formed by linking the device Header of one device- driver to the device Header of the next device-driver. Normally, the next-Header field in the Header is set to -1. PC-DOS overwrites this value with the segment:offset address of the next device-driver in memory when the chain is built. The attribute field in the device Header tells PC-DOS what kind of device the driver controls: it describes the attributes of the device. For example, if bit 15 = 1, the device is a character device; otherwise, a block device. If bit 14 = 1, the device can support IOCTL; otherwise, not. Bits 0 and 1 indicate if this device is the Standard Input or Output device. The Strategy and Interrupt fields in the Header contain the offset (into CS) of the Strategy and Interrupt routines mentioned earlier. The last field in the Header contains either an 8-byte name for character devices or the number of units supported by a block device. In the latter case, only the first byte of this field is used. Let's run through an example. Suppose we want to print a character using PC-DOS function-call 5h. After issuing the appropriate software interrupt, PC-DOS scans the chain of device-drivers looking in the device-name field for the name PRN or LPT1. If DOS doesn't find such a driver, an I/O error -- "Access denied" -- is returned. Assuming that the name is found, PC-DOS creates a block of information called a request-header that is used to control the device. PC-DOS passes the address of the request-header in ES:BX to the Strategy routine which saves this address in a local data area. Control returns to PC-DOS, which immediately calls the Interrupt routine to analyze the information in the request-header and finally print the character. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Making of EMILY ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Let's create a character device called EMILY, named after my daughter. The complete EMILY is presented on Diskette B in file DRIVER1.ASM. We will highlight selected parts in our discussion. The device Header is presented first. ÉÍÍ Device Header ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º º º CSEG Segment Para Public 'CODE' º º Assume CS:CSEG,DS:Nothing,ES:Nothing,SS:Nothing º º º º Org 0000H ; Notice ORG 0, not ORG 100h º º Device_Driver Proc Far º º ; º º ; The device Header º º ; º º Next_Device DD -1 ; Setting this field to -1 º º ; . indicates that there is º º ; . only one driver in this º º ; . device-driver program. º º º º Attribute DW 0C000H ; Turn bits 14 and 15 on, all º º ; . others off. Bit 15 = 1 for º º ; . a character device. Bit 14 = 1 º º ; . if device supports the IOCTL º º ; . function. º º º º Strategy DW Offset Strategy_Routine º º ; Tells PC-DOS the offset address º º ; . of the Strategy routine. º º ; . The segment address is assumed º º ; . to be the value in the CS º º ; . register. º º º º Interrupt DW Offset Interrupt_Routine º º ; Tells PC-DOS the offset address º º ; . of the Interrupt routine. º º ; . Like the Strategy routine, the º º ; . segment address is assumed to º º ; . be in the CS register. º º º º Device_Name DB "EMILY " ; The name of this particular device- º º ; . driver is EMILY. Name must be º º ; . 8 characters, so 3 blanks added. º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Address Save-Area ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ When PC-DOS calls the Strategy routine it passes the segment:offset address of the request-header in ES:BX. The Strategy routine saves this address in a 2-word storage area in EMILY. Request_Header_Offset DW (?) ; Define save-area pointers Request_Header_Segment DW (?) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Request-Headers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ PC-DOS can ask a device-driver to perform any of 16 services. Since each service has a corresponding request- header, it seems that there are 16 types of request- headers. It really isn't as bad as it sounds. Some of the services use the same request-header format and no device-driver ever implements all services. In fact, there are only 6 distinct types of request-headers. Furthermore, the first 13 bytes of all request-headers use the following format. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Request_Header_Preamble STRUC ³ ³ ³ ³ Header_Length DB (?) ; Length in bytes ³ ³ Unit_Code DB (?) ; Unit code ³ ³ Command_Code DB (?) ; Command code ³ ³ Status_Code DW (?) ; Status of the request ³ ³ PCDOS_Reserved DB 8 Dup (?) ; Reserved area for PC-DOS ³ ³ ³ ³ Request_Header_Preamble Ends ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Request_Header_Preamble is exactly that, a preamble. Data pertinent to the particular operation is appended to this preamble, making the request-header a variable-length structure. The length of the entire request-header is placed in the Header_Length field. The Unit_Code is used only by block devices and is not relevant here. The Command_Code field indicates which service is wanted by PC-DOS. For example, command-code 0 indicates the Init(ialization) service and command-code 6 the Input Status service. The Status_Code field reflects the completion-status of the requested operation. 15 9 8 7 0 ÚÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ E ³ ³ B ³ D ³ ³ ³ R ³ RESERVED ³ U ³ O ³ Error Code ³ ³ R ³ ³ S ³ N ³ (if bit 15 set) ³ ³ ³ ³ Y ³ E ³ ³ ÀÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Most services set bit 8 (the DONE flag) to 1 when the service has been completed. Bit 15 (the ERRor flag) is set if an error occurred during the service. In this case, the error-type is specified in bits 0-7, inclusive. For example, an error-code of 3 indicates an unknown command. The request-header preamble is followed by a data area containing data pertinent to the requested operation. These data areas occupy many pages in the PC-DOS manuals, adding to the confusion. The data area for the Init service (command-code 0) is shown below. Remember that the full request-header for the Init service consists of the Request_Header_Preamble followed by the Request_Header_Init. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Request_Header_Init STRUC ³ ³ ³ ³ Unit_Count DB (?) ; Used in service 0 ³ ³ Ending_Program_Addr DD (?) ; Used in service 0 ³ ³ Bios_Param_Block_Addr DD (?) ; Used in services 0 & 2 ³ ³ ³ ³ Request_Header_Init Ends ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ These fields will be examined later. I mentioned that there are only 6 unique types of request- header. Often, the same fields appear in different request-headers. The following usage-table shows this. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Media_Descriptor DB (?) ; Used in functions ³ ³ ; . 1, 2, 3, 4, 8, 9, 12 ³ ³ Data_Transfer_Address DD (?) ; Used in functions ³ ³ ; . 2, 3, 4, 8, 9, 12 ³ ³ Byte_Counter DW (?) ; Used in functions ³ ³ ; . 3, 4, 8, 9, 12 ³ ³ Starting_Sector DW (?) ; Used in functions ³ ³ ; . 3, 4, 8, 9, 12 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Media_Descriptor byte, for example, is used by services 1, 2, 3, 4, 8, 9, and 12. There is, in fact, a good deal of overlap. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Jump Table ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Next, we define a jump table. It's used to jump to the routine in the Interrupt routine responsible for implementing a particular service, the service corresponding to the command-code value. A jump-table fragment is shown next. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Device_Functions Label Word ³ ³ ³ ³ DW Offset Initialize_Driver_Command ;command-code = 0 ³ ³ DW Offset Media_Check_Command ;command-code = 1 ³ ³ DW Offset Build_BPB_Command ;command-code = 2 ³ ³ DW Offset IOCTL_Input_Command ;command-code = 3 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Essentially, the Interrupt routine takes the Command_Code value from the request-header and calculates the offset into the table by multiplying the Command_Code by 2. The routine whose address is at Device_Functions[2*Command_Code] is called. For example, if command-code = 1, the Media_Check_Command routine is called. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Strategy Routine ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The only function of the Strategy routine is to save the request-header address, passed by PC-DOS in ES:BX. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Strategy_Routine Proc Far ³ ³ ³ ³ Mov Word Ptr CS:Request_Header_Offset,BX ³ ³ ³ ³ Mov Word Ptr CS:Request_Header_Segment,ES ³ ³ ³ ³ Ret ³ ³ ³ ³ Strategy_Routine Endp ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ That's all, folks. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Interrupt Routine ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Interrupt routine does the real work of the driver. ÉÍÍ The Interrupt Routine ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º º º Interrupt_Routine Proc Far º º º º INRT01: º º Push AX ; Save the registers º º Push BX ; . and flags º º Push CX ; . º º Push DX ; . º º Push SI ; . º º Push DI ; . º º Push DS ; . º º Push ES ; . º º Pushf ; ..End º º INRT02: º º Push CS ; Set DS equal to CS º º Pop DS ; ..End º º º º Mov BX,Word Ptr CS:Request_Header_Offset º º Mov ES,Word Ptr CS:Request_Header_Segment º º ; Get the address of the º º ; . request-header into º º ; . ES:[BX] º º º º Mov Word Ptr ES:[BX].Status_Code,0000H º º ; Clear the status word º º º º Mov DX,0000H ; Calculate the index into º º Mov DL,Byte Ptr ES:[BX].Command_Code º º Shl DX,1 ; . the jump table and put º º Mov DI,DX ; . it into DI º º º º Cmp DI,0024D ; Compare for an invalid º º Jbe INRT03 ; command º º ; º º ; Here is the error routine for an illegal command º º ; º º Or Byte Ptr ES:[BX].Status_Code+1,80H º º ; Set the error bit º º Mov Byte Ptr ES:[BX].Status_Code,03H º º ; Indicate Unknown-command º º Jmp Short INRT90 º º º º INRT03: º º Mov SI,Word Ptr CS:Device_Functions[DI] º º ; Get the offset address º º ; . of the appropriate º º ; . command º º º º Call SI ; This is a near call to º º ; . the command º º º º INRT90: º º Popf ; Restore the registers º º Pop ES ; . and flags º º Pop DS ; . º º Pop DI ; . º º Pop SI ; . º º Pop DX ; . º º Pop CX ; . º º Pop BX ; . º º Pop AX ; ..End º º º º Ret º º º º Interrupt_Routine Endp º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ All registers are pushed, including the flags. It is extremely important that a driver save the flags because a driver may be called at any time by PC-DOS. PC-DOS uses the carry flag to indicate an error. If our driver accidentally sets the carry flag, then erroneous results may be returned by the PC-DOS service. The stack used by PC-DOS has enough room to save all of the registers and flags. If more room is required, allocate a local stack. For those commands which EMILY doesn't support, the ERRor flag in the Status_Code field of the request-header is set as follows. Mov Byte Ptr ES:[BX].Status_Code,03H ; Set Unknown-command This indicates an "Unknown command." ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Command-Code Values ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Next, we discuss some of the command-code values handled by EMILY. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Command-Code 0 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Command-code 0 corresponds to the Init, or initialization, service. The Init routine of a driver is called immediately after the driver is installed at boot-time. It is called only once to set up relevant device parameters, clear buffers, initialize values, etc. In short, Init prepares the device-driver for execution. For character devices it performs the following functions: o Initializes pertinent variables such as baud rate, etc. o Sets the ending address of the program o Sets the status word in the request-header The second task of the Init function is to set the ending address of the program, assisting PC-DOS memory management. A driver is viewed as being a permanent extension of PC-DOS, a so-called memory-resident program. For a COM module to acquire this status, a terminate-but-stay-resident call (Int 27h, or Int 21h with AH = 31h) must be used. This call should not be issued by the Init routine since PC-DOS assumes that a driver will be memory-resident. Instead, the Init routine places the end-of-driver address in the appropriate request-header field. This tells PC-DOS where it may safely load programs and data without overwriting the driver. The last task of the Init routine is to set the DONE and STATUS flags in the request-header. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Command-Codes: 3 and 12 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The I/O services of the device-driver (command codes 3, 4, 8, 9, and 12) perform the bulk of the driver's work. All such services use the same request-header format. The only difference is in the direction of data flow. Command-codes 3 and 12 correspond to IOCTL input and output, respectively. Some devices support the IOCTL (Input Output ConTroL) feature. IOCTL is used to send control-strings to a device. In other words, the IOCTL function allows data to be sent to and from a device without doing a normal input/output operation. This data could specify the baud rate or parity for a modem, or change modes of a printer. The IOCTL-input command (command-code = 3) is called only if the 14th bit of the attribute word is set. To illustrate IOCTL-input, EMILY puts the letter "E" into the transfer address in preparation for an I/O operation. ÉÍÍ IOCTL-Input Routine ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º º º IOCTL_Input_Command Proc Near º º º º Lds DI,ES:[BX].Data_Transfer_Address º º º º Mov Byte Ptr DS:[DI],'E' º º º º Mov Word Ptr ES:[BX].Byte_Counter,0001H º º ; # of bytes to transfer º º º º Or Word Ptr ES:[BX].Status_Code,0100H º º ; Set the finished bit º º º º Ret º º º º IOCTL_Input_Command Endp º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ This example assumes that ES:BX points to the request-header structure and that Byte_Counter and Status_Code are fields within that structure. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Command-Code: 5 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Command-code 5 corresponds to the Non-destructive input service. This service sends the next available character in a device's input buffer to the application program without removing it from the buffer. This allows a program to "look ahead" in the buffer by one character. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Command-Codes 7 & 11 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Command-codes 7 and 11 correspond to input and output flush, respectively. These calls empty the input queue on character devices and terminate all pending requests known to the driver. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ End of Code Segment ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The last step in EMILY is to define the end of the code segment and the end of the program. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ End_of_the_Program Equ $ ; The assembler sets the ³ ³ ; . variable to the last ³ ³ ; . valid address in the ³ ³ ; . program ³ ³ CSEG Ends ³ ³ End Device_Driver ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conclusion ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ A device-driver is a hardware-configuration-dependent software-tool that controls a peripheral device through a standard application-program interface. Neither COM nor EXE module, device-drivers are installed at boot-time and become a permanent extension of PC-DOS. They enhance PC-DOS, allowing it to control new hardware technology. Now it's your turn to study the complete version of EMILY in DRIVER1.ASM on Diskette B. While EMILY is a character device, it contains request-headers for each of the 16 possible PC-DOS services. Copyright 1985 by ABComputing March 15, 1985 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Installable Device-Drivers º º º º by º º º º Dan Rollins º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This article discusses the basics of device-drivers. For experienced programmers, the article under shows how to debug device-drivers. If you have been turned off by the complexity of the driver shown in the PC-DOS 2.0 manual, don't be. While a device-driver is complicated, it surely is within the reach of the average assembly-language programmer. I have even included a stripped-down device-driver in DRIVER.ASM on Diskette B for your convenience. It's functional and can be used as a skeleton for your own drivers. Now on to the topic... There is widespread interest and considerable mystery about installable device-drivers. By introducing this feature, Microsoft has opened broad new avenues for third-party hardware manufacturers, enabling manufacturers to easily attach their own custom devices to DOS. Device-drivers are for everyone, not only hardware vendors. In fact, a device-driver need not drive any physical equipment! One such pseudo-device driver is a RAM disk. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Examples of Drivers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ It's easy to think of several special-purpose device- drivers. Some are listed below. o The ANSI.SYS driver supplied on the PC-DOS 2.0 diskette drives both the keyboard and the screen. o A console-driver that can save the last 16K of data displayed on the screen, scroll back through the data, and re-use previously entered DOS commands. o A printer-driver that works as a background print- spooler. o A special printer-driver for each printer attached to your system, set-up so the same ESC sequences can be used for any printer in the system. You could make an NEC driver that emulates the EPSON command sequences. o A "smart" printer-driver that accepts commands to set the page length, margin sizes, headers, and footers. o A MODEM device-driver (for an RS-232 port) that emulates the Hayes Smartmodem. o A device-driver that forces the user to enter a password before continuing with DOS. (Device-drivers are installed before the AUTOEXEC batch file is executed, making it impossible to circumvent a driver with Ctrl-Break.) If these ideas don't forward-bias the LED in your head, you're in the wrong column! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Advantages of Drivers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ o If the instructions for creating a device-driver (connect A to B, B to C, this bone to that bone,...) are followed, a driver written under DOS 2.0 should work, without modification, under later DOS releases (assuming that the hardware itself hasn't changed!). Prior to DOS 2.x, manufacturers were forced to "patch" their equipment into the DOS. These patches were really kludges designed for a particular DOS release, and many wouldn't work under the next release. o Device-drivers eliminate the need for most patches. o Device-drivers follow a rigid structure, forcing the programmer to use a standard interface to DOS. This actually simplifies the programmer's job. o Device-drivers are officially recognized by DOS, meaning that the full resources of DOS, such as I/O redirection, can be used with driven devices. o Device-drivers can, if appropriate, receive control- codes which are not regarded as normal input. This is called IOCTL (Input Output ConTroL). IOCTL can be used, for example, to set the baud rate or parity of an async communication device. (The baud rate is control information, not normal input.) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Disadvantages of Drivers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ o Device-drivers cannot use DOS high-level calls to open or write to files. For the most part, a driver must avoid using any of the Int 21h DOS services. This is because DOS is not re-entrant. If a driver uses a DOS service, this causes a re-entry into DOS which may cause a lock-up. The only exceptions are services 1 through 0Ch, the lowest-level system I/O calls, with which DOS takes special effort to avoid re-entrancy problems. o Character-devices, those which process only individual characters, have an 8-byte device name. The name of the device must be different from the name of the assembly-language file in which it is contained. For, when installed, the device name becomes a DOS reserved word and any reference to a file having the same name will be ignored. You can't even edit a file having the same name as the device! (Don't believe it? Try editing a file named CON: or CON.) o Debugging a device-driver is a painful experience as the driver can't be loaded and executed under DEBUG. Your best approach is to do extensive bench-testing before making the first run. (And, of course, see my article on debugging device-drivers under .) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Device-Driver Installation ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Installing a driver involves several steps. We illustrate them by installing the driver in file DRIVER.ASM on Diskette B. (Note that it's named TESTDEV in the driver source-code.) 1. Assemble and link DRIVER.ASM. Ignore the "no stack" warning from the linker. 2. Process DRIVER.EXE with EXE2BIN like this: EXE2BIN driver.exe driver.sys The SYS extension is not mandatory; in fact, it doesn't matter at all. Just avoid using ".COM" or ".EXE", because the driver is NOT executable as a stand-alone program. 3. Add the DEVICE command to your CONFIG.SYS file. Here's one way of doing it: ECHO device=driver.sys >>config.sys (EDITOR'S NOTE: For your convenience, DRIVER.SYS is included on Diskette B.) 4. Reboot DOS to install the driver. To test-drive it, issue some commands like: ÉÍÍ Testing TESTDEV ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º A>COPY test.txt testdev (make sure that TEST.TXT is a short file) º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º A>COPY testdev con (use Ctrl-Z to stop the input process) º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º A>BASIC º º 10 OPEN "testdev" for output as #1 '** BASIC test routine º º 20 print #1,"this text" º º 30 CLOSE #1 º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Types of Device-Drivers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Device-drivers come in two flavors: character or block. A character-driver works only with individual characters, while block devices work with blocks of data. All mass storage devices (diskette drives, hard disks, RAM disks, and tape-backup systems) are block-devices while everything else is considered to be a character-device. Character-devices have an 8-byte (maximum) name such as CON, LPT1, or TESTDEV, while block-devices are associated with drive-letters, like A, B, or C. Block-devices are much more complicated than character- devices. For reasons of expediency, this article discusses only character-devices in any detail. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Device-Driver Structure ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The format of a device-driver program is more complex than any other standard type of program under DOS. This is to be expected, as a device-driver must be capable of representing any hardware or pseudo-device. A driver is not executed like a normal DOS program. It's installed by DOS automatically when the system is booted. Then the driver waits patiently for a specific DOS request. Missing from a driver are the typical entry-type code used in the beginning of standard modules and the standard exit code at the end. Despite the seeming complexity of a driver, all drivers are the same in one respect: they consist of three parts, a HEADER, a STRATEGY procedure, and an INTERRUPT procedure. ÉÍÍ Device-Driver Format ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º º ³ The Device HEADER ³ º º ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ º º ³ The STRATEGY procedure ³ º º ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ º º ³ The INTERRUPT procedure ³ º º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ The next several paragraphs offer a panoramic overview of how device-drivers are recognized and used by DOS. The three parts listed above will assume prominence in our discussion. When DOS is booted, it uses the "device=" statements found in CONFIG.SYS to create a list of devices attached to the system. The next-device field in a device's HEADER points to the next device, if any, in this list. (So the list is actually a linked-list or chain.) When you make a request of a device, DOS searches the chain of headers looking for the proper device. If the first device in the chain is not the requested one, DOS checks the next device, and if it is not the requested one, DOS checks the next member of the chain and so on until the device is found, or the last member of the chain is reached. When the device is found, the search ends, and with a sigh of relief DOS calls the driver's STRATEGY procedure. DOS passes it the address of a request-structure, called a request-header. The request-header contains information describing the service wanted by DOS. (This request-header is not related to the device HEADER. The device HEADER is a data area that appears at the beginning of a device-driver while the request-header is the structure DOS passes to the STRATEGY routine.) The STRATEGY routine saves the address of the request- header, and control returns to DOS. DOS then immediately passes control to the INTERRUPT procedure. This procedure accesses the request-header (using the address saved by the STRATEGY), determines which service was requested, and implements the request. If the request is successfully completed, a "done" flag is set, or if the request is not supported by the device, an error flag is set. This article will spiral back several times to build upon these themes, so you may want to reread the last few paragraphs. If you can't understand the overview, you will only be further confused by the details which follow. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Device HEADER ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The device HEADER is a special data area that precedes the code in a device-driver. Figure 1 diagrams the format of this area. Each box depicts one byte. ÉÍÍ Figure 1 - Device-Header Layout ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º Offset Field Description º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º ÖÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ· º º 00h º ³ º Pointer to next device º º ÓÄÄÄÁÄÄÄÁÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 04h º º Device Attribute º º ÓÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 06h º º Offset of device STRATEGY procedure º º ÓÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 08h º º Offset of device INTERRUPT procedure º º ÓÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ· º º 0Ah º º Device name º º ÓÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄĽ º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ In practice, the HEADER looks something like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ next_dev_ptr dd -1 ;this is the last (only) device defined ³ ³ dev_attribute dw 8000H ;bits indicate a character-device ³ ³ strategy_ptr dw strategy ;saves address of request-header ³ ³ interrupt_ptr dw interrupt ;the proc that handles function calls ³ ³ device_name db 'TESTDEV ' ;8-byte string of device name (note ³ ³ ; single blank after V in device name) ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Remember, this is an example of the very first bytes in a device-driver. There is no jump past this data area as with a COM-format program, and this area is not in a separate data segment as with an EXE program. Let's quickly look at the individual fields of the HEADER. The next-device pointer field normally contains FFFF:FFFF (a doubleword with the value of -1). The exception is when several devices are being installed in the same device- driver source-file. In that case, the first device-driver in the file points to the second device-driver in the file, the second points to the third, and so on until the last device is reached, which has a value of -1 in the next-device field. The Strategy_ptr and Interrupt_ptr fields contain the address of the STRATEGY and INTERRUPT procedures discussed earlier. These fields contain only the offset of those procedures. As a driver is contained in one segment, DOS assumes that the CS value of those procedures is the same as that of the driver. The final field, the device_name field occupies 8 bytes. For a character-device, it contains the left-justified name used to access the device. If a device-name requires fewer than 8 characters, the remaining bytes are blank-padded. For example, "LPT1xxxx", or "TESTDEVx", where x symbolizes a blank. The dev_attribute field in the HEADER is a word containing bit-significant values indicating various capabilities of the driver. This field is shown in Figure 2. ÉÍÍ Figure 2 - Device Attribute Bits ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º ÇÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º bit ³ value ³ description º ÇÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º 15 ³ 8000h ³ 1 = character device, 0 = block (disk) device º º 14 ³ 4000h ³ 1 = IOCTL is used, 0 is IOCTL is not used º º 13 ³ 2000h ³ 1 = non IBM format block device º º ³ ³ 0 = IBM format º º 12-4 ³ ³ reserved: always 0 º º 3 ³ 0008h ³ 1 = default CLOCK device, 0 = if not º º 2 ³ 0004h ³ 1 = default NUL device, 0 = if not º º 1 ³ 0002h ³ 1 = default Standard Output device, 0 = if notº º 0 ³ 0001h ³ 1 = default Standard Input device, 0 =if not º ÈÍÍÍÍÍÍÏÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ In the HEADER example shown beneath Figure 1, an attribute value of 8000H is used to indicate a character-oriented device. The DOS CON device has an attribute value of 8003h (=8000h + 2h + 1h), indicating that it's a character-device and also the default Standard Input and Output device. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The STRATEGY Procedure ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The second part of a device-driver is the STRATEGY procedure. DOS passes this procedure a doubleword pointer in ES:BX, pointing to the request-header discussed earlier. The STRATEGY saves this address in local variables for later use by the INTERRUPT procedure. This is the code of a typical STRATEGY procedure: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ strategy proc far ³ ³ ³ ³ mov cs:word ptr request_ptr,bx ³ ³ mov cs:word ptr request_ptr+2,es ³ ³ ret ;FAR return to DOS ³ ³ ³ ³ strategy endp ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ That's all. It's difficult to imagine why the name STRATEGY was selected, for its function is very limited. Presumably it will assume additional significance in a multi-tasking system or in a track-buffered block device-driver. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The INTERRUPT procedure ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The final and most important part of a device-driver is the INTERRUPT procedure. This procedure gets the address of the request-header (saved by STRATEGY), analyzes the information in that header, and acts accordingly. The information in the header indicates which of the 13 service requests, shown below in Figure 3, is wanted. (EDITOR'S NOTE: DOS 2.x has 13 services. DOS 3.0 has 16. Don't feel denied -- you have something to look forward to!) Since 13 services must be handled, the INTERRUPT procedure is the largest part of the device-driver. Fortunately, most drivers can ignore all but two or three services. For instance, an LPT1 driver does not need to process input requests (services 4,5,6, or 7), because printers are expected to be output-only devices. ÉÍÍ Figure 3 - Device-Driver Service Requests ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º ÇÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º No.³ Use ³ Name ³ Description º ÇÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º 0 ³ bc ³ init ³ initialize driver º º 1 ³ b ³ media check ³ media check (return disk format)º º 2 ³ b ³ build bpb ³ build BIOS Parameter Block º º 3 ³ i ³ ioctl input ³ IOCTL Input º º 4 ³ bc ³ input ³ read from device º º 5 ³ c ³ non-destruct input ³ Non-destructive, no-wait read º º 6 ³ c ³ input status ³ return status of input device º º 7 ³ c ³ input flush ³ flush (clear) the input buffer º º 8 ³ bc ³ output ³ write to the device º º 9 ³ bc ³ output verify ³ write and verify º º 10 ³ c ³ output status ³ return status of output device º º 11 ³ c ³ output flush ³ flush (clear) the output buffer º º 12 ³ i ³ ioctl output ³ IOCTL Output º ÇÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º b = block-devices (disk drives) º º c = character-devices º º i = devices that employ IOCTL (special input/output control) º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ The USE code shown in Figure 3 identifies which functions a device supports. For example, the INIT, or initialization code (number 0) is used by both block and character devices. The non-destructive input (number 5) is used only by character-devices. (Those familiar with the C language will recognize the heart of the "ungetc" function in this service. Ungetc puts a character back into the input stream, allowing for non-destructive input operations.) This figure also has an IOCTL category. The idea of IOCTL was mentioned earlier. IOCTL is used to send control- information to a driver to control the operation of a device. This control-information is viewed as being distinct from ordinary input processed by the driver. For example, the escape sequences sent to the ANSI.SYS driver are control-codes, not ordinary input like a keypress. As another example, you could use the IOCTL feature to clear the buffer of a print-spooler device. The IOCTL service is invoked using DOS function-call 44h. We can spend more time on that in another column. To summarize: A device-driver is composed of three parts: o the device HEADER, a specially formatted data area that is placed at the start of the driver program, o the STRATEGY procedure, a short piece of code that stores ES and BX in local variables, thereby saving the address of the request-header, and o the INTERRUPT procedure that interprets and handles the various requests made by DOS. A device-driver is installed by DOS. DOS sends it service requests, and the device-driver responds. Furthermore, most device-drivers only need to respond to a limited number of service requests, as defined by their nature (block or character, IOCTL or non-IOCTL, input-only or output-only). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ In Greater Depth ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ When DOS calls a device-driver, it passes a block of information (a data packet) called the request-header to the device-driver. The first 13 bytes of the request-header are always the same. Their format is shown below in Figure 4. The request-header contains a service request field, a place to return status information, and an area to hold data needed by the indicated request. ÉÍÍ Figure 4 - Request-Header Layout ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º Offset Field Description º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º ÖÄÄÄ· º º 00h º º Length of structure (incl. transaction data) º º ÓÄÄĽ º º ÖÄÄÄ· º º 01h º º Sub-unit (block-devices only: drive ID) º º ÓÄÄĽ º º ÖÄÄÄ· º º 02h º º Command (function requested: 0 - 0ch) º º ÓÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 03h º º Status code (error & done flags, etc.) º º ÓÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ· º º 05h º º 8 bytes reserved º º ÓÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄĽ º º ÖÄÄ Â Ä Â Ä Â ÄÄ· º º 0Dh º º transaction data (variable size) º º ÓÄÄ Á Ä Á Ä Á ÄĽ º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ The COMMAND byte contains the service-request code, a number from 0 to 12, described in Figure 3. This is how DOS communicates the service wanted to the driver. The status-code field contains two flags: ERROR and DONE. When a service has completed successfully, it sets the DONE flag in the header to communicate this information to DOS. If an error occurred, the ERROR flag is set additionally. This is how the device communicates with DOS. The "transaction" bytes (starting at offset 0Dh) come in several formats, depending upon the nature of the requested service. Some of the fields in the transaction area must be set by the driver, others are set by DOS. The PC-DOS 2.0 Manual shows the layout for each of the many possible transaction data areas, making the device-driver chapter long and seemingly unorganized. As a concrete example, Figure 5 shows the transaction data area for the INPUT and OUTPUT functions. ÉÍÍ Figure 5 - Input and Output Structure Layout ÍÍÍÍÍÍÍÍÍÍÍ» º º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º Offset Field Description º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º ÖÄ Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä· º º 00h º º Request Header º º ÓÄ Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä Ä½ º º ÖÄÄÄ· º º 0Dh º º Media Descriptor (Block-devices only) º º ÓÄÄĽ º º ÖÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ· º º 0Eh º ³ º DWORD Pointer to the Data Buffer º º ÓÄÄÄÁÄÄÄÁÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 12h º º Byte Count (Block-devices: Sector Count) º º ÓÄÄÄÁÄÄĽ º º ÖÄÄÄÂÄÄÄ· º º 14h º º Starting Sector (Block-devices only) º º ÓÄÄÄÁÄÄĽ º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ For INPUT and OUTPUT requests, the structure shown in Figure 5 IS the transaction-data area referred to in Figure 4. Get it? The request-header is created by "merging" Figures 4 and 5. Let's review the steps by which DOS communicates with a device-driver. The sequence is: o DOS calls the STRATEGY procedure, passing it the address of the request-header in ES and BX. o The STRATEGY procedure saves the address of the request-header (passed in ES and BX) in a local variable, a doubleword, and exits to DOS via a FAR return. o DOS calls the INTERRUPT procedure, passing no parameters to it. o The INTERRUPT procedure determines which function is being requested by reading the third byte of the request-header (the COMMAND byte.) The driver takes the appropriate actions, using information stored in the transaction bytes. (The transaction data varies from operation to operation.) o After handling the request, the INTERRUPT procedure sets the DONE flag in the request-header, or the ERROR flag, and returns control to DOS via a FAR return. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Source Code Details ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ First, suppose that the address of the request-header was saved by STRATEGY at CS:[REQUEST_PTR]. The INTERRUPT procedure gets this address as follows. les bx,cs:[request_ptr] ;get pointer to request structure This places the segment of the request-header into ES and the offset into BX. To determine which function was requested, we examine the COMMAND field at offset 2 in the header. (See Figure 4.) mov al,es:[bx+2] ;fetch the command code At this point we could use a test-and-jump sequence to execute the function. It might look something like this: ÚÄÄÄÄÄ ÄÄÄÄÄ¿ cmp al,0 ;INIT request? je init_fn ; yes, go do it cmp al,4 ;INPUT request? je input_fn ; yes, go do it cmp al,8 ;OUTPUT request? je output_fn ; yes, go do it err_exit: ; no, unknown command ...etc.) ÀÄÄÄÄÄ ÄÄÄÄÄÄÙ This is the simplest approach. However, DRIVER.ASM uses a function look-up table and jump table to dispatch control to the proper procedure. DRIVER.ASM has three procedures buried within the INTERRUPT procedure: INPUT_FN, OUTPUT_FN, and INIT_FN. All other requests are handled by setting the ERROR flag and returning to DOS. (Meaning that these services are not supported by this device.) o INPUT_FN handles input requests. It waits for a keystroke, displays it, and returns its value to DOS. o OUTPUT_FN handles output requests. It displays the character that was passed to it in the request-header. o INIT_FN is entirely different. It handles the DOS "initialize thyself" request. Again, in an async communications driver, this would involve setting up a default baud rate, etc. But in this sample driver, I just display a message. The last step of the INIT_FN returns the end-of-driver address, telling DOS where additional drivers or programs can be loaded. After each procedure is finished, control is passed to a COMMON_EXIT where the DONE flag in the request-header is set and the registers are restored to their original values. Control is then passed to DOS via a FAR return. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Closing Thoughts ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ At boot-time, a chain of device-drivers is created from the "device=" statements in CONFIG.SYS. The drivers listed in CONFIG.SYS are placed earlier in the chain than the standard DOS devices. When DOS searches through this chain looking for a driver, it stops at the first match. Suppose that CONFIG.SYS contains "device=CON.SYS", where CON.SYS is your own console-driver. Then at boot-time, DOS installs your driver not the standard DOS console-driver. The same trick is used by the ANSI.SYS driver to gain keyboard-video control. It may seem odd that a procedure as small as the STRATEGY is not combined with the INTERRUPT procedure. There are certain advantages to using this approach. For example, in a print-spooler, the STRATEGY can submit/cancel files while the INTERRUPT is used to print submitted files. This separation is, if nothing else, logically satisfying. To communicate with a driver, DOS creates a request-header instead of passing all information in registers. The latter approach is used by the Int 21h function calls. However, this approach would soon become very limiting. When the address of a data packet, like the request-header, is passed to a routine, the routine has gained total access to all fields in the packet. I predict that Microsoft (and IBM) will continue to move away from register-oriented parameters toward data-packet parameters. Aside from flexibility, data-packets are perfect for passing data on the stack and therefore make it easier to write re-entrant code. (EDITOR'S NOTE: The PC-DOS 3.0 print-spooler, Int 2fh, and the EXEC function-call 4Bh use data packets.) As long-winded as this article may seem, it has barely scratched the surface of the topic. Your best tutorial is to print out DRIVER.ASM and examine it as a whole. This will give you a firm grasp of how the pieces fit together and interact. You might also want to pick up a copy of my book, which covers device-drivers in greater detail. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Dan Rollins is a computer consultant and freelance technical ³ ³ writer. His book "IBM PC: 8088 MACRO Assembler Programming" ³ ³ was published by Macmillan in December of 1984. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Copyright 1985 by ABComputing March 15, 1985 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Debugging Device-Drivers º º º º by º º º º Dan Rollins º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Debugging device-drivers is a difficult task because a driver can't be loaded or executed under DEBUG in the normal manner. Since drivers are so peculiar and often require interactive debugging, I'll discuss two DEBUGging methods. Hopefully, one will appeal to you. Let me warn you, though, that the following discussion is not for the faint hearted. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The INTERRUPT Address ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Both methods need to determine the address of the driver's INTERRUPT procedure. This is no easy task, because DOS loads the driver without user intervention. The INIT_FN procedure in DRIVER.ASM on Diskette B offers a simple solution: it stores the address of the INTERRUPT procedure in a memory-variable at an absolute address. Thus, after booting, DEBUG can be used to examine that address and locate the INTERRUPT procedure. Placing a breakpoint at the start of INTERRUPT and invoking the driver causes DEBUG to catch the driver "in the act of being itself" and arrest its flight. In any case, the segment and offset of INTERRUPT are stored at address 0000:04f0, identified in the Technical Reference Manual as the "interapplication communication area." Using DEBUG to dump 4f0:14 could give: -d 0:4f0 14 0000:04f0 12 34 56 78 Since address 0:04f0 contains the bytes 12 34 56 78, the INTERRUPT procedure starts at 7856:3412. (Recall that the 8088 stores "backwords" in memory.) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Method 1 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Record the value of the first byte in the INTERRUPT procedure on a sheet of paper. Use DEBUG's E(dit) command to replace that byte by 0CCh, creating an INT 3 breakpoint at the start of INTERRUPT. Next, use DEBUG's N(ame) command to specify the device- driver, and send it data using the W(rite) command. For example: ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º -a 100 º º 849E:0100 db 'this is the stuff I will write',0d,0a º º 849E:0120 º º º º -n testdev {select device for the file} º º º º -rcx º º CX 0000 º º :20 {now the W(rite) command will write 20H bytes) º º º º -w º º Writing 0020 bytes º º º º AX=0000 BX=0000 CX=0120 DX=0000 SP=00FE BP=0000 SI=0000 DI=0000 º º DS=0BD5 ES=0BD5 SS=0BE7 CS=7865 IP=3421 NV UP DI PL ZR NA PE NC º º 7865:3421 CC INT 3 º º - º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ The DEBUG register-display appears, indicating that a breakpoint has been reached. Replace the breakpoint opcode by the original value of that byte recorded on the sheet of paper. We can now study the driver in its natural habitat and single-step through the code. ("Ah ha!" he exclaims, "We forgot to use a CS: override to access that data!" Well, that's what handwaving is all about.) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Method 2 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This method uses the same steps to determine the start of the INTERRUPT procedure, but a different approach to reaching the breakpoint. Let's assume that INTERRUPT starts at 7856:3412. DEBUG is used to load a secondary copy of COMMAND.COM. But when the secondary copy is executed, a breakpoint is set at the start of INTERRUPT. For example: ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º º º -n command.com º º -l º º -g 7856:3412 º º º º º º The IBM Personal Computer DOS Version 2.10 (C)Copyright IBM º º Corp 1981, 1982, 1983 º º º º º º C>_ º º º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Don't be fooled -- we haven't "returned to DOS." The secondary copy of COMMAND.COM is currently executing under DEBUG, and the driver still contains a breakpoint. We need only to invoke the driver to return to DEBUG and trace the driver's code. For instance: C>copy testfile.txt testdev AX=0000 BX=0000 CX=0120 DX=0000 SP=00FE BP=0000 SI=0000 DI=0000 DS=0BD5 ES=0BD5 SS=0BE7 CS=7865 IP=3421 NV UP DI PL ZR NA PE NC 7865:3421 50 PUSH AX - brings us back into DEBUG, with CS:IP set to the first byte of the INTERRUPT procedure. We can single-step, unassemble, etc. There is one VERY CRITICAL detail to watch out for now. DO NOT use DEBUG's Q(uit) command -- DEBUG is installed beneath the secondary copy of COMMAND.COM. (Beneath means both beneath in memory and as a subtask.) When we are satisfied that all is well, we can either do a Ctrl-Alt-Del (system reset) or issue a G(o) command (setting another breakpoint if desired). Finally, returning to the secondary copy of COMMAND.COM, the ill-documented DOS EXIT command is used: C>exit program terminated normally - and we're back in DEBUG. The program which terminated was the secondary copy of COMMAND.COM, so it's now safe to exit from DEBUG. This method is both simpler (fewer steps) and more complex (further removed from normal debugging practices) than the first method. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Dan Rollins is a computer consultant and freelance technical ³ ³ writer. His book "IBM PC: 8088 MACRO Assembler Programming" ³ ³ was published by Macmillan in December of 1984. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ