; make wakeups optional (with a command line flag)
; external flag from mouseled on a wakeup (if next packet has non-xero X or Y, do a reinit -- because streaming mode has become disabled)?
; Must make sure that mouse stuff remains responsive enough to deal with LEDs (2 to 3 sec max delay), worst case while re-initting mouse
; -- ie. MAKE SURE NO BLOCKS OR RELOOPS ARE INFINITE!
; some HIHI error handling to deal with, still
; use tick_set for setting ebx in terms of seconds
; Have a (resettable?) flag to block any PS2 mouse initialization -- just do LED stuff -- mouse utility blocks forever on flag, first


; System defines:

kb_sstat	equ	0x201146	; bitfield for the keyboard shift status and LEDs (short)
dir_idx		equ	0x2015ec	; memory for directory index (fixed half)
vol_inft	equ	0x2015f4	; -> table containing current mounted volume info
mouseID		equ	0x20156c	; last non-packet non-ACK mouse byte received
mouse_ack	equ	0x20156d	; flag that a mouse ACK byte was seen & discarded
m_reinit	equ	0x20156e	; flag that a mouse 0xff init byte was seen & discarded
m_flags		equ	0x20156f	; flag bits: 7-activity, 6-btn4&5 NOT exist, 0-3/4 byte
m_pktpos	equ	0x201549	; current mouse packet flags (byte)
kbd_ack		equ	0x201548	; flag that a kbd ACK byte was seen (byte)
cpq_status	equ	0x2010c6	; compaq status command result from keyboard (byte)
s_newthrd	equ	0x201580	; entrypoint to "allocate" a new task tbl entry

; function call entrypoint table
rdir_ld		equ	0x200000
dir_srch	equ	0x200004
read28		equ	0x200008
free		equ	0x20000c
_nxtcls		equ	0x200010
asc_to_l	equ	0x200014
sm_longs	equ	0x200018
filrd_all	equ	0x20001c


; blocking defines
blk_onbyte	equ	0
blk_onshort	equ	0x40000000
blk_onlong	equ	0x80000000
blk_onqwrd	equ	0xc0000000
; bit tests are anded with di
blk_ANDisNE	equ	0
blk_ANDisEQ	equ	0x1000000
blk_ANDisGT	equ	0x2000000
blk_ANDisLT	equ	0x3000000
blk_ANDisA	equ	0x4000000
blk_ANDisB	equ	0x5000000
; 8 more possible ANDs for future expansion, if needed
blk_untilNE	equ	0x10000000
blk_untilEQ	equ	0x11000000
blk_untilGT	equ	0x12000000
blk_untilLT	equ	0x13000000
blk_untilA	equ	0x14000000
blk_untilB	equ	0x15000000

; task tbl (thread creation) defines:
rt_esp		equ	124


; local defines (variable offsets of workspace)
pend_reset	equ	0	; ps2 mouse seems inactive (byte)
work_todo	equ	4	; flag to wake up the main thread (byte)
led_old		equ	8	; last known state of "...lock" keys (byte)
dataloc		equ	12	; physical addy of DATA segment
tmpvar		equ	16	; reusable storage
; then 40 bytes each, for the "stacks" of the 2 wakeup threads


SEGMENT CODE USE32
; esi -> beginning of code (after signature)
; ebp -> data segment (at signature)
; edi -> command string
; eax = bss size
; edx -> bss segment

; Running in multitasking ring0, using 1K local stack, no other environment info passed in.
; NO REGISTERS need to be preserved.
	dw	0xc0de		; segment signature short -- init entrypoint immediately follows


; This program initializes any PS2 mouse at bootup, then waits forever to see if the mouse
;  ever needs to be re-initialized (if it gets unplugged & replugged)
; -- technically, this is never supposed to happen ... but just in case ....
; It also updates the keyboard LEDs according to the changing values of Num/Caps/Scroll lock keys.
; The point of having both the LED and mouse routines together is to avoid needing atomics,
; -- because the "two" routines will never be sending commands to the keyboard at the same time.

