The keyboard sends a scan code for every key event: a "make" code when a key goes down and a "break" code when it comes up, which on the PC is the make code with bit 7 set (Esc is 01h down, 81h up). Holding a key down repeats the make code. The keyboard controller puts the code in its output register, port 60h, and raises IRQ 1, which arrives as vector 09h. The keys added by the 101-key keyboard send an extra E0h byte first (a separate interrupt), and Pause sends a whole sequence.
What the BIOS handler does
| Step | What happens |
|---|---|
| 1 | read the scan code from port 60h |
| 2 | AT and later: call INT 15h AH=4Fh with it, so a hook can change or swallow the key |
| 3 | Shift, Ctrl, Alt, the Lock keys: update the flags at 0040:0017h / 0018h, store nothing |
| 4 | special combinations: Ctrl+Alt+Del reboots, Ctrl+Break calls INT 1Bh, Print Screen calls INT 05h, Pause waits |
| 5 | other keys: translate to ASCII by the shift state and store scan code and ASCII at the buffer tail (see INT 16h) |
| 6 | buffer full: beep and drop the key |
| 7 | send the end of interrupt to the 8259 (OUT 20h, AL with AL = 20h) and IRET |
On the PC and XT the handler must also acknowledge the key to the keyboard logic by pulsing bit 7 of port 61h; the AT's 8042 controller does that itself.
Where the vector points is easy to see; on a plain system it is the BIOS itself, in segment F000h:
MOV AX, 3509h ; DOS: get vector 09hINT 21h ; ES:BX = the keyboard handlerMOV AX, ESCALL PHEX4 ; the segmentMOV DL, ":"MOV AH, 02h ; DOS: print the character in DLINT 21hMOV AX, BXCALL PHEX4 ; the offsetPHEX4 PROC NEAR ; print AX as four hex digits; keeps every registerPUSH AXMOV AL, AHCALL PHEX2 ; the high byte firstPOP AXCALL PHEX2 ; then the low byteRETPHEX4 ENDPPHEX2 PROC NEAR ; print AL as two hex digits; keeps every registerPUSH AXPUSH CXPUSH DXMOV DH, AL ; keep the byteMOV CL, 4SHR AL, CL ; the high nibbleCALL HEXN1MOV AL, DHAND AL, 0Fh ; the low nibbleCALL HEXN1POP DXPOP CXPOP AXRETHEXN1:ADD AL, "0"CMP AL, "9"JBE HEXN2ADD AL, 7 ; past "9": "A".."F"HEXN2:MOV DL, ALMOV AH, 02hINT 21hRETPHEX2 ENDP
Hooking the keyboard
Games hook 09h to see every press and release, which INT 16h never shows: they keep a table of which keys are down, so that holding two keys at once works. A hook that chains to the BIOS reads port 60h too (reading does not remove the code) and then jumps to the old handler, which does all the rest, EOI included. A hook that does not chain must do everything itself: read port 60h, acknowledge the keyboard on an XT, send the EOI, IRET, and nothing reaches the buffer. This shape counts presses of Esc and passes every key on:
KEY1 PROC FAR ; installed with INT 21h AX=2509hPUSH AXIN AL, 60h ; peek at the scan codeCMP AL, 01h ; Esc pressed (make code)?JNE CHAIN1INC CS:ESCS1 ; count it; DS is unknown, use CSCHAIN1:POP AXJMP DWORD PTR CS:OLD9 ; the BIOS handler does the rest, EOI includedKEY1 ENDPOLD9 DD 0 ; filled from INT 21h AX=3509hESCS1 DW 0
Pitfalls
A handler that neither chains nor sends the EOI locks the keyboard (and every lower interrupt) at the first key. Keys stored with INT 16h AH=05h never pass through INT 09h: they go straight into the buffer. A program that takes over the keyboard completely must restore the vector on every way out, and should leave the shift flags sane, or the user returns to DOS with a "stuck" Ctrl. For filtering keys while keeping the BIOS in charge, the INT 15h AH=4Fh hook is the lighter choice on AT-class machines.