Kbd.inc
[ Return to Browse Source Page ]
;DEBUG_KBD equ 0

; Keyboard.inc
; A standard keyboard driver.
; Instead of just referrencing a few BIG tables that have the
;  states of each key for each combination of CTRL, ALT,
;  SHIFT, CAPS LOCK and NUM LOCK, i break the keyboard into
;  similarly-operating sections and use patterns to reduce the
;  size of data needed. (e.g. if (caps ^ shift), you can just
;  subtact 0x20 to the letter value to get it's caps value.)
; This next thing needs some explanation:
; Each code section for similarly-operated keys (i.e. letter
;  keys in this example has entrances like this:
;    kbdi_alpha3: sub al,5
;    kbdi_alpha2: sub al,4
;    kbdi_alpha1: sub al,010h
;  This is actually fairly efficient -
;  The first group of alphabetical keys start with "Q", which
;   has the scancode of 0x10. We want to do something to these keys
;   so that "Q" is 0, since it is the first entry in the table
;   for alphabetical keys. So we subtract 0x10. "W" is 0x11, and
;   that becomes 1 when 0x10 is subtracted. good. "E" is 0x12->2,
;   etc. This works nicely since until "A", their scancodes are
;   all next to each other. Since keys from 0x10 (Q) to 0x19
;   are sent to kbdi_alpha1, the output of the subtraction at
;   kbdi_alpha1 is 0 through 9. "A" is code 0x1E. It will enter
;   at kbdi_alpha2 from the system of switches. We want this
;   to be 10 (0x0A) since 0 through 9 are used. It will already
;   be subtracted by 0x10 since it will run through the
;   subtraction at kbdi_alpha1. so 0x1E - 0x10 = 0x0E (12).
;   To get from 12 to 10, we have to subtract 4. so now it's
;   if (in_group(kbdi_alpha1)) subtract(0x04+0x10);
;   if (in_group(kbdi_alpha2)) subtract(0x10);
;   With enough of these if statements encoded as successive
;   subtract operations, the scancodes are relocated and the
;   new values can be used as an index into a table containing
;   the values for the scancodes.
; Is it unnecessarily complicated?
;   I'm an assembly programmer. I like unnecessary complication
;    if it will make smaller code. :-)
; Does it make smaller code?
;   Yes. The tables alone from the Linux 2.0 keyboard driver
;    are 3k+400 bytes. Again, not including any code. Not even
;    all the tables. (that figure is just for what was in
;    KBD_KERN.H)
;    My entire keyboard system, including a cin proc, is 1k+28
;    bytes. (as of 0.00.0032)

Kbd_int:
pushad
sti
cld
mov eax,os_data
push ds
mov ds,ax
push es
mov es,ax
xor eax,eax

in al,064h
test al,1
in al,060h ; some systems have interrupts w/ no data reported to 64h after
           ;  command completion (source: Linux 2.0 Keyboard.c)
jz kbd_int_empty  ; possibly another device on IRQ1. or some
                  ; troublemaker's software-generated interrupt.
                  ; carry denotes timeout (5 tries)
ifdef DEBUG_KBD
 call print_hex
endif
cmp al,0E0h
jz kbd_E0_flagset
cmp al,0E1h
jz kbd_reset
mov ah,ds:[kbd_e0_flag]
test ah,ah
jnz kbd_E0_mask
kbd_E0_mask_ret:
mov ebp,dword ptr ds:[kbd_last]
; + mov bp,ds:[kbd_last], without the renaming stall :-)
xor edx,edx
mov dx,ds:[kbd_flags]
; + mov dx,ds:[...] / movzx edx,dx
test al,080h
jnz kbd_break_clip
mov ds:[kbd_last],ax
shl ebp,16
add ebp,eax
; + mov bp,ax
test al,al
jz kbi_err ; 0 or 0FFh = Keyboard Error (happens w/ key overflow or when
           ;  the keyboard is being plugged in)

;decode:

