'===========================================================================
' Subject: KEYBOARD ISR                       Date: 06-22-96 (22:14)       
'  Author: Steven Sensarn                     Code: QB, QBasic, PDS        
'  Origin: comp.lang.basic.misc             Packet: KEYBOARD.ABC
'===========================================================================
'Here is a nice program that uses its own ISR to handle keyboard I/O.  
'Normally, BIOS handles interrupt calls from the keyboard.  Unfortunately, 
'BIOS is relatively slow and often cannot catch up with the user pressing the 
'keys on the keyboard.  This results in a keyboard buffer overflow and the PC 
'starts beeping (it's happend to all of us).  To correct this error, the 
'programmer writes his own ISR (interrupt service routine) and replaces the 
'old ISR with it.  The keyboard is connected to IRQ1 (or it should be--if 
'you're having keyboard problems, that might be the reason).  Therefore, the 
'ISR must be connected to interrupt 9h (IRQ1's alias).  Enjoy!

DECLARE SUB SETVECT (S AS INTEGER, O AS INTEGER, I AS INTEGER)
DECLARE SUB GETVECT (S AS INTEGER, O AS INTEGER, I AS INTEGER)
DECLARE SUB KEYBOARD.IN (OLDSEG AS INTEGER, OLDOFF AS INTEGER)
DECLARE SUB KEYBOARD.OUT (OLDSEG AS INTEGER, OLDOFF AS INTEGER)

'$STATIC

DIM RAWKEY AS INTEGER
DIM OLD.ISR.SEG AS INTEGER, OLD.ISR.OFF AS INTEGER

CALL KEYBOARD.IN(OLD.ISR.SEG, OLD.ISR.OFF)

DO
    PRINT RAWKEY 'DISPLAY SCANCODE OF LAST KEY PRESSED
                 'NOTICE THAT EACH KEY HAS A PRESS AND RELEASE CODE
LOOP UNTIL RAWKEY = 1

CALL KEYBOARD.OUT(OLD.ISR.SEG, OLD.ISR.OFF)

SUB GETVECT (S AS INTEGER, O AS INTEGER, I AS INTEGER)

    'GETVECT RETURNS THE ADDRESS OF A FUNCTION POINTED TO IN THE
    'INTERRUPT VECTOR TABLE (STARTS AT 0000:0000H)

    STATIC ASM AS STRING 'THE CODE FOR GETVECT

    STATIC INI AS INTEGER 'USED TO DETECT WHETHER GETVECT HAS PREVIOUSLY
                          'BEEN CALLED
    IF INI = 0 THEN
        
        'CREATE ML FUNCTION IF NOT ALREADY CREATED

        ASM = ASM + CHR$(&H55)                          'PUSH    BP
        ASM = ASM + CHR$(&H89) + CHR$(&HE5)             'MOV     BP,SP
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H6) 'MOV     BX,[BP+06]
        ASM = ASM + CHR$(&H8A) + CHR$(&H7)              'MOV     AL,[BX]
        ASM = ASM + CHR$(&HB4) + CHR$(&H35)             'MOV     AH,35
        ASM = ASM + CHR$(&HCD) + CHR$(&H21)             'INT     21
        ASM = ASM + CHR$(&H53)                          'PUSH    BX
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&HA) 'MOV     BX,[BP+0A]
        ASM = ASM + CHR$(&H8C) + CHR$(&H7)              'MOV     [BX],ES
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H8) 'MOV     BX,[BP+08]
        ASM = ASM + CHR$(&H58)                          'POP     AX
        ASM = ASM + CHR$(&H89) + CHR$(&H7)              'MOV     [BX],AX
        ASM = ASM + CHR$(&H5D)                          'POP     BP
        ASM = ASM + CHR$(&HCA) + CHR$(&H6) + CHR$(&H0)  'RETF    0006
        INI = 1 'FLAG CREATION
    END IF

    DEF SEG = VARSEG(ASM)
    CALL ABSOLUTE(S, O, I, SADD(ASM)) 'RUN FUNCTION
END SUB