init:
	sub esp, 100				; allocate 25 dwords for local storage
	mov edx, esp				; point edx at local storage permanently
	mov [edx + work_todo], dword 1		; must initialize to non-zero (flag to subthreads)
	; (malloc initted all the rest of stack memory to 0)
	mov [edx + dataloc], ebp		; save DATA segment ptr

	xor ebx, ebx			; infinite timeout
	mov ebp, esi			; preserve code segment ptr through kbdp_wait
	mov ah, [cpq_status]		; recover "compaq status" byte from kernel storage
	mov al, ah			; make a copy
	or al, 2			; turn on irq12 bit -- mouse interrupt
	and al, 0xdf			; enable mouse clock (turn OFF bit 5)
	cmp ah, al
	je short .mouse_on		; if no change, cpq status byte was already correct
	mov [cpq_status], al		; store modified "compaq status" byte

; Note: mouse defaults into "disabled" streaming mode on power-up or reset
; turn on irq12 -- Delete this when the multitasking kbd driver is running!
.irq12_l1:
	call kbdp_wait			; wait until kbd controller is ready for command
	jne short .irq12_l1
	mov al, 0x60			; "set compaq status" command
	out 0x64, al
.irq12_l2:
	call kbdp_wait			; wait until kbd controller is ready for command
	jne short .irq12_l2
	mov al, ah			; "compaq status" byte
	out 0x60, al			; and write it back to kbd/mouse controller

.mouse_on:
	call kbdp_wait			; wait until kbd controller is ready for command
	jne short .mouse_on
	mov al, 0xA8			; enable mouse port
	out 0x64, al			; this command generates an ACK response on P60

	mov esi, ebp			; recover code seg ptr

	; spawn both utility threads
	xor eax, eax			; core #
	xor ecx, ecx			; storage for mouse tte pointer
; spawn the mouse reinit watching/blocking thread
	call [s_newthrd]		; preserves ecx, esi, eax, edx
	jnc short .LEDt
.fatal:
; fatal: no more task tbl entries avail (newthrd failed -- should never happen!)
; init-mt shouldn't have spawned this task unless there are a minimum # of tbl entries available
	jecxz .killme			; was mouse tte allocated yet?
	mov [ecx], eax			; zero out systask# in tte (disallocate tte)
.killme:
;	mov ebx, errorvalue
	int 0x31			; die -- NEVER RETURNS
	; HIHI put some simple code here that generates a GPF, in a loop

.LEDt:
	mov ecx, ebx			; save mouse tte pointer