cmp al,02
jb kbdi_command1
cmp al,0Eh
jb kbdi_ascii1
cmp al,010h
jb kbdi_command2
cmp al,01Ah
jb kbdi_alpha1
cmp al,01Ch
jb kbdi_ascii2
jz kbdi_command3
cmp al,01Dh
jz kbdi_switch_ctrl
cmp al,027h
jb kbdi_alpha2
cmp al,02Ah
jb kbdi_ascii3
jz kbdi_switch_lshift
cmp al,02Bh
jz kbdi_ascii4
cmp al,033h
jb kbdi_alpha3
cmp al,035h
jb kbdi_ascii5
jz kbdi_divide
cmp al,037h
jb kbdi_switch_rshift
jz kbdi_semiconstant1
cmp al,039h
jb kbdi_switch_alt
jz kbdi_spacebar
cmp al,03Ah
jz kbdi_lights_caps
cmp al,045h
jb kbdi_command4
cmp al,47h
jb kbdi_lights_numscroll
cmp al,04Ah
jb kbdi_keypad1
jz kbdi_semiconstant2
cmp al,04Eh
jb kbdi_keypad2
jz kbdi_semiconstant3
cmp al,054h
jb kbdi_keypad3
jz kbdi_command5
cmp al,057h
jb kbd_int_end
cmp al,059h
jb kbdi_command6
cmp al,05Bh
jb kbd_int_end
cmp al,05Eh
jb kbdi_command7
jmp kbd_int_end

;-----------------------------------------------------------------
kbdi_alpha3: sub al,5
kbdi_alpha2: sub al,4
kbdi_alpha1: sub al,010h
mov ebx,offset kbi_alpha_table
and eax,0FFh
mov al,[eax+ebx] ; +xlat
test dh,08h
jz kbdi_alpha_nocaps
sub al,020h
kbdi_alpha_nocaps:
push offset kbd_int_end
jmp put_key_2 ; call put_key_2 / jmp kbd_int_end

kbdi_ascii5: sub al,7
kbdi_ascii4: dec al
kbdi_ascii3: sub al,0Bh
kbdi_ascii2: sub al,0Ch
kbdi_ascii1: sub al,2
mov ebx,offset kbi_ascii_table
and eax,0FFh
test dh,1
jz kbdi_ascii_noshift
mov ebx,offset kbi_ascii_sh_table
kbdi_ascii_noshift:
mov al,[eax+ebx] ; +xlat
jmp kbdi_alpha_nocaps

kbdi_keypad3: dec al
kbdi_keypad2: dec al
kbdi_keypad1: sub al,047h
mov cl,dh
shr cl,4
and cl,1
xor cl,1
or cl,ah
test cl,cl
jnz kbdi_keypad_command
and eax,0FFh
or dl,010b ; ext
mov ebx,offset kbi_keypad_table
mov al,[eax+ebx] ; +xlat
jmp kbdi_alpha_nocaps


kbdi_command7: add al,9
kbdi_command6: sub al,5
kbdi_command5: sub al,0Dh
kbdi_command4: sub al,01Eh
kbdi_command3: sub al,0Ch
kbdi_command2: sub al,0Ch
kbdi_command1: dec al
kbdi_command_entrance:
and ah,1
or dl,1
shl ah,1
or dl,ah
push offset kbd_int_end
jmp put_key_2 ; call put_key_2 / jmp kbd_int_end

kbdi_switch_alt:    sub al,019h
kbdi_switch_ctrl:   add al,01Ah
                    add al,ah
kbdi_switch_rshift: sub al,0Bh
kbdi_switch_lshift: sub al,028h
mov cl,al
mov bl,1
shl bl,cl
or dl,bl
kbdi_switch_bentry:
push offset kbd_int_end
jmp kbd_shift_logic ; call kbd_shift_logic / jmp kbd_int_end

kbi_switch_balt:     sub al,019h
kbi_switch_bctrl:    add al,01Ah
                    add al,ah
kbi_switch_brshift: sub al,0Bh
kbi_switch_blshift: sub al,028h
xor edx,edx
mov dx,ds:[kbd_flags]
; + mov dx,ds:[kbd_flags] / movzx edx,dx
mov cl,al
mov bl,0FEh
rol bl,cl
and dl,bl
jmp kbdi_switch_bentry

