Chapter 3
PQXT Clock Device Driver, OS$CLOCK

This chapter discusses OS$CLOCK that is used by the PQXT in place of the standard MS-DOS clock device driver used in desktop PCs.

General Description

OS$CLOCK is an installable character device driver that the PQXT uses in place of the standard MS-DOS clock device driver. It provides an interface between the MS-DOS operating system and the normal BIOS clock services.

In order to keep code size at a minimum, OS$CLOCK only supports clock services for an XT, although it does provide extra features to make the XT clock services behave more like those of an AT. The AT system provides a real-time clock, while the XT system normally does not.

The XT system has two basic problems in dealing with the date and time:

  1. Since an XT system has no real-time clock, it loses track of the time and date during a warm boot.
  2. An XT system loses date increments if two or more midnight rollovers occur while the system is off.
The OS$CLOCK device driver overcomes both of these problems by re-vectoring the keyboard and timer tick interrupts to itself, performing certain operations, then returning control to the standard keyboard and timer tick interrupt handlers.

The "warm-boot" problem is solved by re-vectoring the keyboard interrupt handler (Int 15, Function 4FH) to the OS$CLOCK device driver where a check is made for the warm-boot Ctrl-Alt-Del key sequence. If this key sequence is detected, the current time and date is stored in an unused BIOS data area. When the OS$CLOCK initialize function is called, it reads back the time and date information, and properly updates the time and date. More detail on the keyboard interrupt handler is given later.

The "date loss" problem is solved by re-vectoring the timer tick interrupt handler (Int 1CH) to the OS$CLOCK device driver, where it checks for a midnight rollover, and adjusts the date count appropriately. The date information is stored in a word as the number of days elapsed since January 1, 1980. This information is normally kept by the standard device driver -- OS$CLOCK keeps its own version of this value. More detail on the timer tick interrupt handler is given later.

Listed below are some of the other salient features of OS$CLOCK:

OS$CLOCK Device Driver Structure

As mentioned previously, OS$CLOCK is a character device driver, which means that all communication between MS-DOS and the driver is performed on a character-by-character basis. Requests for input of a fixed number of characters are passed directly to the driver from MS-DOS and the return to MS-DOS is made up of characters from the driver as a result of performing a particular function as directed by MS-DOS.

The OS$CLOCK driver is made up of three parts:

Device Header

The device header provides important information to MS-DOS on how to find and use the device driver. It consists of an 18-byte area divided into five fields, as follows: As MS-DOS initializes itself, it establishes a chain of the standard device drivers; the Next Driver Pointer in each driver gives the address of the next driver in the chain. The last driver in the chain has a pointer of -1 to indicate the end of the chain.

Strategy Routine

The purpose of the strategy routine within OS$CLOCK is to "remember" in memory where the operating system has assigned the location of the device's request header (RH). The RH serves two main purposes: When a driver is about to be called, MS-DOS builds the request header into a reserved area of memory and its address is passed to the strategy routine in ES:BX. Request headers vary in length but always have a fixed 13-byte header as shown in Table 3-1.

Table 3-1 Request Header Leading Bytes

        BYTE   FIELD    MEANING
        OFFSET LENGTH
        --------------------------------------------------------------------
        00H    Byte     Length of the request header.

        01H    Byte     Unit Code: the device number for block devices
                        (OS$CLOCK is a character device).

        02H    Byte     Command Code: the number of the most recent command
                        sent to the driver.

        03H    Word     Status: status code set by the driver after each call.
                        If bit 15 is set, an error code exists in the lower
                        byte.  A status code of 0 indicates a successful
                        completion.  The status bytes are listed below:

                              Bits
                        FEDCBA98 76543210 Meaning
                        -------- -------- ---------------------------------
                        1....... 00000001 Write-protect violation error
                        1....... 00000010 Unknown unit error
                        1....... 00000011 Drive not ready error
                        1....... 00000100 CRC error
                        1....... 00000101 Bad drive request structure len.
                        1....... 00000110 Seek error
                        1....... 00000111 Unknown media error
                        1....... 00001000 Sector not found error
                        1....... 00001001 Printer out of paper error
                        1....... 00001010 Write fault
                        1....... 00001011 Read fault
                        1....... 00001100 General failure
                        .......x ........ Done
                        ......x. ........ Busy
                        .xxxxx.. ........ Reserved
                        0....... ........ No error

        05H    8 Bytes  Reserved for use by DOS
        0DH    Variable Data required by the driver
The error bit (bit 15) in the status word is set to indicate that an error occurred in the operation of the driver. The error code is returned in the lower 8 bits of the status word.

