The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty (Part 6)

Table of Content

Chapter Twenty One  

CHAPTER TWENTY:
THE PC KEYBOARD (Part 7)
20.7.3 - Using the 8042 Microcontroller to Simulate Keystrokes

20.7.3 Using the 8042 Microcontroller to Simulate Keystrokes

Although the trace flag based "keyboard stuffer" routine works with most software that talks to the hardware directly it still has a few problems. Specifically it doesn't work at all with programs that operate in protected mode via a "DOS Extender" library (programming libraries that let programmers access more than one megabyte of memory while running under DOS). The last technique we will look at is to program the on-board 8042 keyboard microcontroller to transmit a keystroke for us. There are two ways to do this: the PS/2 way and the hard way.

The PS/2's microcontroller includes a command specifically designed to return user programmable scan codes to the system. By writing a 0D2h byte to the controller command port (64h) and a scan code byte to port 60h you can force the controller to return that scan code as though the user pressed a key on the keyboard. See "The Keyboard Hardware Interface" for more details.

Using this technique provides the most compatible (with existing software) way to return scan codes to an application. Unfortunately this trick only works on machines that have keyboard controllers that are compatible with the PS/2's; this is not the majority of machines out there. However if you are writing code for PS/2s or compatibles this is the best way to go.

The keyboard controller on the PC/AT and most other PC compatible machines does not support the 0D2h command. Nevertheless there is a sneaky way to force the keyboard controller to transmit a scan code if you're willing to break a few rules. This trick may not work on all machines (indeed there are many machines on which this trick is known to fail) but it does provide a workaround on a large number of PC compatible machines.

The trick is simple. Although the PC's keyboard controller doesn't have a command to return a byte you send it it does provide a command to return the keyboard controller command byte (KCCB). It also provides another command to write a value to the KCCB. So by writing a value to the KCCB and then issuing the read KCCB command we can trick the system into returning a user programmable code. Unfortunately the KCCB contains some undefined reserved bits that have different meanings on different brands of keyboard microcontroller chips. That is the main reason this technique doesn't work with all machines. The following assembly code demonstrates how to use the PS/2 and PC keyboard controller stuffing methods:

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

cseg            segment para public 'code'
assume  ds:nothing

;****************************************************************************
;
; PutInATBuffer-
;
; The following code sticks the scan code into the AT-class keyboard
; microcontroller chip and asks it to send the scan code back to us
; (through the hardware port).
;
; The AT keyboard controller:
;
; Data port is at I/O address 60h
; Status port is at I/O address 64h (read only)
; Command port is at I/O address 64h (write only)
;
; The controller responds to the following values sent to the command port:
;
; 20h - Read Keyboard Controller's Command Byte (KCCB) and send the data to
; the data port (I/O address 60h).
;
; 60h - Write KCCB. The next byte written to I/O address 60h is placed in
; the KCCB. The bits of the KCCB are defined as follows:
;
;       bit 7- Reserved
should be a zero
;       bit 6- IBM industrial computer mode.
;       bit 5- IBM industrial computer mode.
;       bit 4- Disable keyboard.
;       bit 3- Inhibit override.
;       bit 2- System flag
;       bit 1- Reserved
should be a zero.
;       bit 0- Enable output buffer full interrupt.
;
;       AAh - Self test
;       ABh - Interface test
;       ACh - Diagnostic dump
;       ADh - Disable keyboard
;       AEh - Enable keyboard
;       C0h - Read Keyboard Controller input port (equip installed)
;       D0h - Read Keyboard Controller output port
;       D1h - Write Keyboard Controller output port
;       E0h - Read test inputs
;       F0h - FFh - Pulse Output port.
;
; The keyboard controller output port is defined as follows:
;
;       bit 7 - Keyboard data (output)
;       bit 6 - Keyboard clock (output)
;       bit 5 - Input buffer empty
;       bit 4 - Output buffer full
;       bit 3 - undefined
;       bit 2 - undefined
;       bit 1 - Gate A20
;       bit 0 - System reset (0=reset)
;
; The keyboard controller input port is defined as follows:
;
;       bit 7 - Keyboard inhibit switch (0=inhibited)
;       bit 6 - Display switch (0=color
1= mono)
;       bit 5 - Manufacturing jumper
;       bit 4 - System board RAM (0=disable 2nd 256K RAM on system board).
;       bits 0-3 - undefined.
;
; The keyboard controller status port (64h) is defined as follows:
;
;       bit 1 - Set if input data (60h) not available.
;       bit 0 - Set if output port (60h) cannot accept data.



PutInATBuffer   proc    near
assume  ds:nothing
pushf
push    ax
push    bx
push    cx
push    dx


