        Name MSLK250
; MSLK250.ASM - A simple driver program for the DEC LK250 keyboard on IBM AT's
; Author:  Terry Kennedy, St. Peter's College, Terry@spcvxa.Bitnet.
;
; Edit history
; Last edit Sat Apr  6 18:02:34 1991
;  6 Apr 1991 Change internal references to this file to have right name [fdc]
; 17 Oct 1989 Add test for pre-80286 cpu and jmp $+2 breathers. [jrd]
; 16 Oct 1989 Sync USU, SPC versions; add initialization tests. [tmk] 
;  9 Oct 1989 Add building instructions etc. [jrd]
;
; Purpose: Allow access to LK250 features without requiring the user to have
;          any DEC drivers loaded.
;
; Method:  Intercept INT 15 subfunction 4F (SysReq) and 50 (Control). If en-
;          tered via 4F, place substitute scan code in buffer if the keypress
;          was for a key we're remapping *and* we are active. If entered via
;          50, perform control function as follows:
;
;               AL=00 - Disable substitution (keyboard "Special" [IBM] mode)
;               AL=01 - Enable substitution (keyboard DEC mode)
;               AL=02 - Send the byte in BL to the keyboard (caution: sending
;			"random" bytes with this function can lock the key-
;			board, necessitating a reboot.
;
; Construct final file MSLK250.COM by
;  masm msulk2;        reads file msulk2.asm, writes file msulk2.obj
;  link msulk2;        expect and ignore statement about no stack segment
;  exe2bin msulk2      produces file msulk2.bin from msulk2.exe
;  ren msulk2.bin msulk2.com    rename the result to our runnable filename
;  del msulk2.exe      delete non-runnable intermediate files .exe & .obj
;  del msulk2.obj
;
;  msulk2              run msulk2.com to install the driver
;
;
; Date:    20-Aug-1989, X0.0-00 - Original version
;	   26-Aug-1989, X0.0-01 - Pass Shift-PrtSc through even in DEC mode
;	   16-Oct-1989, X0.0-02 - Add initialization tests suggested by jrd
;	   17-Oct-1989, X0.0-03 - Add cpu test, add time between tests. jrd
;	   28-Nov-1990  X0.0-04 - Fix problem on PS/2's where special keys
;				  were followed by a spurious divide symbol.
;	   29-Nov-1990  X0.0-05 - More PS/2 fixes.
;
main    group   code
;
data    segment at 40h
;
        db      23 dup (?)
shift	db	?
	db	2 dup (?)
head    dw      ?
tail    dw      ?
buff    dw      16 dup (?)
        db      66 dup (?)
bstart  dw      ?
bend    dw      ?
        db      19 dup (?)
kbflg2  db      ?
;
data    ends
;
code    segment public para 'code'
assume  cs:main,ds:nothing
;
        org     100h                    ; making a.COM file
;
begin:  jmp     start                   ; program starts here
;
saved15 dd      ?                       ; previous int 15 vector
active  db      0                       ; are we translating?
;
bail:   jmp     [saved15]
;
; This is the new interrupt 15 routine.
;
newi15  proc    far
        assume  ds:data
        cmp     ah,50h                  ; control function?
        jne     noctl
	jmp	ctlfnc                  ; if so...
noctl:  cmp     ah,4fh                  ; keyboard intercept?
        jne     bail                    ; nope, time to leave...
        cmp     active,1                ; are we active?
        jne     bail                    ; no, just exit...
        cmp     al,01h                  ; Esc/PF1?
        je      kpf1                    ; if so
        cmp     al,45h                  ; Num Lk/PF2?
        je      kpf2                    ; if so
        cmp     al,46h                  ; Scrl Lk/PF3?
        je      kpf3                    ; if so
        cmp     al,37h                  ; PrtSc / PF4?
        je      kpf4                    ; if so
        cmp     al,54h                  ; is it a scan we handle?
        jl      bail                    ; nope, bail out...
        cmp     al,69h
        jg      bail                    ; likewise...
        jmp     putkey                  ; otherwise go save the key...
;
kpf1:   mov     al,6ah                  ; make PF1 scan 6ah
        jmp     putkey
;
kpf2:   mov     al,6bh                  ; make PF2 scan 6bh
        jmp     putkey
;
kpf3:   mov     al,6ch                  ; make PF3 scan 6ch
        jmp     putkey
;
kpf4:	push	ds			; save user's [DS]
	mov	ax,40h
	push	ax
	pop	ds
	mov	al,shift		; get shift states
	pop	ds			; restore user's [DS]
	and	al,3			; either shift state set?
	jz	kpf4a			; nope, this key gets translated
	mov	al,37h			; yes, we didn't want to translate it
	jmp	bail			; oh, this is a bad pun...
;
kpf4a:	mov     al,6dh                  ; make PF4 scan 6dh
        jmp     putkey
;
; stuff a scancode into the keyboard buffer
;
putkey: push	ax			; edit 04 - save registers for PS/2
	push	bx			; "
	push	si			; "
	mov     ah,al                   ; copy scan to someplace useful
        mov     al,0
        push    ds                      ; save user's [DS]
        mov     bx,40h                  ; point to system space
        push    bx
        pop     ds
        mov     bx,tail                 ; offset of buffer tail
        mov     si,bx
        inc     bx
        inc     bx                      ; point to next free
        cmp     bx,bend                 ; at end?
        jne     nowrap                  ; if not...
        mov     bx,bstart               ; else wrap buffer
nowrap: cmp     bx,head                 ; buffer full?
        je      bufull                  ; if so...
        mov     [si],ax                 ; else store character
        mov     tail,bx                 ; and update pointer
bufull: pop     ds                      ; restore [DS]
	pop	si			; edit 04 - restore registers for PS/2
	pop	bx			; "
	pop	ax			; "
        clc                             ; say we did something...
	retf	2			; edit 05 - preserve flags
;
; control functions: enable / disable translation, set LED's, set click
; volume, set auto-repeat rate
;
ctlfnc: cmp     al,0                    ; disable translation?
        jne     tst1                    ; no
        mov     active,al               ; else do it
        mov     al,0adh                 ; set keyboard mode
        call    kbsend
        jmp     exit                    ; and exit
;
tst1:   cmp     al,1                    ; enable translation?
        jne     tst2                    ; no
        mov     active,al               ; else do it
        mov     al,0ach                 ; set keyboard mode
        call    kbsend
        jmp     exit                    ; and exit
;
tst2:   cmp     al,2                    ; send to keyboard?
        jne     error                   ; nope, must be an error
        mov     al,bl                   ; byte to [AL] for send
        call    kbsend                  ; yes, send the byte out
        jmp     exit                    ; and exit
;
error:  mov     ax,0                    ; say bad function
        iret                            ; and exit
;
exit:   mov     ax,1234h                ; say we did it
        iret                            ; and exit
;
newi15  endp
;
; send a byte to the keyboard controller
;
kbsend  proc    near
        push    ax                      ; save some regs
        push    bx
        push    cx
        push    ds                      ; save user's [DS]
        mov     bx,40h                  ; point to system space
        push    bx
        pop     ds
        mov     bh,al                   ; copy data byte for retry
        mov     bl,3                    ; set retry count
sd0:    cli
        and     kbflg2,0cfh             ; turn off ack, re-send bits
        sub     cx,cx                   ; a nice big loop
sd1:    in      al,64h
	jmp	$+2			; reduce looping rate
        test    al,2
        loopnz  sd1                     ; if not ready
        mov     al,bh
        out     60h,al                  ; send the command
        sti
        mov     cx,2800h                ; wait a bit
sd3:    test    kbflg2,30h              ; anything happen?
        jnz     sd7                     ; seems so, go handle
	jmp	$+2			; reduce looping rate
        loop    sd3                     ; else wait
sd5:    dec     bl                      ; call it a retry if timed out
        jnz     sd0                     ; if we have another chance
        or      kbflg2,80h              ; otherwise fail it
        jmp     sd9                     ; and exit
;
sd7:    test    kbflg2,10h              ; got a proper ack?
        jz      sd5                     ; nope, re-send it
sd9:    pop     ds
        pop     cx
        pop     bx
        pop     ax
        ret
;
kbsend  endp
;
endres  label   byte                    ; end of resident code
;
; Code after here will not remain resident
;  Cpu test uses DOS's stack [jrd]
start:					; begin with cpu test [jrd]
	push	sp			; push DOS's SP, 8088's push old SP-2
	pop	ax			; 286's and higher push old SP
	mov	dx,offset main:cpumsg	; prepare bad news message
	cmp	ax,sp			; pre versus post push SP's
	jne	start0			; ne = an 8088, sorry 'bout that [jrd]
	mov     ax,5000h                ; see if we are already loaded
        int     15h                     ; look for DOS->DEC mode driver
        cmp     ax,1234h                ; find marker 1234h
        jne     start1                  ; ne = marker not present, no driver
        mov     dx,offset main:errmsg   ; say we're already loaded
start0: mov     ah,9
        int     21h
	int	20h			; and bail out
;
start1: mov     ax,3515h                ; get existing INT 15 vector
        int     21h
        mov     word ptr [saved15],bx   ; save it
        mov     word ptr [saved15+2],es
        mov     dx,offset main:newi15   ; set new INT 15 vector
        mov     ax,2515h
        int     21h                     ; set new vector from DS:DX
;
        mov     dx,offset main:lodmsg   ; say we're loaded
        mov     ah,9
        int     21h
;
        mov     ax,ds:[2ch]             ; de-allocate the environment
        mov     es,ax                   ; load envirnoment segment into es
        mov     ah,49h                  ; DOS function number
        int     21h                     ; free the environment memory
;
        mov     dx,offset main:endres   ; point to end of resident code
        add     dx,0fh                  ; round up
        mov     cl,4
        shr     dx,cl                   ; convert to paragraphs (divide by 16)
        mov     ax,3100h                ; DOS function 31h, error code=0
        int     21h                     ; terminate and remain resident
;
errmsg:	db	0dh,0ah,'MSULK2 is already loaded',0dh,0ah,07h,'$'
lodmsg: db      0dh,0ah,'MSULK2 X0.0-05 loaded',0dh,0ah,'$'
cpumsg:	db	0dh,0ah,'MSULK2 requires a 286 (AT) machine or higher'
	db	0dh,0ah,'$'		; wrong cpu type msg  [jrd]
;
code    ends
        end     begin                   ; start execution at BEGIN
