The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty (Part 1)

Table of Content

Chapter Twenty (Part 3) 

CHAPTER TWENTY:
THE PC KEYBOARD (Part 2)
20.2 - The Keyboard Hardware Interface
20.2 The Keyboard Hardware Interface

IBM used a very simple hardware design for the keyboard port on the original PC and PC/XT machines. When they introduced the PC/AT, IBM completely resigned the interface between the PC and the keyboard. Since then, almost every PC model and PC clone has followed this keyboard interface standard. Although IBM extended the capabilities of the keyboard controller when they introduced their PS/2 systems, the PS/2 models are still upwards compatible from the PC/AT design. Since there are so few original PCs in use today (and fewer people write original software for them), we will ignore the original PC keyboard interface and concentrate on the AT and later designs.

There are two keyboard microcontrollers that the system communicates with - one on the PC's motherboard (the on-board microcontroller) and one inside the keyboard case (the keyboard microcontroller). Communication with the on-board microcontroller is through I/O port 64h. Reading this byte provides the status of the keyboard controller. Writing to this byte sends the on-board microcontroller a command. The organization of the status byte is

Communication to the microcontroller in the keyboard unit is via the bytes at I/O addresses 60h and 64h. Bits zero and one in the status byte at port 64h provide the necessary handshaking control for these ports. Before writing any data to these ports, bit zero of port 64h must be zero; data is available for reading from port 60h when bit one of port 64h contains a one. The keyboard enable and disable bits in the command byte (port 64h) determine whether the keyboard is active and whether the keyboard will interrupt the system when the user presses (or releases) a key, etc.

Bytes written to port 60h are sent to the keyboard microcontroller and bytes written to port 64h are sent to the on-board microcontroller. Bytes read from port 60h generally come from the keyboard, although you can program the on-board microcontroller to return certain values at this port, as well. The following tables lists the commands sent to the keyboard microcontroller and the values you can expect back. The following table lists the allowable commands you can write to port 64h:

On-Board Keyboard Controller Commands (Port 64h)
Value (hex)

Description

20 Transmit keyboard controller's command byte to system as a scan code at port 60h.
60 The next byte written to port 60h will be stored in the keyboard controller's command byte.
A4 Test if a password is installed (PS/2 only). Result comes back in port 60h. 0FAh means a password is installed, 0F1h means no password.
A5 Transmit password (PS/2 only). Starts receipt of password. The next sequence of scan codes written to port 60h, ending with a zero byte, are the new password.
A6 Password match. Characters from the keyboard are compared to password until a match occurs.
A7 Disable mouse device (PS/2 only). Identical to setting bit five of the command byte.
A8 Enable mouse device (PS/2 only). Identical to clearing bit five of the command byte.
A9 Test mouse device. Returns 0 if okay, 1 or 2 if there is a stuck clock, 3 or 4 if there is a stuck data line. Results come back in port 60h.
AA Initiates self-test. Returns 55h in port 60h if successful.
AB Keyboard interface test. Tests the keyboard interface. Returns 0 if okay, 1 or 2 if there is a stuck clock, 3 or 4 if there is a stuck data line. Results come back in port 60h.
AC Diagnostic. Returns 16 bytes from the keyboard's microcontroller chip. Not available on PS/2 systems.
AD Disable keyboard. Same operation as setting bit four of the command register.
AE Enable keyboard. Same operation as clearing bit four of the command register.
C0 Read keyboard input port to port 60h. This input port contains the following values:

bit 7: Keyboard inhibit keyswitch (0 = inhibit, 1 = enabled).

bit 6: Display switch (0=color, 1=mono).

bit 5: Manufacturing jumper.

bit 4: System board RAM (always 1).

bits 0-3: undefined.
C1 Copy input port (above) bits 0-3 to status bits 4-7. (PS/2 only)
C2 Copy input pot (above) bits 4-7 to status port bits 4-7. (PS/2 only).
D0 Copy microcontroller output port value to port 60h (see definition below).
D1 Write the next data byte written to port 60h to the microcontroller output port. This port has the following definition:

bit 7: Keyboard data.

bit 6: Keyboard clock.

bit 5: Input buffer empty flag.

bit 4: Output buffer full flag.

bit 3: Undefined.

bit 2: Undefined.

bit 1: Gate A20 line.