The busy bit is set if the device driver was busy when called. The done bit is set when the OS$CLOCK has completed its operation.

Interrupt Routine

The major portion of OS$CLOCK is the interrupt routine. It is this portion that actually contains the code that performs the desired functions and acts on the command code field of the request header.

The interrupt routine is poorly named; a device driver is actually invoked with a call from MS-DOS, and returns with a RET (return from call), not an IRET (return from interrupt). So it is not really an interrupt routine. Original plans to have a device driver interrupt MS-DOS when it could take care of its next task have not yet materialized.

Once called, it begins by saving the present machine state on the stack, which is typical of called routines. Then the request header is retrieved from the location at which the strategy routine stored it. The OS$CLOCK device driver then determines the function it is supposed to perform by examining the byte at offset 02H into the request header. A check is made to ensure the command is legal; if it is, the interrupt routine branches to the code location that handles that function. If the command is illegal (larger than the maximum command number) the driver returns with an error flag to indicate that the command was unknown.

OS$CLOCK Device Driver Functions

As mentioned previously, the attribute word in the device header has bit 15 set to indicate that it is a character device, and bit 3 set to indicate that it is to be used as the clock driver. Based on these bit settings, MS-DOS will use OS$CLOCK as the clock device driver instead of the standard driver.

OS$CLOCK supports the functions defined by the command codes listed in Table 3-2.

Table 3-2 Command Codes Supported

        Code    Function
        ------------------------------------
        00H     Initialize
        01H     Media Check (1)
        02H     Build BPB (1)
        04H     Read
        05H     Nondestructive Read
        06H     Input Status (1)
        07H     Flush Input Buffers (1)
        08H     Write
        09H     Write with Verify
        0AH     Output Status (1)
        0BH     Flush Output Buffers (1)

        Note: 1. These command codes do nothing but set
              the 'done' bit in the status word and return
Any other command code given to OS$CLOCK results in the 'done' and 'error' bits being set in the upper byte of the status word, and an error code of 03H being returned in the lower byte, indicating that the command was unknown.

Function 00H - Initialize

A command code of 00H performs an initialization of OS$CLOCK. One task of the initialization function is to place the address of the end of the driver code into byte offset 0EH of the request header. This address is essential to MS-DOS operation.

The request header contains the following bytes on entry to the initialize function:

        Request Header  Length  Description
        Offset
        ------------------------------------------------------------------
        02H             byte    command code (OOH)
        12H             dword   segment, offset of CONFIG.SYS command line
        16H             byte    next available MS-DOS drive number
The request header contains the following bytes on returning from the initialize function:
        Request Header  Length  Description
        Offset
        ------------------------------------------------------------------
        03H             word    status
        ODH             byte    number of units
        OEH             dword   segment, offset of end of driver
        12H             dword   segment, offset of BIOS parameter block (BPB)
The initialize function code checks the CONGIG.SYS command line dealing with OS$CLOCK If it finds a /D switch, the version number and copyright message is displayed, as follows:
	OS$CLOCK Clock device driver V1.00
	Copyright (c) 1990 Poqet Computer Corp.
	All rights reserved
As part of the initialization, the OS$CLOCK device driver code also checks the interrupt communication area (ICA) located at 40:00F0H to find out if it contains valid date and time information. The validity of the data is verified by using a checksum byte. The format of the stored data should be as follows:
        Segment:Offset  Length  Description
        ------------------------------------
        40:00F0H        word    date
        40:00F2H        dword   timer ticks
        40:00F6H        byte    checksum
If the checksum is valid for the data given, the date is stored in a word within the OS$CLOCK driver. The timer ticks are read from the BIOS Int 1AH, service 00H. Timer ticks are returned in CX and DX. The timer ticks count located at 40:00F2H is added to the value in CX and DX, plus an adjustment to allow for the time the timer interrupt was disabled during power-on reset. The result is used to set a new time using BIOS Int 1AH, service 01H. When the two times are added together, a check is made for rollover. If rollover occurs, the date will be incremented.

Rollover occurs when the tick count is greater than or equal to 001800B0H. This value is equal to the total number of clock ticks in a 24-hour period. After a new time is set, the checksum at 40:00F6H is inverted to indicate that the data is no longer valid.

Before a return to MS-DOS is executed, a call is made to the extended BIOS Int 66H, function 02H, service 06H, to disable the UART power supply. This saves system power when the serial port is not being used.