; spawn the LED status watching/blocking thread
	call [s_newthrd]
	jc short .fatal

	; get both of the utility threads running (set up their stacks
	lea eax, [edx + 56]		; bottom of stack for LED thread
	lea edi, [esi + led_blk - init]	; entrypoint to LED thread
	call make_tstk
	mov ebx, ecx			; recover tte ptr for the mouse thread
	lea edi, [esi + mous_blk - init]	; entrypoint to mouse thread
	lea eax, [edx + 16]		; bottom of stack for mouse thread
	call make_tstk
	mov [m_reinit], byte 1		; init PS2 mouse, too

	jmp short LED_set		; always should initialize LEDs

main:
;		block until work_todo flag != 0, with infinite timeout --
; the mouse blocking thread will set work_todo every 10 seconds anyway
	xor ebx, ebx			; requested timeout = forever
	mov [edx + pend_reset], bl	; just starting into a wakemouse sleep loop
	and [m_flags], byte 0x7f	; clear "activity" flag, to test for NEW activity
reblock:
	lea esi, [edx + work_todo]	; memory address to block on
	mov [esi], bl			; both LED and mouse are always clear here: reset work_todo
	mov edi, blk_untilNE		; wait until work_todo(byte) is not 0
	int 0x30			; tell scheduler to block me

	mov al, [kb_sstat +1]		; get current LED state
	cmp al, [edx + led_old]		; have LEDs changed state?
	jne short LED_set
	movzx ecx, byte [m_reinit]	; is the PS2 mouse ready to initialize?
	jecxz wakemouse			; or was this a "timeout"?
do_minit:
; Send/parse full init sequence (user may have changed mouse types)
; send a "mouse found" message to OS? -- clear "mouse lost" flag? HIHI
	call mous_init
	mov al, [kb_sstat +1]		; get current LED state
	cmp al, [edx + led_old]		; have LEDs changed state?
	je short main			; go back and block until a status changes
LED_set:
	call do_LEDs
	movzx ecx, byte [m_reinit]	; PS2 mouse needs to be (re)initialized?
	jecxz main
	jmp short do_minit

wakemouse:
	test [m_flags], byte 0x80	; any mouse packets received since last sleep cycle?
	jne short main			; if so, things are normal: go back to sleep

	cmp bl, [edx + pend_reset]	; (ebx=0) already sent a wakeup?
	je short snd_wake

; mouse timed out twice, & did not respond to a wakeup command -- reset it!
force_reset:
	mov [m_pktpos], bh		; (bh=0) put mouse into "command mode"
	mov [m_flags], bh		; reinitialize flags -- mouse officially does not exist now
	mov al, 0xff			; reset command
	call mous_cmd
	jmp short main			; wait for mouse to send an AA reinit
snd_wake:
	mov [edx + pend_reset], byte 1	; sent wakeup flag
	movzx ebx, byte [m_pktpos]	; went to sleep in the middle of a packet?
	dec ebx				; (quick test, pktpos > 1)
	jg short force_reset		; if so, don't bother to wakeup -- just reset
	mov ebx, 512			; short timeout (.025 sec)
	call kbdp_wait
	mov al, 0xd4
	out 0x64, al			; prepare for mouse command
	call kbdp_wait
	mov al, 0xeb			; "request mousedata packet" command
	out 0x60, al
	xor ebx, ebx
	jmp near reblock		; see if mouse times out again


; ************* send and process the full init sequence to the PS2 mouse ****************
; preserves: edx (no others needed)
; mouse sample rate ends up at 80 per second, always
mous_init:
; Note: LED updates are on hold until this function completes -- make it quick!
; HIHI!!! or should I periodically (using tslice delay testing) check if LEDs need updating, and call the updater myself?
	mov esi, m_reinit		; address to block on
	mov ebx, 1024		; start with a .1 sec timeout
	mov [esi], bl		; (bl = 0) clear mouse reinitialization flag
	mov [m_pktpos], bl	; "deactivate" mouse packets -- enter "command mode"
	mov [m_flags], bl	; reinitialize flags
	mov al, 0xff		; tell mouse to reset
.mretry:
	shl ebx, 1		; double the timeout each loop, until it's set to 0 (forever)
	call mous_cmd
	mov edi, blk_untilNE
	int 0x30		; block (w/timeout) waiting on reset acknowledgement
	movzx ecx, byte [esi]	; unblocked on a timeout?
	jecxz .mretry		; if so, start over

; use the sample rate commands to determine if mouse has a scroll wheel & turn it on
	mov edi, [edx + dataloc]
	add edi, tst_sw		; command string to test for scroll wheel
	mov ah, 6		; send 7 bytes
	mov esi, mouseID	; address to block on later
	mov [esi], al		; set mouseID illegal (0xff)
.snd_swc:
	mov al, [edi]
	inc edi
	call mous_cmd		; preserves edi, esi
;	jne short ????	HIHI!!! what to do on timeout failures?
	dec ah
	jge short .snd_swc

	cmp [esi], byte 0xff
	jne short .notff1
	mov ebx, 6000		; timeout = .3 sec
	mov edi, blk_untilNE + 0xff
	int 0x30		; block waiting for a mouseID byte
.notff1:
	movzx ecx, byte [esi]
	jecxz plain_mse		; if mouseID == 0, it doesn't have a scroll wheel

	mov [m_flags], byte 0x41	; packet size= 4, button3 active, 4&5 NOT active

; use sample rate flags to try to turn on 4th & 5th mouse buttons
	mov edi, [edx + dataloc]
	add edi, tst_5b		; command string to test for 5 button mouse clone
	mov [esi], ah		; set mouseID illegal (0xff)
	mov ah, 7		; send 7 bytes
.snd_5bc:
	mov al, [edi]
	inc edi
	call mous_cmd
;	jne short ????	HIHI!!! what to do on timeout failures?
	dec ah
	jg short .snd_5bc

	cmp [esi], byte 0xff
	jne short .notff2
	mov ebx, 6000		; timeout = .3 sec
	mov edi, blk_untilNE + 0xff
	int 0x30		; block waiting for another mouseID byte
.notff2:
	mov al, byte [esi]
	cmp al, 4		; if mouseID==4 then it's a 5 button mouse
	jne short set_res
	mov [m_flags], byte 1	; activate button 4 and 5 (turn bit 6 OFF)
	jmp short set_res

plain_mse:
	mov [m_flags], byte 0x40	; packet size= 3, 4&5 NOT active
set_res:
	mov esi, [edx + dataloc]
	add esi, m_setres	; set resolution and start streaming packets!
	mov ah, 3		; send 3 bytes
.snd_sr:
	lodsb
	call mous_cmd
;	jne short ????	HIHI!!! what to do on timeout failures?
	dec ah
	jg short .snd_sr
	mov [m_reinit], ah	; clear reinit flag again (it was set a 2nd time)
	mov [m_pktpos], byte 1	; "activate" mouse packets
	ret


; ************* jump through hoops to send each command byte to mouse *****************
; preserves ebx, edx, esi, edi, eax -- destroys ebp, ecx (on success, ebp=0, ecx=1)
mous_cmd:
; writing command bytes to the mouse (at port 0x60) must be preceded by writing 0xD4 to port 0x64
; (must also wait for the ports to become ready to accept each byte)
	push edi			; kbdp_wait destroys edi, ch
	push esi
	push ebx
	mov ebx, 1024			; wait .05 second at most during each blocking wait

	; set up the outer loop
	mov ebp, 0x5000000		; loop 6 times, trying to write command
	mov cl, al			; save the command byte (cl is preserved until below)
.reloop:
	bswap ebp			; put outer loop count into bp (low byte)
	dec bp
	jl short .done			; after retries, fail on a NE condition
	bswap ebp			; store outer loop count back in upper ebp
	mov bp, 5			; subloop 6 times, waiting to write command

	; do the wait checking
.rewait:
	call kbdp_wait
	je short .snd_d4		; wait was successful?
	dec bp				; if kbdp_wait failed, retry it a few times
	jl short .done			; after retries, fail on a NE condition
	jmp short .rewait
.snd_d4:
	mov al, 0xd4
	out 0x64, al			; prepare for mouse command
	call kbdp_wait
	; assume success -- don't want to send multiple 0xd4's?

		; prepare to receive the ACK for next command
	mov esi, mouse_ack		; memory address to block on afterward
	mov [esi], byte 0		; clear the flag for testing
	mov al, cl			; recover the mouse command byte (only al was modified)
	out 0x60, al			; send requested mouse command -- finally!
	cmp al, 0xff			; if this was a reset command, skip the ACK
	je short .done

	mov edi, blk_untilNE		; wait until mouse_ack != 0 (byte)
	int 0x30			; tell scheduler to block me
	movzx ecx, byte [esi]		; did the ACK timeout?
	jecxz .reloop			; if so, redo the whole command process
	xor ebp, ebp			; force the zero flag to set ("success")
.done:
	pop ebx
	pop esi
	pop edi
	ret


; *************** sets the keyboard LEDs according to current kb_sstat **************
; preserves: edx (no others needed)
do_LEDs:
	call kbdp_wait
	xor eax, eax
	mov esi, kbd_ack
	mov [esi], al			; clear the ack flag, so it can be tested
	mov al, 0xed			; set LEDs command
	out 0x60, al
; wait for an ACK on port 0x60 -- *kbd becomes unresponsive* until this ack is read from the port!
	mov edi, blk_untilNE		; wait until kbd_ack != 0 (byte)
; HIHI!!! what's my real timeout, and what do I do on a timeout? Loop a few times and fail?
	mov ebx, 20000
	int 0x30
; then send the status byte with proper bits set: Scroll = 1, Num = 2, Caps = 4
	call kbdp_wait
	mov al, [kb_sstat +1]		; grab the new LED status (shifted up 2)
	mov [edx + led_old], al		; update "last known LED status"
	shr eax, 2
	out 0x60, al
	ret


; wait until p64 is ready to accept a command byte
; inputs: ebx = short but reasonable blocking delay (.05 sec sounds good)
; destroys: al, ch, esi, edi
; on success, zero flag is set (JE or JZ)
kbdp_wait:
	mov ch, 3			; loop 3 times only
.kbp_lp:
	dec ch
	jl short .prt_rdy		; if cl goes to -1, abort
	in al, 0x64			; test P64 status
	test al, 2			; is bit 1 of port 0x64 clear?
	je short .prt_rdy		; if not, try blocking on it
	mov esi, 0x64			; i/o addy to test
	lea edi, [blk_ANDisEQ +2]	; wait until p64 & 2 == 0 (byte)
	push edx
	xor edx, edx			; value to test against is 0
	int 0x30
	pop edx
	in al, 0x64			; did the bit clear during the block?
	test al, 2
	je short .prt_rdy
	test al, 1			; if not, is kbd busy with input?
	je short .kbp_lp
; maybe kbd IRQ1 handler is falling down on the job -- try to activate it directly
	int 0x21			; wake up the kbd driver
	jmp short .kbp_lp
.prt_rdy:
	ret


; ********** utility to set up and start a thread ************
; inputs: edx -> workspace, ecx -> bottom of stack, edi -> entrypoint
make_tstk:
	; set up the "registers" on the stack for thread initialization
	mov [eax], edx			; only need to pass edx -- pass it 7 times
	mov [eax + 4], edx
	mov [eax + 8], edx
	mov [eax + 12], edx
	mov [eax + 16], edx
	mov [eax + 20], edx
	mov [eax + 24], edx
	; prepare the iret info
	mov [eax + 28], edi		; entrypoint
	mov [eax + 32], dword 8		; cs
	mov [eax + 36], dword 0x202	; eflags (interrupts ON)
	mov [ebx + rt_esp], eax		; set "esp" in tte to start thread executing!
	ret


; utility thread to wake up the main thread when [reinit] state changes
mous_blk:
; block until the main thread is done processing, and has reset [reinit]
	mov edi, blk_untilEQ		; wait until work_todo (byte) clears
	xor ebx, ebx			; requested timeout = forever
	lea esi, [edx + work_todo]	; memory address to block on
	int 0x30			; tell scheduler to block me

; block me until the reinit flag sets, or it's time to check on the mouse
	mov edi, blk_untilNE		; wait until reinit (byte) sets
	mov esi, m_reinit		; memory address to block on
	mov ebx, 200000			; timeout = 10 secs
	int 0x30			; tell scheduler to block me

	mov [edx + work_todo], byte 1	; wake up the main thread
	jmp short mous_blk



; utility thread to wake up the main thread when LED state changes
led_blk:
; block until the main thread is done processing, and has reset [led_old]
	mov edi, blk_untilEQ		; wait until work_todo (byte) clears
	xor ebx, ebx			; requested timeout = forever
	lea esi, [edx + work_todo]	; memory address to block on
	int 0x30			; tell scheduler to block me

; block me until ***lock keys change state from last known state
	movzx ecx, byte [edx + led_old]	; value to check against (last known LED state)
	lea edi, [blk_untilNE + ecx]	; wait until newLED != oldLED (bytes)
	mov esi, kb_sstat +1		; memory address to block on
	int 0x30			; tell scheduler to block me

	mov [edx + work_todo], byte 1	; wake up the main thread
	jmp short led_blk	; and go back to waiting for LED state to change


align 4

SEGMENT DATA USE32			; base addy = 0, to add to passed in edx
	db	'Data'			; segment signature dword
tst_sw:			; 7 bytes
	db	0xf3		; set sample rate
	db	0xc8		; 200 per sec
	db	0xf3
	db	0x64		; 100 per sec
	db	0xf3
	db	0x50		; 80 per sec
	db	0xf2		; read device type
tst_5b:			; 7 bytes
	db	0xf3		; set sample rate
	db	0xc8		; 200 per sec
	db	0xf3
	db	0xc8		; 200 per sec
	db	0xf3
	db	0x50		; 80 per sec
	db	0xf2		; read device type
m_setres:		; 3 bytes
	db	0xe8		; set resolution
	db	3		; 8 counts per mm
	db	0xf4		; "turn on" streaming data

align 4

