xlat
instruction to translate the scan code to an ASCII code using a table int 9 selects on the basis of the modifier flags. Another important issue is that the int 9 handler must handle special key sequences like ctrl-alt-del (reset) and PrtSc. The following assembly code provides a simple int 9 handler for the keyboard. It does not support alt-Keypad ASCII code entry or a few other minor features, but it does support almost everything you need for a keyboard interrupt service routine. Certainly it demonstrates all the techniques you need to know when programming the keyboard.; INT9.ASM ; ; A short TSR to provide a driver for the keyboard hardware interrupt. ; ; Note that this code does not patch into int 2Fh (multiplex interrupt) ; nor can you remove this code from memory except by rebooting. ; If you want to be able to do these two things (as well as check for ; a previous installation), see the chapter on resident programs. Such ; code was omitted from this program because of length constraints. ; ; ; cseg and EndResident must occur before the standard library segments! cseg segment para public 'code' OldInt9 dword ? 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 NumLockScan equ 45h ScrlLockScan equ 46h CapsLockScan equ 3ah CtrlScan equ 1dh AltScan equ 38h RShiftScan equ 36h LShiftScan equ 2ah InsScanCode equ 52h DelScanCode equ 53h ; Bits for the various modifier keys RShfBit equ 1 LShfBit equ 2 CtrlBit equ 4 AltBit equ 8 SLBit equ 10h NLBit equ 20h CLBit equ 40h InsBit equ 80h KbdFlags equ <byte ptr ds:[17h]> KbdFlags2 equ <byte ptr ds:[18h]> KbdFlags3 equ <byte ptr ds:[96h]> KbdFlags4 equ <byte ptr ds:[97h]> byp equ <byte ptr> cseg segment para public 'code' assume ds:nothing ; Scan code translation table. ; The incoming scan code from the keyboard selects a row. ; The modifier status selects the column. ; The word at the intersection of the two is the scan/ASCII code to ; put into the PC's type ahead buffer. ; If the value fetched from the table is zero, then we do not put the ; character into the type ahead buffer. ; ; norm shft ctrl alt num caps shcap shnum ScanXlat word 0000h, 0000h, 0000h, 0000h, 0000h, 0000h, 0000h, 0000h word 011bh, 011bh, 011bh, 011bh, 011bh, 011bh, 011bh, 011bh ;ESC word 0231h, 0231h, 0000h, 7800h, 0231h, 0231h, 0231h, 0321h ;1 ! word 0332h, 0340h, 0300h, 7900h, 0332h, 0332h, 0332h, 0332h ;2 @ word 0433h, 0423h, 0000h, 7a00h, 0433h, 0433h, 0423h, 0423h ;3 # word 0534h, 0524h, 0000h, 7b00h, 0534h, 0534h, 0524h, 0524h ;4 $ word 0635h, 0625h, 0000h, 7c00h, 0635h, 0635h, 0625h, 0625h ;5 % word 0736h, 075eh, 071eh, 7d00h, 0736h, 0736h, 075eh, 075eh ;6 ^ word 0837h, 0826h, 0000h, 7e00h, 0837h, 0837h, 0826h, 0826h ;7 & word 0938h, 092ah, 0000h, 7f00h, 0938h, 0938h, 092ah, 092ah ;8 * word 0a39h, 0a28h, 0000h, 8000h, 0a39h, 0a39h, 0a28h, 0a28h ;9 ( word 0b30h, 0b29h, 0000h, 8100h, 0b30h, 0b30h, 0b29h, 0b29h ;0 ) word 0c2dh, 0c5fh, 0000h, 8200h, 0c2dh, 0c2dh, 0c5fh, 0c5fh ;- _ word 0d3dh, 0d2bh, 0000h, 8300h, 0d3dh, 0d3dh, 0d2bh, 0d2bh ;= + word 0e08h, 0e08h, 0e7fh, 0000h, 0e08h, 0e08h, 0e08h, 0e08h ;bksp word 0f09h, 0f00h, 0000h, 0000h, 0f09h, 0f09h, 0f00h, 0f00h ;Tab ; norm shft ctrl alt num caps shcap shnum word 1071h, 1051h, 1011h, 1000h, 1071h, 1051h, 1051h, 1071h ;Q word 1177h, 1057h, 1017h, 1100h, 1077h, 1057h, 1057h, 1077h ;W word 1265h, 1245h, 1205h, 1200h, 1265h, 1245h, 1245h, 1265h ;E word 1372h, 1352h, 1312h, 1300h, 1272h, 1252h, 1252h, 1272h ;R word 1474h, 1454h, 1414h, 1400h, 1474h, 1454h, 1454h, 1474h ;T word 1579h, 1559h, 1519h, 1500h, 1579h, 1559h, 1579h, 1559h ;Y word 1675h, 1655h, 1615h, 1600h, 1675h, 1655h, 1675h, 1655h ;U word 1769h, 1749h, 1709h, 1700h, 1769h, 1749h, 1769h, 1749h ;I word 186fh, 184fh, 180fh, 1800h, 186fh, 184fh, 186fh, 184fh ;O word 1970h, 1950h, 1910h, 1900h, 1970h, 1950h, 1970h, 1950h ;P word 1a5bh, 1a7bh, 1a1bh, 0000h, 1a5bh, 1a5bh, 1a7bh, 1a7bh ;[ { word 1b5dh, 1b7dh, 1b1dh, 0000h, 1b5dh, 1b5dh, 1b7dh, 1b7dh ;] } word 1c0dh, 1c0dh, 1c0ah, 0000h, 1c0dh, 1c0dh, 1c0ah, 1c0ah ;enter word 1d00h, 1d00h, 1d00h, 1d00h, 1d00h, 1d00h, 1d00h, 1d00h ;ctrl word 1e61h, 1e41h, 1e01h, 1e00h, 1e61h, 1e41h, 1e61h, 1e41h ;A word 1f73h, 1f5eh, 1f13h, 1f00h, 1f73h, 1f53h, 1f73h, 1f53h ;S ; norm shft ctrl alt num caps shcap shnum word 2064h, 2044h, 2004h, 2000h, 2064h, 2044h, 2064h, 2044h ;D word 2166h, 2146h, 2106h, 2100h, 2166h, 2146h, 2166h, 2146h ;F word 2267h, 2247h, 2207h, 2200h, 2267h, 2247h, 2267h, 2247h ;G word 2368h, 2348h, 2308h, 2300h, 2368h, 2348h, 2368h, 2348h ;H word 246ah, 244ah, 240ah, 2400h, 246ah, 244ah, 246ah, 244ah ;J word 256bh, 254bh, 250bh, 2500h, 256bh, 254bh, 256bh, 254bh ;K word 266ch, 264ch, 260ch, 2600h, 266ch, 264ch, 266ch, 264ch ;L word 273bh, 273ah, 0000h, 0000h, 273bh, 273bh, 273ah, 273ah ;; : word 2827h, 2822h, 0000h, 0000h, 2827h, 2827h, 2822h, 2822h ;' " word 2960h, 297eh, 0000h, 0000h, 2960h, 2960h, 297eh, 297eh ;` ~ word 2a00h, 2a00h, 2a00h, 2a00h, 2a00h, 2a00h, 2a00h, 2a00h ;LShf word 2b5ch, 2b7ch, 2b1ch, 0000h, 2b5ch, 2b5ch, 2b7ch, 2b7ch ;\ | word 2c7ah, 2c5ah, 2c1ah, 2c00h, 2c7ah, 2c5ah, 2c7ah, 2c5ah ;Z word 2d78h, 2d58h, 2d18h, 2d00h, 2d78h, 2d58h, 2d78h, 2d58h ;X word 2e63h, 2e43h, 2e03h, 2e00h, 2e63h, 2e43h, 2e63h, 2e43h ;C word 2f76h, 2f56h, 2f16h, 2f00h, 2f76h, 2f56h, 2f76h, 2f56h ;V ; norm shft ctrl alt num caps shcap shnum word 3062h, 3042h, 3002h, 3000h, 3062h, 3042h, 3062h, 3042h ;B word 316eh, 314eh, 310eh, 3100h, 316eh, 314eh, 316eh, 314eh ;N word 326dh, 324dh, 320dh, 3200h, 326dh, 324dh, 326dh, 324dh ;M word 332ch, 333ch, 0000h, 0000h, 332ch, 332ch, 333ch, 333ch ;, < word 342eh, 343eh, 0000h, 0000h, 342eh, 342eh, 343eh, 343eh ;. > word 352fh, 353fh, 0000h, 0000h, 352fh, 352fh, 353fh, 353fh ;/ ? word 3600h, 3600h, 3600h, 3600h, 3600h, 3600h, 3600h, 3600h ;rshf word 372ah, 0000h, 3710h, 0000h, 372ah, 372ah, 0000h, 0000h ;* PS word 3800h, 3800h, 3800h, 3800h, 3800h, 3800h, 3800h, 3800h ;alt word 3920h, 3920h, 3920h, 0000h, 3920h, 3920h, 3920h, 3920h ;spc word 3a00h, 3a00h, 3a00h, 3a00h, 3a00h, 3a00h, 3a00h, 3a00h ;caps word 3b00h, 5400h, 5e00h, 6800h, 3b00h, 3b00h, 5400h, 5400h ;F1 word 3c00h, 5500h, 5f00h, 6900h, 3c00h, 3c00h, 5500h, 5500h ;F2 word 3d00h, 5600h, 6000h, 6a00h, 3d00h, 3d00h, 5600h, 5600h ;F3 word 3e00h, 5700h, 6100h, 6b00h, 3e00h, 3e00h, 5700h, 5700h ;F4 word 3f00h, 5800h, 6200h, 6c00h, 3f00h, 3f00h, 5800h, 5800h ;F5 ; norm shft ctrl alt num caps shcap shnum word 4000h, 5900h, 6300h, 6d00h, 4000h, 4000h, 5900h, 5900h ;F6 word 4100h, 5a00h, 6400h, 6e00h, 4100h, 4100h, 5a00h, 5a00h ;F7 word 4200h, 5b00h, 6500h, 6f00h, 4200h, 4200h, 5b00h, 5b00h ;F8 word 4300h, 5c00h, 6600h, 7000h, 4300h, 4300h, 5c00h, 5c00h ;F9 word 4400h, 5d00h, 6700h, 7100h, 4400h, 4400h, 5d00h, 5d00h ;F10 word 4500h, 4500h, 4500h, 4500h, 4500h, 4500h, 4500h, 4500h ;num word 4600h, 4600h, 4600h, 4600h, 4600h, 4600h, 4600h, 4600h ;scrl word 4700h, 4737h, 7700h, 0000h, 4737h, 4700h, 4737h, 4700h ;home word 4800h, 4838h, 0000h, 0000h, 4838h, 4800h, 4838h, 4800h ;up word 4900h, 4939h, 8400h, 0000h, 4939h, 4900h, 4939h, 4900h ;pgup word 4a2dh, 4a2dh, 0000h, 0000h, 4a2dh, 4a2dh, 4a2dh, 4a2dh ;- word 4b00h, 4b34h, 7300h, 0000h, 4b34h, 4b00h, 4b34h, 4b00h ;left word 4c00h, 4c35h, 0000h, 0000h, 4c35h, 4c00h, 4c35h, 4c00h ;Center word 4d00h, 4d36h, 7400h, 0000h, 4d36h, 4d00h, 4d36h, 4d00h ;right word 4e2bh, 4e2bh, 0000h, 0000h, 4e2bh, 4e2bh, 4e2bh, 4e2bh ;+ word 4f00h, 4f31h, 7500h, 0000h, 4f31h, 4f00h, 4f31h, 4f00h ;end ; norm shft ctrl alt num caps shcap shnum word 5000h, 5032h, 0000h, 0000h, 5032h, 5000h, 5032h, 5000h ;down word 5100h, 5133h, 7600h, 0000h, 5133h, 5100h, 5133h, 5100h ;pgdn word 5200h, 5230h, 0000h, 0000h, 5230h, 5200h, 5230h, 5200h ;ins word 5300h, 532eh, 0000h, 0000h, 532eh, 5300h, 532eh, 5300h ;del word 0,0,0,0,0,0,0,0 ; -- word 0,0,0,0,0,0,0,0 ; -- word 0,0,0,0,0,0,0,0 ; -- word 5700h, 0000h, 0000h, 0000h, 5700h, 5700h, 0000h, 0000h ;F11 word 5800h, 0000h, 0000h, 0000h, 5800h, 5800h, 0000h, 0000h ;F12 ;**************************************************************************** ; ; AL contains keyboard scan code. PutInBuffer proc near push ds push bx mov bx, 40h ;Point ES at the BIOS mov ds, bx ; variables. ; If the current scan code is E0 or E1, we need to take note of this fact ; so that we can properly process cursor keys. cmp al, 0e0h jne TryE1 or KbdFlags3, 10b ;Set E0 flag and KbdFlags3, 0FEh ;Clear E1 flag jmp Done TryE1: cmp al, 0e1h jne DoScan or KbdFlags3, 1 ;Set E1 flag and KbdFlags3, 0FDh ;Clear E0 Flag jmp Done ; Before doing anything else, see if this is Ctrl-Alt-Del: DoScan: cmp al, DelScanCode jnz TryIns mov bl, KbdFlags and bl, AltBit or CtrlBit ;Alt = bit 3, ctrl = bit 2 cmp bl, AltBit or CtrlBit jne DoPIB mov word ptr ds:[72h], 1234h ;Warm boot flag. jmp dword ptr cs:RebootAdrs ;REBOOT Computer RebootAdrs dword 0ffff0000h ;Reset address. ; Check for the INS key here. This one needs to toggle the ins bit ; in the keyboard flags variables. TryIns: cmp al, InsScanCode jne TryInsUp or KbdFlags2, InsBit ;Note INS is down. jmp doPIB ;Pass on INS key. TryInsUp: cmp al, InsScanCode+80h ;INS up scan code. jne TryLShiftDn and KbdFlags2, not InsBit ;Note INS is up. xor KbdFlags, InsBit ;Toggle INS bit. jmp QuitPIB ; Handle the left and right shift keys down here. TryLShiftDn: cmp al, LShiftScan jne TryLShiftUp or KbdFlags, LShfBit ;Note that the left jmp QuitPIB ; shift key is down. TryLShiftUp: cmp al, LShiftScan+80h jne TryRShiftDn and KbdFlags, not LShfBit ;Note that the left jmp QuitPIB ; shift key is up. TryRShiftDn: cmp al, RShiftScan jne TryRShiftUp or KbdFlags, RShfBit ;Right shf is down. jmp QuitPIB TryRShiftUp: cmp al, RShiftScan+80h jne TryAltDn and KbdFlags, not RShfBit ;Right shf is up. jmp QuitPIB ; Handle the ALT key down here. TryAltDn: cmp al, AltScan jne TryAltUp or KbdFlags, AltBit ;Alt key is down. GotoQPIB: jmp QuitPIB TryAltUp: cmp al, AltScan+80h jne TryCtrlDn and KbdFlags, not AltBit ;Alt key is up. jmp DoPIB ; Deal with the control key down here. TryCtrlDn: cmp al, CtrlScan jne TryCtrlUp or KbdFlags, CtrlBit ;Ctrl key is down. jmp QuitPIB TryCtrlUp: cmp al, CtrlScan+80h jne TryCapsDn and KbdFlags, not CtrlBit ;Ctrl key is up. jmp QuitPIB ; Deal with the CapsLock key down here. TryCapsDn: cmp al, CapsLockScan jne TryCapsUp or KbdFlags2, CLBit ;Capslock is down. xor KbdFlags, CLBit ;Toggle capslock. jmp QuitPIB TryCapsUp: cmp al, CapsLockScan+80h jne TrySLDn and KbdFlags2, not CLBit ;Capslock is up. call SetLEDs jmp QuitPIB ; Deal with the Scroll Lock key down here. TrySLDn: cmp al, ScrlLockScan jne TrySLUp or KbdFlags2, SLBit ;Scrl lock is down. xor KbdFlags, SLBit ;Toggle scrl lock. jmp QuitPIB TrySLUp: cmp al, ScrlLockScan+80h jne TryNLDn and KbdFlags2, not SLBit ;Scrl lock is up. call SetLEDs jmp QuitPIB ; Handle the NumLock key down here. TryNLDn: cmp al, NumLockScan jne TryNLUp or KbdFlags2, NLBit ;Numlock is down. xor KbdFlags, NLBit ;Toggle numlock. jmp QuitPIB TryNLUp: cmp al, NumLockScan+80h jne DoPIB and KbdFlags2, not NLBit ;Numlock is up. call SetLEDs jmp QuitPIB ; Handle all the other keys here: DoPIB: test al, 80h ;Ignore other up keys. jnz QuitPIB ; If the H.O. bit is set at this point, we'd best only have a zero in AL. ; Otherwise, this is an up code which we can safely ignore. call Convert test ax, ax ;Chk for bad code. je QuitPIB PutCharInBuf: push cx mov cx, ax mov ah, 5 ;Store scan code into int 16h ; type ahead buffer. pop cx QuitPIB: and KbdFlags3, 0FCh ;E0, E1 not last code. Done: pop bx pop ds ret PutInBuffer endp ;**************************************************************************** ; ; Convert- AL contains a PC Scan code. Convert it to an ASCII char/Scan ; code pair and return the result in AX. This code assumes ; that DS points at the BIOS variable space (40h). Convert proc near push bx test al, 80h ;See if up code jz DownScanCode mov ah, al mov al, 0 jmp CSDone ; Okay, we've got a down key. But before going on, let's see if we've ; got an ALT-Keypad sequence. DownScanCode: mov bh, 0 mov bl, al shl bx, 1 ;Multiply by eight to compute shl bx, 1 ; row index index the scan shl bx, 1 ; code xlat table ; Compute modifier index as follows: ; ; if alt then modifier = 3 test KbdFlags, AltBit je NotAlt add bl, 3 jmp DoConvert ; if ctrl, then modifier = 2 NotAlt: test KbdFlags, CtrlBit je NotCtrl add bl, 2 jmp DoConvert ; Regardless of the shift setting, we've got to deal with numlock ; and capslock. Numlock is only a concern if the scan code is greater ; than or equal to 47h. Capslock is only a concern if the scan code ; is less than this. NotCtrl: cmp al, 47h jb DoCapsLk test KbdFlags, NLBit ;Test Numlock bit je NoNumLck test KbdFlags, LShfBit or RShfBit ;Check l/r shift. je NumOnly add bl, 7 ;Numlock and shift. jmp DoConvert NumOnly: add bl, 4 ;Numlock only. jmp DoConvert ; If numlock is not active, see if a shift key is: NoNumLck: test KbdFlags, LShfBit or RShfBit ;Check l/r shift. je DoConvert ;normal if no shift. add bl, 1 jmp DoConvert ; If the scan code's value is below 47h, we need to check for capslock. DoCapsLk: test KbdFlags, CLBit ;Chk capslock bit je DoShift test KbdFlags, LShfBit or RShfBit ;Chk for l/r shift je CapsOnly add bl, 6 ;Shift and capslock. jmp DoConvert CapsOnly: add bl, 5 ;Capslock jmp DoConvert ; Well, nothing else is active, check for just a shift key. DoShift: test KbdFlags, LShfBit or RShfBit ;l/r shift. je DoConvert add bl, 1 ;Shift DoConvert: shl bx, 1 ;Word array mov ax, ScanXlat[bx] CSDone: pop bx ret Convert endp ; 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 bh, 3 ;Retry cnt. RetryLp: cli ;Disable ints while accessing HW. ; Clear the Error, Acknowledge received, and resend received flags ; in KbdFlags4 and byte ptr KbdFlags4, 4fh ; 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 sti ;Allow interrupts now. ; Wait for the arrival of an acknowledgement from the keyboard ISR: xor cx, cx ;Wait a long time, if need be. Wait4Ack: test byp KbdFlags4,10h ;Acknowledge received bit. jnz GotAck loop Wait4Ack dec bh ;Do a retry on this guy. jne RetryLp ; If the operation failed after 3 retries, set the error bit and quit. or byp KbdFlags4,80h ;Set error bit. GotAck: pop cx pop bx pop ds ret SendCmd endp ; SetLEDs- Updates the KbdFlags4 LED bits from the KbdFlags ; variable and then transmits new flag settings to ; the keyboard. SetLEDs proc near push ax push cx mov al, KbdFlags mov cl, 4 shr al, cl and al, 111b and KbdFlags4, 0F8h ;Clear LED bits. or KbdFlags4, al ;Mask in new bits. mov ah, al ;Save LED bits. mov al, 0ADh ;Disable kbd for now. call SetCmd 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. mov al, 0AEh ;Reenable keyboard. call SetCmd mov al, 0F4h ;Restart kbd scanning. call SendCmd pop cx pop ax ret SetLEDs endp ; MyInt9- Interrupt service routine for the keyboard hardware ; interrupt. MyInt9 proc far push ds push ax push cx mov ax, 40h mov ds, ax mov al, 0ADh ;Disable keyboard call SetCmd cli ;Disable interrupts. xor cx, cx Wait4Data: in al, 64h ;Read kbd status port. test al, 10b ;Data in buffer? loopz Wait4Data ;Wait until data available. in al, 60h ;Get keyboard data. cmp al, 0EEh ;Echo response? je QuitInt9 cmp al, 0FAh ;Acknowledge? jne NotAck or KbdFlags4, 10h ;Set ack bit. jmp QuitInt9 NotAck: cmp al, 0FEh ;Resend command? jne NotResend or KbdFlags4, 20h ;Set resend bit. jmp QuitInt9 ; Note: other keyboard controller commands all have their H.O. bit set ; and the PutInBuffer routine will ignore them. NotResend: call PutInBuffer ;Put in type ahead buffer. QuitInt9: mov al, 0AEh ;Reenable the keyboard call SetCmd mov al, 20h ;Send EOI (end of interrupt) out 20h, al ; to the 8259A PIC. pop cx pop ax pop ds iret MyInt9 endp Main proc assume ds:cseg mov ax, cseg mov ds, ax print byte "INT 9 Replacement",cr,lf byte "Installing....",cr,lf,0 ; Patch into the INT 9 interrupt vector. Note that the ; statements above have made cseg the current data segment, ; so we can store the old INT 9 value directly into ; the OldInt9 variable. cli ;Turn off interrupts! mov ax, 0 mov es, ax mov ax, es:[9*4] mov word ptr OldInt9, ax mov ax, es:[9*4 + 2] mov word ptr OldInt9+2, ax mov es:[9*4], offset MyInt9 mov es:[9*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 byte 1024 dup ("stack ") sseg ends zzzzzzseg segment para public 'zzzzzz' LastBytes db 16 dup (?) zzzzzzseg ends end Main