kbdi_keypad_sh_5:
or dl,2     ; extraneous space
and dl,0FEh ; not control
kbdi_spacebar:
mov al,020h
jmp kbdi_alpha_nocaps

kbdi_semiconstant3: sub al,6
kbdi_semiconstant2: sub al,01Dh
or dl,010b
jmp kbdi_alpha_nocaps


kbdi_semiconstant1: ; (* key)
test ah,ah
jnz kbdi_printscreen
mov al,'*'
or dl,010b
jmp kbdi_alpha_nocaps
kbdi_printscreen:
mov al,010h ; print screen key
jmp kbdi_command_entrance



kbdi_keypad_command:
cmp al,4
jz kbdi_keypad_sh_5
mov ebx,offset kbi_keypad_sh_table
and eax,0FFh
mov al,[eax+ebx] ; +xlat
jmp kbdi_command_entrance


kbd_int_end:
xor al,al
kbd_E0_flagset_ret:
mov ds:[kbd_e0_flag],al
kbd_int_empty:
mov al, 020h
out 020h, al  ; send EOI to primary PIC
pop eax
mov es,ax
pop ebx
mov ds,bx

popad
iretd

kbd_E0_flagset:
mov al,1
jmp kbd_E0_flagset_ret


kbi_blshift_or_reset: ; 0xAA means Basic Assurance Test passed.
test dl,0100b         ;  That happens when the system resets power or
jnz kbi_switch_blshift;  the keyboard is pulled out and plugged back in.
push offset kbd_int_end
jmp kbd_hardware_init ; 0xAA also is the break code for the left shift key.
;jmp kbd_int_end      ;  So... if we have shift on record as being held
                      ;  down, respond to 0xAA as a shift key up. Otherwise
                      ;  init the keyboard. Worst case scenario for this is
                      ;  an 0xAA is caught while the shift key is held down.

kbi_err:
; Printf("Keybaord is complaining.\n"); ??
; 0x00 or 0xFF is encountered when too many keys are being held
; down or when the keyboard is being re-inserted into the computer.
; in both cases we should clear the shift alt and ctrl flags.
; In the first case, because there is now an unknown shift state,
; And in the second, we want to clear the shift flag so that
; when the keyboard handler gets an 0AAh from the keybaord, it will
; know that it's a Keyboard Setup message, not the shift key being
; released (same code for both). It can then setup the keyboard.
xor dl,dl

jmp kbd_int_end
jmp kbd_shift_logic ; call kbd_shift_logic / jmp kbd_int_end

;; These 2 lines are = to the following two
;push kbd_int_end
;;jmp kbd_shift_logic
;; the jmp isn't needed since they are right next to eachother.

kbd_shift_logic:
push eax
mov eax,edx
and ax,01110000011111100b
test al,00001100b ; just the shifts.
jz kbd_shift_logic_1
or ah,01b
kbd_shift_logic_1:
test al,00110000b ; ctrl's
jz kbd_shift_logic_2
or ah,010b
kbd_shift_logic_2:
test al,011000000b ; alt's
jz kbd_shift_logic_3
or ah,0100b
kbd_shift_logic_3:
test ah,00100001b

jpe kbd_shift_logic_4
; i'd just like to add that this is the first time i've ever written this
;  instruction as well as the first time i've ever seen it used in any
;  practical setting (outside HelpPC and the Intel manuals).

or ah,01000b
kbd_shift_logic_4:
test ah,01000001b
jpe kbd_shift_logic_5
or ah,010000b
kbd_shift_logic_5:
mov ds:[kbd_flags],ax
mov edx,eax
pop eax
ret


kbd_E0_mask:
mov bl,al
mov bh,al
xor ax,ax
and bl,07Fh
cmp bl,02Ah
jz kbd_int_end
cmp bl,036h
jz kbd_int_end
cmp bl,046h  ; ctrl-break will always be reset.
jz kbd_reset
mov ah,1
mov al,bh
jmp kbd_E0_mask_ret