mov     dl
al          ;Save char to output.

; Wait until the keyboard controller does not contain data before
; proceeding with shoving stuff down its throat.

xor     cx
cx
WaitWhlFull:    in      al
64h
test    al
1
loopnz  WaitWhlFull


; First things first
let's mask the interrupt controller chip (8259) to
; tell it to ignore interrupts coming from the keyboard. However
turn the
; interrupts on so we properly process interrupts from other sources (this
; is especially important because we're going to wind up sending a false
; EOI to the interrupt controller inside the INT 9 BIOS routine).

cli
in      al
21h         ;Get current mask
push    ax              ;Save intr mask
or      al
2           ;Mask keyboard interrupt
out     21h
al

; Transmit the desired scan code to the keyboard controller. Call this
; byte the new keyboard controller command (we've turned off the keyboard

; so this won't affect anything).
;
; The following code tells the keyboard controller to take the next byte
; sent to it and use this byte as the KCCB:


call    WaitToXmit
mov     al
60h         ;Write new KCCB command.
out     64h
al

; Send the scan code as the new KCCB:

call    WaitToXmit
mov     al
dl
out     60h
al

; The following code instructs the system to transmit the KCCB (i.e.
the
; scan code) to the system:

call    WaitToXmit
mov     al
20h         ;"Send KCCB" command.
out     64h
al

xor     cx
cx
Wait4OutFull:   in      al
64h
test    al
1
loopz   Wait4OutFull

; Okay
Send a 45h back as the new KCCB to allow the normal keyboard to work
; properly.

call    WaitToXmit
mov     al
60h
out     64h
al

call    WaitToXmit
mov     al
45h
out     60h
al

; Okay
execute an INT 9 routine so the BIOS (or whoever) can read the key
; we just stuffed into the keyboard controller. Since we've masked INT 9
; at the interrupt controller
there will be no interrupt coming along from
; the key we shoved in the buffer.

DoInt9:         in      al
60h         ;Prevents ints from some codes.
int     9               ;Simulate hardware kbd int.


; Just to be safe
reenable the keyboard:

call    WaitToXmit
mov     al
0aeh
out     64h
al

; Okay
restore the interrupt mask for the keyboard in the 8259a.

pop     ax
out     21h
al

pop     dx
pop     cx
pop     bx
pop     ax
popf
ret
PutInATBuffer   endp



; WaitToXmit-   Wait until it's okay to send a command byte to the keyboard
;               controller port.

WaitToXmit      proc    near
push    cx
push    ax
xor     cx
cx
TstCmdPortLp:   in      al
64h
test    al
2           ;Check cntrlr input buffer full flag.
loopnz  TstCmdPortLp
pop     ax
pop     cx
ret
WaitToXmit      endp



;****************************************************************************
;
; PutInPS2Buffer- Like PutInATBuffer
it uses the keyboard controller chip
;                to return the keycode. However
PS/2 compatible controllers
;                have an actual command to return keycodes.

PutInPS2Buffer  proc    near
pushf
push    ax
push    bx
push    cx
push    dx

mov     dl
al          ;Save char to output.

; Wait until the keyboard controller does not contain data before
; proceeding with shoving stuff down its throat.

xor     cx
cx
WaitWhlFull:            in      al
64h
test    al
1
loopnz  WaitWhlFull


; The following code tells the keyboard controller to take the next byte
; sent to it and return it as a scan code.


call    WaitToXmit
mov     al
0d2h        ;Return scan code command.
out     64h
al

; Send the scan code:

call    WaitToXmit
mov     al
dl
out     60h
al

pop     dx
pop     cx
pop     bx
pop     ax
popf
ret
PutInPS2Buffer  endp


; Main program - Simulates some keystrokes to demo the above code.

Main            proc

mov     ax
cseg
mov     ds
ax

print
byte    "Simulating keystrokes via Trace Flag"
cr
lf
byte    "This program places 'DIR' in the keyboard buffer"
byte    cr
lf
0

mov     al
20h         ;"D" down scan code
call    PutInATBuffer
mov     al
0a0h        ;"D" up scan code
call    PutInATBuffer

mov     al
17h         ;"I" down scan code
call    PutInATBuffer
mov     al
97h         ;"I" up scan code
call    PutInATBuffer

mov     al
13h         ;"R" down scan code
call    PutInATBuffer
mov     al
93h         ;"R" up scan code
call    PutInATBuffer

mov     al
1Ch         ;Enter down scan code
call    PutInATBuffer
mov     al
9Ch         ;Enter up scan code
call    PutInATBuffer

ExitPgm
Main            endp


cseg            ends

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

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

Chapter Twenty (Part 6)

Table of Content

Chapter Twenty One  

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