After the UART is powered down, the 'done' bit in the status word is set, the current code segment and offset are loaded into the request header at offset 02H, and the initialize function returns to MS-DOS. The segment and offset values point to the start of the initialization routine. This is because it is executed only once at boot-up. The initialization routine is placed at the end driver so that the memory it occupies can be released after the code finishes executing.

The number of units and the BPB segment and offset are not used by MS-DOS for OS$CLOCK.

Function 04H - Read

A command code of 04H reads the timer ticks using BIOS Int 1A service 00H. The ticks count is converted into four bytes representing hours, minutes, seconds and hundredths of seconds. These four bytes, along with the date word, are written into the transfer address at 0EH of the entry request header. The total number of bytes transferred, 06H, is returned at offset 12H of the request header.

The request header contains the following bytes on entry to the read function:

        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        02H             byte    command code (04H)
        0EH             dword   transfer address
        12H             word    number of bytes to transfer
The request header contains the following bytes on returning from the read function:
        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        03H             word    status
        12H             word    number of bytes actually transferred
The six bytes that are transferred are in the following format:
        Byte Number     Description
        ------------------------------------------
        +00             date (low byte)
        +01             date (high byte)
        +02             minutes
        +03             hours
        +04             seconds
        +05             hundredths of seconds

Function 05H - Nondestructive Read

The request header contains the following bytes on entry to the nondestructive read function:
        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        02H             byte    command code (05H)
The request header contains the following bytes on returning from the nondestructive read function:
        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        03H             word    status
The result is simply that the 'busy' and 'done' bits are set in the status word.

Function 08H - Write

The address given at address 0EH in the entry request header points to a string of six bytes which contain the date and time. These bytes are used to set the date and time, and have the same format as the bytes in function 04H. Function 08H reads the date and time information as pointed to by the request header, and writes these bytes into the OS$CLOCK device driver date count variable. OS$CLOCK then reads the time, converts it into timer ticks, and stores it using BIOS Int 1AH, service 01H.

The number of bytes to be read, then written, is contained in offset 12H of the request header. The function returns to MS-DOS after setting the 'done' bit in the status word.

The request header contains the following bytes on entry to the write function:

        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        02H             byte    command code (08H)
        0EH             dword   transfer address
        12H             word    number of bytes to transfer
The request header contains the following bytes on returning from the write function:
        Request Header  Length  Description
        Offset
        -------------------------------------------------------
        03H             word    status
        12H             word    number of bytes actually transferred

Function 09H - Write With Verify

This performs in the same manner as function 08H.

Interrupt Handlers

The following paragraphs briefly describe each of the following interrupt handlers:

Interrupt 08H - Timer Tick

During execution of the initialize function, the interrupt vector for the timer tick interrupt routine is initialized to point to OS$CLOCK. The original interrupt vector is stored in the OS$CLOCK data area. When timer tick is invoked, execution jumps to OS$CLOCK and a far call is made to the original interrupt handler. After the timer tick interrupt is finished executing, execution returns to OS$CLOCK. At this point the state of the rollover flag is checked. The flag is stored in bit 0 at 40:0070. If it is set to 1, a midnight rollover has occurred, and OS$CLOCK responds by updating its date count word.

Control is then returned to MS-DOS through an IRET instruction, as though program execution is returning from the timer tick interrupt.

Interrupt 09H - Keyboard

During execution of the initialize function, the interrupt vector for the keyboard interrupt routine is initialized to point to OS$CLOCK. The original interrupt vector is stored in the OS$CLOCK data area. When the keyboard interrupt is invoked, execution jumps to OS$CLOCK.

The status of the Ctrl and Alt keys are checked by examining byte 40:0017. If the Ctrl key is depressed, bit 2 will be set. If the Alt key is depressed, bit 3 will be set. If both keys are depressed, OS$CLOCK reads the scan code from I/O port 60H. If the scan code is 53H, indicating the Del key is depressed, a warm boot is about to occur.

If this is the case, OS$CLOCK restores the original Int 09H interrupt vector and reads the time by using BIOS Int 1A, service 0. This value is then stored in the ICA together with the current value of the date count. A checksum is computed on the first 6 bytes in the ICA, and the result is stored in the next byte. The locations and format of the data in the ICA is as described previously in the initialize function.

A far jump is then made to the original keyboard interrupt vector -- this occurs whether a CTRL-ALT-DEL sequence is detected or not.


Copyright (c) 1989, 1990, 1991, 1992 Poqet Computer Corporation. All rights reserved.
Filename: PoqetPC/docs/poqetpc/techref/chapter3.html
Date Created: 12 Feb 96, Last Modified: 13 Feb 96
Created by Bryan Mason - E-Mail: poqetpc<at>bmason<dot>com