kbd_do_leds:
push eax
push ebx
push ecx
mov al,dh
shr al,5
mov bl,al
mov bh,al
and bl,010b
and al,0100b
and bh,01b
shl bh,2
shr al,2
or bl,bh
or bl,al
call kbd_wait
jc kbd_leds_nope
mov al,0edh ; write leds
out 060h,al
call kbd_wait
jc kbd_leds_nope
mov al,bl
out 060h,al
kbd_leds_nope:
call kbd_shift_logic
mov ds:[kbd_flags],dx
pop ecx
pop ebx
pop eax
clc
ret

kbd_break_clip:
cmp al,0FAh
jz kbd_int_end
mov ds:[kbd_last],ax
cmp al,01Dh+080h
jz kbi_switch_bctrl
cmp al,02Ah+080h
jz kbi_blshift_or_reset
cmp al,036h+080h
jz kbi_switch_brshift
cmp al,038h+080h
jz kbi_switch_balt
cmp al,07Fh+080h
jz kbi_err ; 0 or 0FFh = Keyboard Error (happens w/ key overflow or when
           ;  the keyboard is being plugged in)
           ; 0FFh & 07Fh = 07Fh
jmp kbd_int_end


kbd_wait:
push ecx
push eax
mov ecx, 0FFFFh
kbd_wait_loop:
in al,064h
test al,2
jz kbd_wait_1
dec ecx
jnz kbd_wait_loop
stc ; timeout
jmp kbd_wait_2
kbd_wait_1:
clc
kbd_wait_2:
pop eax
pop ecx
ret

kbd_wait2:
push ecx
push eax
mov ecx, 0FFFFh
kbd_wait2_loop:
in al,064h
test al,1
jnz kbd_wait2_1
dec ecx
jnz kbd_wait2_loop
stc
jmp kbd_wait2_2
kbd_wait2_1:
clc
kbd_wait2_2:
pop eax
pop ecx
ret


;OS_Data entries:
;
;ignore_scancodes db 0
;kbd_cur dw 0
;kbd_last dw 0

; how last_byte works:
;  it starts off as 0
;  after that it is the previous scan code recieved.
;  this is for repeating. we don't want to repeat a key
;  unless the buffer is empty, or else slow programs don't
;  run so good. (i.e. my Mandelbrot fractal program for
;  DOS. you hold arrow, and you have to wait for minutes
;  fot it to stop.)
;  the logic is this:
;  if (kbd_last == current_scancode){
;     if (!(buffer is empty)) goto end;
;     put_buffer(current_scancode);
;  }
;  so if a break code is recieved, this would reset the
;  last_byte recieved, so type-ahead buffering isn't screwed up.
;  drawbacks: if you're type-aheadding while waiting for a slow
;     app to close or something, if you pressed "A" then held
;     space for a while, the fact that there would be only one
;     space after the A could take some getting used to.
;  but i still think it works better than BIOS's system.


; Roads Less Traveled (grouped together so that we're not loading
;  them too often into the code cache).

kbdi_lights_numscroll:     sub al,0Ah
kbdi_lights_caps:    sub al,03Ah
mov ebx,ebp
rol ebp,16
xor ebx,ebp
jz kbd_int_end
mov cl,al
mov al,00100000b
shl al,cl
xor dh,al
push offset kbd_int_end
jmp kbd_do_leds ; call kbd_do_leds / jmp kbd_int_end


kbdi_divide:
test ah,ah
jnz kbdi_div_noshift
test dh,1
jz kbdi_div_noshift
mov al,'?'
jmp kbdi_alpha_nocaps
kbdi_div_noshift:
mov al,'/'
shl ah,2
add dl,ah
jmp kbdi_alpha_nocaps



kbd_init:
mov esi,offset kbd_init_str
mov ax,fs
mov ds,ax
push offset kbd_init_1
push offset kbd_id
jmp Print ; call print / call kbd_id
          ; more of an experiment than an optimization
kbd_init_1:
pushfd
mov esi,ebx
call Print
popfd
jc kbd_init_notok
mov esi,offset OK_0d
call Print
clc
kbd_init_notok:
push offset kbd_enable
jmp kbd_hardware_init_postid ; call kbd_h_i_p / call kbd_e / ret