bit 0: System reset (if zero).

 

Note: writing a zero to bit zero will reset the machine.

Writing a one to bit one combines address lines 19 and 20 on the PC's address bus.
D2 Write keyboard buffer. The keyboard controller returns the next value sent to port 60h as though a keypress produced that value. (PS/2 only).
D3 Write mouse buffer. The keyboard controller returns the next value sent to port 60h as though a mouse operation produced that value. (PS/2 only).
D4 Writes the next data byte (60h) to the mouse (auxiliary) device. (PS/2 only).
E0 Read test inputs. Returns in port 60h the status of the keyboard serial lines. Bit zero contains the keyboard clock input, bit one contains the keyboard data input.
Fx Pulse output port (see definition for D1). Bits 0-3 of the keyboard controller command byte are pulsed onto the output port. Resets the system if bit zero is a zero.

Commands 20h and 60h let you read and write the keyboard controller command byte. This byte is internal to the on-board microcontroller and has the following layout:

The system transmits bytes written to I/O port 60h directly to the keyboard's microcontroller. Bit zero of the status register must contain a zero before writing any data to this port. The commands the keyboard recognizes are

Keyboard Microcontroller Commands (Port 60h)

Value (hex)

Description

ED Send LED bits. The next byte written to port 60h updates the LEDs on the keyboard. The parameter (next) byte contains:

bits 3-7: Must be zero.

bit 2: Capslock LED (1 = on, 0 = off).

bit 1: Numlock LED (1 = on, 0 = off).

bit 0: Scroll lock LED (1 = on, 0 = off).
EE Echo commands. Returns 0EEh in port 60h as a diagnostic aid.
F0 Select alternate scan code set (PS/2 only). The next byte written to port 60h selects one of the following options:

00: Report current scan code set in use (next value read from port 60h).

01: Select scan code set #1 (standard PC/AT scan code set).

02: Select scan code set #2.

03: Select scan code set #3.
F2 Send two-byte keyboard ID code as the next two bytes read from port 60h (PS/2 only).
F3 Set Autorepeat delay and repeat rate. Next byte written to port 60h determines rate:

bit 7: must be zero

bits 5,6: Delay. 00- 1/4 sec, 01- 1/2 sec, 10- 3/4 sec, 11- 1 sec.

bits 0-4: Repeat rate. 0- approx 30 chars/sec to 1Fh- approx 2 chars/sec.
F4 Enable keyboard.
F5 Reset to power on condition and wait for enable command.
F6 Reset to power on condition and begin scanning keyboard.
F7 Make all keys autorepeat (PS/2 only).
F8 Set all keys to generate an up code and a down code (PS/2 only).
F9 Set all keys to generate an up code only (PS/2 only).
FA Set all keys to autorepeat and generate up and down codes (PS/2 only).
FB Set an individual key to autorepeat. Next byte contains the scan code of the desired key. (PS/2 only).
FC Set an individual key to generate up and down codes. Next byte contains the scan code of the desired key. (PS/2 only).
FD Set an individual key to generate only down codes. Next byte contains the scan code of the desired key. (PS/2 only).
FE Resend last result. Use this command if there is an error receiving data.
FF Reset keyboard to power on state and start the self-test.

The following short program demonstrates how to send commands to the keyboard's controller. This little TSR utility programs a "light show" on the keyboard's LEDs.

; LEDSHOW.ASM
;
; This short TSR creates a light show on the keyboard's LEDs. For space
; reasons, this code does not implement a multiplex handler nor can you
; remove this TSR once installed. See the chapter on resident programs
; for details on how to do this.
;
; cseg and EndResident must occur before the standard library segments!

cseg            segment para public 'code'
cseg            ends

; Marker segment, to find the end of the resident section.

EndResident     segment para public 'Resident'
EndResident     ends

                .xlist
                include         stdlib.a
                includelib      stdlib.lib
                .list


byp             equ     <byte ptr>

cseg            segment para public 'code'
                assume  cs:cseg, ds:cseg



; SetCmd-       Sends the command byte in the AL register to the 8042
;               keyboard microcontroller chip (command register at
;               port 64h).

SetCmd          proc    near
                push    cx
                push    ax              ;Save command value.
                cli                     ;Critical region, no ints now.