SUB KEYBOARD.IN (OLDSEG AS INTEGER, OLDOFF AS INTEGER)
    SHARED RAWKEY AS INTEGER 'GLOBAL VARIABLE HOLDS SCANCODE

    DIM SGL AS INTEGER, SGH AS INTEGER 'SEGMENT OF RAWKEY
    DIM OFL AS INTEGER, OFH AS INTEGER 'OFFSET OF RAWKEY

    DIM BYTE AS STRING * 1 'USED TO ACTIVATE IRQ 1 IN PIC

    STATIC ASM AS STRING 'HOLDS ISR

    SGL = VARSEG(RAWKEY) AND &HFF 'LOAD LOW "BYTE" SEGMENT
    SGH = INT(VARSEG(RAWKEY) / 256) AND &HFF 'LOAD HIGH "BYTE" SEGMENT

    OFL = VARPTR(RAWKEY) AND &HFF 'LOAD LOW "BYTE" OFFSET
    OFH = INT(VARPTR(RAWKEY) / 256) AND &HFF 'LOAD HIGH "BYTE" OFFSET

    'THIS IS THE ISR.  IT READS A SCANCODE FROM THE KEYBOARD BUFFER
    'AND RESETS IT.  THE BEST PART IS, BIOS CAN'T TOUCH IT!

    ASM = ""
    ASM = ASM + CHR$(&H52)                          'PUSH DX
    ASM = ASM + CHR$(&H51)                          'PUSH CX      
    ASM = ASM + CHR$(&H53)                          'PUSH BX
    ASM = ASM + CHR$(&H50)                          'PUSH AX
    ASM = ASM + CHR$(&H6)                           'PUSH ES
    ASM = ASM + CHR$(&H57)                          'PUSH DI
    ASM = ASM + CHR$(&H1E)                          'PUSH DS
    ASM = ASM + CHR$(&H56)                          'PUSH SI
    ASM = ASM + CHR$(&HFB)                          'STI
    ASM = ASM + CHR$(&HBA) + CHR$(&H60) + CHR$(&H0) 'MOV DX,0060
    ASM = ASM + CHR$(&HEC)                          'IN AL,DX
    ASM = ASM + CHR$(&H30) + CHR$(&HE4)             'XOR AH,AH
    ASM = ASM + CHR$(&HBA) + CHR$(SGL) + CHR$(SGH)  'MOV DX,SEG RAWKEY
    ASM = ASM + CHR$(&H8E) + CHR$(&HDA)             'MOV DS,DX
    ASM = ASM + CHR$(&HBE) + CHR$(OFL) + CHR$(OFH)  'MOV SI,OFFSET RAWKEY
    ASM = ASM + CHR$(&H88) + CHR$(&H4)              'MOV [SI],AL
    ASM = ASM + CHR$(&HBA) + CHR$(&H61) + CHR$(&H0) 'MOV DX,0061
    ASM = ASM + CHR$(&HEC)                          'IN AL,DX
    ASM = ASM + CHR$(&HC) + CHR$(&H82)              'OR AL,82
    ASM = ASM + CHR$(&HEE)                          'OUT DX,AL
    ASM = ASM + CHR$(&H24) + CHR$(&H7F)             'AND AL,7F
    ASM = ASM + CHR$(&HEE)                          'OUT DX,AL
    ASM = ASM + CHR$(&HB0) + CHR$(&H20)             'MOV AL,20
    ASM = ASM + CHR$(&HBA) + CHR$(&H20) + CHR$(&H0) 'MOV DX,0020
    ASM = ASM + CHR$(&HEE)                          'OUT DX,AL
    ASM = ASM + CHR$(&H5E)                          'POP SI
    ASM = ASM + CHR$(&H1F)                          'POP DS
    ASM = ASM + CHR$(&H5F)                          'POP DI
    ASM = ASM + CHR$(&H7)                           'POP ES
    ASM = ASM + CHR$(&H58)                          'POP AX
    ASM = ASM + CHR$(&H5B)                          'POP BX
    ASM = ASM + CHR$(&H59)                          'POP CX
    ASM = ASM + CHR$(&H5A)                          'POP DX
    ASM = ASM + CHR$(&HCF)                          'IRET

    BYTE = CHR$(INP(&H21)) 'LOAD IRQ ENABLE REGISTER IN PIC

    OUT &H21, (ASC(BYTE) AND (255 XOR 2)) 'CLEAR BIT 2 (IRQ 1)

    CALL GETVECT(OLDSEG, OLDOFF, &H9) 'LOAD OLD ISR
    CALL SETVECT(VARSEG(ASM), SADD(ASM), &H9) 'STORE NEW ISR
END SUB

SUB KEYBOARD.OUT (OLDSEG AS INTEGER, OLDOFF AS INTEGER)
    CALL SETVECT(OLDSEG, OLDOFF, &H9) 'RESTORE OLD ISR
END SUB

SUB SETVECT (S AS INTEGER, O AS INTEGER, I AS INTEGER)

    'SETVECT CHANGES THE ADDRESSES IN THE INTERRUPT VECTOR TABLE
    'TO POINT TO NEW FUNCTIONS

    STATIC ASM AS STRING 'HOLDS THE SETVECT FUNCTION
    STATIC INI AS INTEGER 'USED TO TEST WHETHER OR NOT FUNCTION HAS PREVOUSLY
                          'BEEN CALLED
    IF INI = 0 THEN

        'CREATE FUNCTION IF NOT ALREADY CREATED

        ASM = ""
        ASM = ASM + CHR$(&H55)                          'PUSH BP
        ASM = ASM + CHR$(&H89) + CHR$(&HE5)             'MOV BP,SP
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H8) 'MOV BX,[BP+08]
        ASM = ASM + CHR$(&H8B) + CHR$(&H17)             'MOV DX,[BX]
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&H6) 'MOV BX,[BP+06]
        ASM = ASM + CHR$(&H8A) + CHR$(&H7)              'MOV AL,[BX]
        ASM = ASM + CHR$(&H8B) + CHR$(&H5E) + CHR$(&HA) 'MOV BX,[BP+0A]
        ASM = ASM + CHR$(&H1E)                          'PUSH DS
        ASM = ASM + CHR$(&H8E) + CHR$(&H1F)             'MOV DS,[BX]
        ASM = ASM + CHR$(&HB4) + CHR$(&H25)             'MOV AH,25
        ASM = ASM + CHR$(&HCD) + CHR$(&H21)             'INT 21
        ASM = ASM + CHR$(&H1F)                          'POP DS
        ASM = ASM + CHR$(&H5D)                          'POP BP
        ASM = ASM + CHR$(&HCA) + CHR$(&H6) + CHR$(&H0)  'RETF 0006
        INI = 1 'FLAG CREATION
    END IF
    DEF SEG = VARSEG(ASM)
    CALL ABSOLUTE(S, O, I, SADD(ASM)) 'RUN SETVECT
END SUB