kbd_id:
call kbd_disable ; necessary - can be run from both INT and SYS contexts.
mov ebx,offset kbd_init_pcxt ; the return dword. ptr -> string
xor edx,edx           ; the return byte (dl). 0=PC/XT,1=AT,2=MF II
mov al,0f2h
out 060h,al
kbd_id_tryagain1:
call kbd_wait2
jc kbd_id_ret
in al,060h ; empty buffer
ifdef DEBUG_KBD
 call print_hex
endif
cmp al,0feh
jz kbd_id_err
cmp al,0fah
jnz kbd_id_tryagain1 ; if !ACK, then could just be a keystroke. ignore it.
mov ebx,offset kbd_init_at
inc edx ; not a PC/XT
kbd_id_tryagain2:
call kbd_wait2
jc kbd_id_ret
in al,060h
ifdef DEBUG_KBD
 call print_hex
endif
cmp al,0ABh ; first MFII ID byte
jnz kbd_id_tryagain2
kbd_id_tryagain3:
call kbd_wait2
jc kbd_id_ret
in al,060h
ifdef DEBUG_KBD
 call print_hex
endif
cmp al,041h
jnz kbd_id_tryagain3
mov ebx,offset kbd_init_mfii
inc edx ; not an AT
kbd_id_ret:
call kbd_wait
jc kbd_id_ret_norr
mov al,0f3h
out 60h,al
call kbd_wait2
jc kbd_id_ret_norr
xor al,al ;00100000b seems too standardish - OP?
out 60h,al
kbd_id_ret_norr:
call kbd_enable
mov eax,PDA
push ds
mov ds,ax
mov ds:[PDA_KbdType],dl
pop ds
in al,21h
and al,011111101b ; enable IRQ 1
out 21h,al
clc
ret

kbd_id_err:
mov ebx,offset not_detected
mov dl,0FFh
call kbd_id_ret_norr ; (grin)
stc
ret

kbd_disable:
push edx
push eax
mov dx,021h
in al,dx
or al,010b ; disable IRQ 1
out dx,al
pop eax
pop edx
ret


kbd_enable:
push edx
push eax
mov dx,021h
in al,dx
and al,011111101b ; enable IRQ 1
out dx,al
pop eax
pop edx
ret

kbd_hardware_init:
call kbd_id
kbd_hardware_init_postid:
jc kbd_hardware_nothere
call kbd_wait
jc kbd_hi_dontenable
mov al,0aeh
out 64h,al
kbd_hi_dontenable:
mov dx,ds:[kbd_flags]
; print [ebx], " OK"
call kbd_do_leds
kbd_hardware_nothere:
ret


print_hex_2:
push eax
call print_hex
mov al,020h
push offset print_char
call print_char ; call print_char / call print_char
pop eax
ret

kbd_reset:
jmp reset

put_key_2:
;cli
mov ebx,ebp
rol ebp,16
xor ebx,ebp
jnz put_key_2_yes
mov bl,ds:[kbd_buflen]
test bl,bl
jnz put_key_2_no
put_key_2_yes:

;
; this used to be a separate procedure, called from
;  put_key_2 at this point. it was called put_key.
; Using a 32 - key, 96 - byte buffer.
;cli
xor ecx,ecx
mov cl,ds:[kbd_buflen]
cmp ecx,32
jz put_key_end
xor ebx,ebx
mov bl,ds:[kbd_head]
lea edi,[offset kbd_buffer + ebx]
mov [edi],al
mov [edi+1],dx
lea ebx,[ebx+3]
cmp ebx,96
jb put_key2
xor ebx,ebx
put_key2:
inc ecx
mov ds:[kbd_head],bl
mov ds:[kbd_buflen],cl
put_key_end:
;sti
;
put_key_2_no:
ret