; Wait until the 8042 is done processing the current command.

                xor     cx, cx          ;Allow 65,536 times thru loop.
Wait4Empty:     in      al, 64h         ;Read keyboard status register.
                test    al, 10b         ;Input buffer full?
                loopnz  Wait4Empty      ;If so, wait until empty.

; Okay, send the command to the 8042:

                pop     ax              ;Retrieve command.
                out     64h, al
                sti                     ;Okay, ints can happen again.
                pop     cx
                ret
SetCmd          endp




; SendCmd-      The following routine sends a command or data byte to the
;               keyboard data port (port 60h).

SendCmd         proc    near
                push    ds
                push    bx
                push    cx
                mov     cx, 40h
                mov     ds, cx
                mov     bx, ax          ;Save data byte

                mov     al, 0ADh        ;Disable kbd for now.
                call    SetCmd

                cli                     ;Disable ints while accessing HW.

; Wait until the 8042 is done processing the current command.

                xor     cx, cx          ;Allow 65,536 times thru loop.
Wait4Empty:     in      al, 64h         ;Read keyboard status register.
                test    al, 10b         ;Input buffer full?
                loopnz  Wait4Empty      ;If so, wait until empty.

; Okay, send the data to port 60h

                mov     al, bl
                out     60h, al

                mov     al, 0AEh        ;Reenable keyboard.
                call    SetCmd
                sti                     ;Allow interrupts now.

                pop     cx
                pop     bx
                pop     ds
                ret
SendCmd         endp


; SetLEDs-      Writes the value in AL to the LEDs on the keyboard.
;               Bits 0..2 correspond to scroll, num, and caps lock,
;               respectively.

SetLEDs         proc    near
                push    ax
                push    cx

                mov     ah, al          ;Save LED bits.

                mov     al, 0EDh        ;8042 set LEDs cmd.
                call    SendCmd         ;Send the command to 8042.
                mov     al, ah          ;Get parameter byte
                call    SendCmd         ;Send parameter to the 8042.

                pop     cx
                pop     ax
                ret
SetLEDs         endp



; MyInt1C-      Every 1/4 seconds (every 4th call) this routine
;               rotates the LEDs to produce an interesting light show.

CallsPerIter    equ     4
CallCnt         byte    CallsPerIter
LEDIndex        word    LEDTable
LEDTable        byte    111b, 110b, 101b, 011b,111b, 110b, 101b, 011b
                byte    111b, 110b, 101b, 011b,111b, 110b, 101b, 011b
                byte    111b, 110b, 101b, 011b,111b, 110b, 101b, 011b
                byte    111b, 110b, 101b, 011b,111b, 110b, 101b, 011b

                byte    000b, 100b, 010b, 001b, 000b, 100b, 010b, 001b
                byte    000b, 100b, 010b, 001b, 000b, 100b, 010b, 001b
                byte    000b, 100b, 010b, 001b, 000b, 100b, 010b, 001b
                byte    000b, 100b, 010b, 001b, 000b, 100b, 010b, 001b

                byte    000b, 001b, 010b, 100b, 000b, 001b, 010b, 100b
                byte    000b, 001b, 010b, 100b, 000b, 001b, 010b, 100b
                byte    000b, 001b, 010b, 100b, 000b, 001b, 010b, 100b
                byte    000b, 001b, 010b, 100b, 000b, 001b, 010b, 100b

                byte    010b, 001b, 010b, 100b, 010b, 001b, 010b, 100b
                byte    010b, 001b, 010b, 100b, 010b, 001b, 010b, 100b
                byte    010b, 001b, 010b, 100b, 010b, 001b, 010b, 100b
                byte    010b, 001b, 010b, 100b, 010b, 001b, 010b, 100b

                byte    000b, 111b, 000b, 111b, 000b, 111b, 000b, 111b
                byte    000b, 111b, 000b, 111b, 000b, 111b, 000b, 111b
                byte    000b, 111b, 000b, 111b, 000b, 111b, 000b, 111b
                byte    000b, 111b, 000b, 111b, 000b, 111b, 000b, 111b
TableEnd                equ     this byte

OldInt1C        dword   ?

MyInt1C         proc    far
                assume  ds:cseg

                push    ds
                push    ax
                push    bx

                mov     ax, cs
                mov     ds, ax

                dec     CallCnt
                jne     NotYet
                mov     CallCnt, CallsPerIter   ;Reset call count.
                mov     bx, LEDIndex
                mov     al, [bx]
                call    SetLEDs
                inc     bx
                cmp     bx, offset TableEnd
                jne     SetTbl
                lea     bx, LEDTable
SetTbl:         mov     LEDIndex, bx
NotYet:         pop     bx
                pop     ax
                pop     ds
                jmp     cs:OldInt1C
MyInt1C         endp


Main            proc

                mov     ax, cseg
                mov     ds, ax

                print
                byte    "LED Light Show",cr,lf
                byte    "Installing....",cr,lf,0

; Patch into the INT 1Ch interrupt vector. Note that the
; statements above have made cseg the current data segment,
; so we can store the old INT 1Ch values directly into
; the OldInt1C variable.

                cli                             ;Turn off interrupts!
                mov     ax, 0
                mov     es, ax
                mov     ax, es:[1Ch*4]
                mov     word ptr OldInt1C, ax
                mov     ax, es:[1Ch*4 + 2]
                mov     word ptr OldInt1C+2, ax
                mov     es:[1Ch*4], offset MyInt1C
                mov     es:[1Ch*4+2], cs
                sti                             ;Okay, ints back on.


; We're hooked up, the only thing that remains is to terminate and
; stay resident.

                print
                byte    "Installed.",cr,lf,0

                mov     ah, 62h                 ;Get this program's PSP
                int     21h                     ; value.

                mov     dx, EndResident         ;Compute size of program.
                sub     dx, bx
                mov     ax, 3100h               ;DOS TSR command.
                int     21h
Main            endp
cseg            ends

sseg            segment para stack 'stack'
stk             db      1024 dup ("stack ")
sseg            ends

zzzzzzseg       segment para public 'zzzzzz'
LastBytes       db      16 dup (?)
zzzzzzseg       ends
                end     Main

The keyboard microcontroller also sends data to the on-board microcontroller for processing and release to the system through port 60h. Most of these values are key press scan codes (up or down codes), but the keyboard transmits several other values as well. A well designed keyboard interrupt service routine should be able to handle (or at least ignore) the non-scan code values. Any particular, any program that sends commands to the keyboard needs to be able to handle the resend and acknowledge commands that the keyboard microcontroller returns in port 60h. The keyboard microcontroller sends the following values to the system:

Keyboard to System Transmissions
Value (hex)

Description

00 Data overrun. System sends a zero byte as the last value when the keyboard controller's internal buffer overflows.
1..58

81..D8
Scan codes for key presses. The positive values are down codes, the negative values (H.O. bit set) are up codes.
83AB Keyboard ID code returned in response to the F2 command (PS/2 only).
AA Returned during basic assurance test after reset. Also the up code for the left shift key.
EE Returned by the ECHO command.
F0 Prefix to certain up codes (N/A on PS/2).
FA Keyboard acknowledge to keyboard commands other than resend or ECHO.
FC Basic assurance test failed (PS/2 only).
FD Diagnostic failure (not available on PS/2).
FE Resend. Keyboard requests the system to resend the last command.
FF Key error (PS/2 only).

Assuming you have not disabled keyboard interrupts (see the keyboard controller command byte), any value the keyboard microcontroller sends to the system through port 60h will generate an interrupt on IRQ line one (int 9). Therefore, the keyboard interrupt service routine normally handles all the above codes. If you are patching into int 9, don't forget to send and end of interrupt (EOI) signal to the 8259A PIC at the end of your ISR code. Also, don't forget you can enable or disable the keyboard interrupt at the 8259A.

In general, your application software should not access the keyboard hardware directly. Doing so will probably make your software incompatible with utility software such as keyboard enhancers (keyboard macro programs), pop-up software, and other resident programs that read the keyboard or insert data into the system's type ahead buffer. Fortunately, DOS and BIOS provide an excellent set of functions to read and write keyboard data. Your programs will be much more robust if you stick to using those functions. Accessing the keyboard hardware directly should be left to keyboard ISRs and those keyboard enhancers and pop-up programs that absolutely have to talk directly to the hardware.

Chapter Twenty (Part 1)

Table of Content

Chapter Twenty (Part 3) 

Chapter Twenty: The PC Keyboard (Part 2)
29 SEP 1996