get_key:
push ebx
push ecx
push esi
push ds
push OS_Data
pop ds
call kbd_disable ; cli/sti out of the question?
xor ecx,ecx
mov cl,ds:[kbd_buflen]
test ecx,ecx
; + mov cl,ds:[] - avoids more renaming
; + or cl,cl
jz get_key_err
mov esi,offset kbd_buffer
xor ebx,ebx
mov bl,ds:[kbd_tail]
; + movzx ebx,bl
add esi,ebx
;<+>
xor eax,eax
mov al,[esi]
xor edx,edx
mov dx,[esi+1]
; + lodsb
; + movzx edx,al
; + lodsw
; + movzx eax,ax
; + xchg eax,edx
;
dec ecx
lea ebx,[ebx+3]
cmp ebx,96
jb get_key_nocarry1
xor ebx,ebx
get_key_nocarry1:
mov ds:[kbd_tail],bl
mov ds:[kbd_buflen],cl
clc
get_key_end:
pushfd
call kbd_enable
popfd
pop ds
pop esi
pop ecx
pop ebx
ret
get_key_err:
stc
jmp get_key_end


kbd_got_byte:
in al,060h
ifdef DEBUG_KBD
 call print_hex
endif
clc
ret

kbd_get_byte:
; timeout on 120 tries
xor eax,eax
lea ecx,[eax+120] ; smaller than mov. (faster? - executes on a p6
                 ;    while the in al,64h instruction is in progress).
kbd_get_byte_loop:
in al,064h
test al,1
jnz kbd_got_byte
 ; note: kbd_got_byte moved above the jcc so that
 ;        it would be assumed to be taken by the
 ;        static predictor
dec ecx
jnz kbd_get_byte_loop
stc
ret


get_key_1:
call get_key
jc get_key_1
ret

stringin:
 push offset do_crlf
 ;jmp stringin_nb

stringin_nb: ; cx = number of characters (including the 0 at the end)
pushad
push es
cmp ecx,2
jb stringin_end
push os_data
pop es
mov edi,offset stringin_buffer
dec ecx
mov ebp,ecx
call getcursorpos
        stringin_loop:
        call get_key_1
        test dl,1
        jnz stringin_notascii
        test ecx,ecx
        jz stringin_loop
        ; + jcxz stringin_loop
        stosb
ifndef DEBUG_KBD
        call print_char
endif
        dec ecx
        jmp stringin_loop

stringin_end:
xor al,al
stosb
pop es
popad
ret

stringin_notascii:
        cmp al,03
        jz stringin_end
        cmp al,01
        jz stringin_bs_clip
        cmp al,019h
        jz stringin_bs_clip
        jmp stringin_loop

stringin_bs_clip:
        cmp ecx,ebp
        jnb stringin_loop
        inc ecx
        dec edi
ifndef DEBUG_KBD
        call print_char_keyctrl
endif
        jmp stringin_loop

ifdef NEVER


push edi
   mov edi,offset get_num_c32
pop edi

get_num:
mov eax,0ffffffffh
push offset do_crlf
;jmp get_num_nb

get_num_nb:
push esi
push edi
push ebx
push ecx
push edx
push ds

ax key/num
bx num backup
cx key
dx mul overrun
si count
di limit
bp ten

xor esi,esi
xor ecx,ecx
xor ebx,ebx
mov edi,eax ; limit parameter
; edx/eax can be set at first
lea ebp,[esi+10] ; save a few bytes?
     getnum_loop:
        call get_key_1
        test dl,1
        mov cl,al
         jnz getnum_ascii
        xor cl,30h
        mov eax,ebx
        test cl,0f0h
         jnz getnum_loop
        mul ebp
        add eax,ecx
         jc getnum_loop ; result too big, abort
        cmp eax,edi
         ja getnum_loop ; same here, too big
        mov ebx,eax ; it's small enough, save results
        lea eax,[ecx+030h] ; convert digit back to ascii
        lea esi,[esi+1]
        call print_char
        jmp getnum_loop
     getnum_ascii:
        cmp al,;;;;;;;;;;;;;;;;;;;;;;;;
        test esi,esi


pop ds
pop edx
pop ecx
pop ebx
pop edi
pop esi
ret

get_num_32:
mov eax,0FFFFFFFFh
call get_num
ret

endif


Download this file.


[ Return to Browse Source Page ]
Copyright 2000, Ed Pizzi