forked from KolibriOS/kolibrios
476 lines
18 KiB
PHP
476 lines
18 KiB
PHP
|
; HID keyboard driver, part of USBHID driver.
|
||
|
|
||
|
; Global constants.
|
||
|
; They are assembled in a macro to separate code and data;
|
||
|
; the code is located at the point of "include 'keyboard.inc'",
|
||
|
; the data are collected when workers_globals is instantiated.
|
||
|
macro workers_globals
|
||
|
{
|
||
|
; include global constants from previous workers
|
||
|
workers_globals
|
||
|
align 4
|
||
|
; Callbacks for HID layer.
|
||
|
keyboard_driver:
|
||
|
dd keyboard_driver_add_device
|
||
|
dd keyboard_driver_disconnect
|
||
|
dd keyboard_driver_begin_packet
|
||
|
dd keyboard_driver_array_overflow?
|
||
|
dd keyboard_driver_input_field
|
||
|
dd keyboard_driver_end_packet
|
||
|
; Callbacks for keyboard layer.
|
||
|
kbd_functions:
|
||
|
dd 12
|
||
|
dd CloseKeyboard
|
||
|
dd SetKeyboardLights
|
||
|
; Kernel keyboard layer takes input in form of PS/2 scancodes.
|
||
|
; data for keyboard: correspondence between HID usage keys and PS/2 scancodes.
|
||
|
EX = 80h ; if set, precede the scancode with special scancode 0xE0
|
||
|
label control_keys byte
|
||
|
; Usages 700E0h ... 700E7h: LCtrl, LShift, LAlt, LWin, RCtrl, RShift, RAlt, RWin
|
||
|
db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX
|
||
|
; Usages 70004h ... 70004h + normal_keys_number - 1
|
||
|
label normal_keys byte
|
||
|
db 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h, 32h, 31h, 18h, 19h
|
||
|
db 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h, 04h, 05h, 06h, 07h
|
||
|
db 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah, 1Bh, 2Bh, 0, 27h
|
||
|
db 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h, 41h, 42h, 43h, 44h
|
||
|
db 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX,4Bh+EX,50h+EX,48h+EX,45h
|
||
|
db 35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h, 51h, 4Bh, 4Ch, 4Dh, 47h, 48h, 49h, 52h, 53h
|
||
|
db 0,5Dh+EX,5Eh+EX
|
||
|
normal_keys_number = $ - normal_keys
|
||
|
}
|
||
|
|
||
|
; Data that are specific for one keyboard device.
|
||
|
struct keyboard_device_data
|
||
|
handle dd ? ; keyboard handle from RegKeyboard
|
||
|
timer dd ? ; auto-repeat timer handle
|
||
|
repeatkey db ? ; auto-repeat key code
|
||
|
rb 3 ; padding
|
||
|
usbdev dd ? ; pointer to device_data of USB and HID layers
|
||
|
modifiers dd ? ; state of LCtrl ... RWin
|
||
|
led_report dd ? ; output report for LEDs state
|
||
|
numlock_bit dd ? ; position of NumLock bit in LED output report
|
||
|
capslock_bit dd ?
|
||
|
scrolllock_bit dd ? ; guess what
|
||
|
ends
|
||
|
|
||
|
; This procedure is called when HID layer detects a new keyboard.
|
||
|
; in: ebx -> usb_device_data, edi -> collection
|
||
|
; out: eax = device-specific data or NULL on error
|
||
|
proc keyboard_driver_add_device
|
||
|
; 1. Allocate memory for keyboard_device_data. If failed, return NULL.
|
||
|
movi eax, sizeof.keyboard_device_data
|
||
|
call Kmalloc
|
||
|
test eax, eax
|
||
|
jz .nothing
|
||
|
; 2. Initialize keyboard_device_data: store pointer to USB layer data,
|
||
|
; zero some fields, initialize bit positions to -1.
|
||
|
mov [eax+keyboard_device_data.usbdev], ebx
|
||
|
xor ecx, ecx
|
||
|
mov [eax+keyboard_device_data.timer], ecx
|
||
|
mov [eax+keyboard_device_data.repeatkey], cl
|
||
|
mov [eax+keyboard_device_data.modifiers], ecx
|
||
|
mov [eax+keyboard_device_data.led_report], ecx
|
||
|
dec ecx
|
||
|
mov [eax+keyboard_device_data.numlock_bit], ecx
|
||
|
mov [eax+keyboard_device_data.capslock_bit], ecx
|
||
|
mov [eax+keyboard_device_data.scrolllock_bit], ecx
|
||
|
; 3. Look for LED report and bits corresponding to indicators.
|
||
|
; For now, assume that all LEDs are set by the same report.
|
||
|
; 3a. Save registers.
|
||
|
push ebx esi
|
||
|
; 3b. Prepare for loop over output reports: get the first output report.
|
||
|
; If there are no output records, skip step 3;
|
||
|
; default values of led_report and *_bit were set in step 2.
|
||
|
mov edx, [edi+collection.output.first_report]
|
||
|
test edx, edx
|
||
|
jz .led_report_set
|
||
|
.scan_led_report:
|
||
|
; Process one output report.
|
||
|
; 3c. Prepare for loop over field groups in the current report:
|
||
|
; get the first field group.
|
||
|
mov ecx, [edx+report.first_field]
|
||
|
.scan_led_field:
|
||
|
; Process one field group.
|
||
|
; 3d. If there are no more field groups, exit the loop over field groups.
|
||
|
test ecx, ecx
|
||
|
jz .next_led_report
|
||
|
; For now, assume that all LEDs are plain variable fields, not arrays.
|
||
|
; 3e. Ignore array field groups.
|
||
|
test byte [ecx+report_field_group.flags], HID_FIELD_VARIABLE
|
||
|
jz .next_led_field
|
||
|
; 3f. Loop over all fields in the current group.
|
||
|
push [ecx+report_field_group.count]
|
||
|
; esi = pointer to usage of the current field
|
||
|
lea esi, [ecx+report_field_group.common_sizeof]
|
||
|
; ebx = bit position of the current field
|
||
|
mov ebx, [ecx+report_field_group.offset]
|
||
|
; if report is numbered, add extra byte in the start of report
|
||
|
cmp [edx+report.id], 0
|
||
|
jz .scan_led_usage
|
||
|
add ebx, 8
|
||
|
.scan_led_usage:
|
||
|
; for USAGE_LED_*LOCK, store the current bit position in the corresponding field
|
||
|
; and store the current report as the LED report
|
||
|
cmp dword [esi], USAGE_LED_NUMLOCK
|
||
|
jz .numlock
|
||
|
cmp dword [esi], USAGE_LED_CAPSLOCK
|
||
|
jz .capslock
|
||
|
cmp dword [esi], USAGE_LED_SCROLLLOCK
|
||
|
jnz .next_field
|
||
|
.scrolllock:
|
||
|
mov [eax+keyboard_device_data.scrolllock_bit], ebx
|
||
|
jmp @f
|
||
|
.capslock:
|
||
|
mov [eax+keyboard_device_data.capslock_bit], ebx
|
||
|
jmp @f
|
||
|
.numlock:
|
||
|
mov [eax+keyboard_device_data.numlock_bit], ebx
|
||
|
@@:
|
||
|
mov [eax+keyboard_device_data.led_report], edx
|
||
|
.next_field:
|
||
|
add esi, 4
|
||
|
add ebx, [ecx+report_field_group.size]
|
||
|
dec dword [esp]
|
||
|
jnz .scan_led_usage
|
||
|
pop ebx
|
||
|
.next_led_field:
|
||
|
; 3g. Continue loop over field groups: get next field group.
|
||
|
mov ecx, [ecx+report_field_group.next]
|
||
|
jmp .scan_led_field
|
||
|
.next_led_report:
|
||
|
; 3h. If the LED report has been set, break from the loop over reports.
|
||
|
; Otherwise, get the next report and continue if the current report is not
|
||
|
; the last for this collection.
|
||
|
cmp [eax+keyboard_device_data.led_report], 0
|
||
|
jnz .led_report_set
|
||
|
cmp edx, [edi+collection.output.last_report]
|
||
|
mov edx, [edx+report.next]
|
||
|
jnz .scan_led_report
|
||
|
.led_report_set:
|
||
|
; 3i. Restore registers.
|
||
|
pop esi ebx
|
||
|
; 4. Register keyboard in the kernel.
|
||
|
; store pointer to keyboard_device_data in the stack
|
||
|
push eax
|
||
|
; call kernel API
|
||
|
stdcall RegKeyboard, kbd_functions, eax
|
||
|
; restore pointer to keyboard_device_data from the stack,
|
||
|
; putting keyboard handle from API to the stack
|
||
|
xchg eax, [esp]
|
||
|
; put keyboard handle from API from the stack to keyboard_device_data field
|
||
|
pop [eax+keyboard_device_data.handle]
|
||
|
; If failed, free keyboard_device_data and return NULL.
|
||
|
cmp [eax+keyboard_device_data.handle], 0
|
||
|
jz .fail_free
|
||
|
; 5. Return pointer to keyboard_device_data.
|
||
|
.nothing:
|
||
|
ret
|
||
|
.fail_free:
|
||
|
call Kfree
|
||
|
xor eax, eax
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called when HID layer detects disconnect of a previously
|
||
|
; connected keyboard.
|
||
|
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device)
|
||
|
proc keyboard_driver_disconnect
|
||
|
; 1. If an autorepeat timer is active, stop it.
|
||
|
cmp [edi+keyboard_device_data.timer], 0
|
||
|
jz @f
|
||
|
stdcall CancelTimerHS, [edi+keyboard_device_data.timer]
|
||
|
@@:
|
||
|
; 2. Unregister keyboard in the kernel.
|
||
|
stdcall DelKeyboard, [edi+keyboard_device_data.handle]
|
||
|
; We should free data in CloseKeyboard, not here.
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called when HID layer starts processing a new input packet
|
||
|
; from a keyboard.
|
||
|
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device)
|
||
|
proc keyboard_driver_begin_packet
|
||
|
; Nothing to do.
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called when HID layer processes every non-empty array field group.
|
||
|
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device)
|
||
|
; in: ecx = fields count (always nonzero), edx = pointer to fields values
|
||
|
; in: esi -> report_field_group
|
||
|
; out: CF set => group is ok, CF cleared => group should be ignored
|
||
|
proc keyboard_driver_array_overflow?
|
||
|
; The keyboard signals array overflow by filling the entire array with
|
||
|
; USAGE_KBD_ROLLOVER codes.
|
||
|
mov eax, [edx] ; eax = first field in the array
|
||
|
sub eax, USAGE_KBD_ROLLOVER ; eax = 0 if overflow, nonzero otherwise
|
||
|
neg eax ; CF cleared if eax was zero, CF set if eax was nonzero
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called from HID layer for every field.
|
||
|
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device)
|
||
|
; in: ecx = field usage, edx = value, esi -> report_field_group
|
||
|
proc keyboard_driver_input_field
|
||
|
if HID_DUMP_UNCLAIMED
|
||
|
.unclaimed = default_driver_input_field
|
||
|
end if
|
||
|
; 1. Process normal keys:
|
||
|
; from USAGE_KBD_FIRST_KEY to USAGE_KBD_FIRST_KEY + normal_keys_number - 1,
|
||
|
; excluding zeroes in [normal_keys].
|
||
|
; 1a. Test whether usage is in the range.
|
||
|
lea eax, [ecx-USAGE_KBD_FIRST_KEY]
|
||
|
cmp eax, normal_keys_number
|
||
|
jae .not_normal_key
|
||
|
; 1b. If the corresponding entry in [normal_keys] is zero,
|
||
|
; pass this field to the default handler - if HID_DUMP_UNCLAIMED is enabled,
|
||
|
; default handler is default_driver_input_field, otherwise just ignore the field.
|
||
|
cmp [normal_keys + eax], 0
|
||
|
jz .unclaimed
|
||
|
; 1c. Get the scancode.
|
||
|
movzx ecx, [normal_keys + eax]
|
||
|
; 1d. Further actions are slightly different for key press and key release.
|
||
|
; Decide what to do.
|
||
|
test edx, edx
|
||
|
jz .normal_key_released
|
||
|
.normal_key_pressed:
|
||
|
; The key is pressed.
|
||
|
; 1e. Store the last pressed key for autorepeat.
|
||
|
mov [edi+keyboard_device_data.repeatkey], cl
|
||
|
; 1f. Copy bit 7 to CF and send scancode with bit 7 cleared.
|
||
|
btr ecx, 7
|
||
|
call .send_key
|
||
|
; 1g. Stop the previous autorepeat timer, if any.
|
||
|
mov eax, [edi+keyboard_device_data.timer]
|
||
|
test eax, eax
|
||
|
jz @f
|
||
|
stdcall CancelTimerHS, eax
|
||
|
@@:
|
||
|
; 1h. Start the new autorepeat timer with 250 ms initial delay
|
||
|
; and 50 ms subsequent delays.
|
||
|
stdcall TimerHS, 25, 5, autorepeat_timer, edi
|
||
|
mov [edi+keyboard_device_data.timer], eax
|
||
|
if ~HID_DUMP_UNCLAIMED
|
||
|
.unclaimed:
|
||
|
end if
|
||
|
ret
|
||
|
.normal_key_released:
|
||
|
; The key is released.
|
||
|
; 1i. Stop the autorepeat timer if it is autorepeating the released key.
|
||
|
cmp [edi+keyboard_device_data.repeatkey], cl
|
||
|
jnz .no_stop_timer
|
||
|
push ecx
|
||
|
mov [edi+keyboard_device_data.repeatkey], 0
|
||
|
mov eax, [edi+keyboard_device_data.timer]
|
||
|
test eax, eax
|
||
|
jz @f
|
||
|
stdcall CancelTimerHS, eax
|
||
|
mov [edi+keyboard_device_data.timer], 0
|
||
|
@@:
|
||
|
pop ecx
|
||
|
.no_stop_timer:
|
||
|
; 1j. Copy bit 7 to CF and send scancode with bit 7 set.
|
||
|
bts ecx, 7
|
||
|
call .send_key
|
||
|
ret
|
||
|
.not_normal_key:
|
||
|
; 2. USAGE_KBD_NOEVENT is simply a filler for free array fields,
|
||
|
; ignore it.
|
||
|
cmp ecx, USAGE_KBD_NOEVENT
|
||
|
jz .nothing
|
||
|
; 3. Process modifiers: 8 keys starting at USAGE_KBD_LCTRL.
|
||
|
; 3a. Test whether usage is in range.
|
||
|
; If not, we don't know what this field means, so pass it to the default handler.
|
||
|
lea eax, [ecx-USAGE_KBD_LCTRL]
|
||
|
cmp eax, 8
|
||
|
jae .unclaimed
|
||
|
; 3b. Further actions are slightly different for modifier press
|
||
|
; and modifier release. Decide what to do.
|
||
|
test edx, edx
|
||
|
jz .modifier_not_pressed
|
||
|
.modifier_pressed:
|
||
|
; The modifier is pressed.
|
||
|
; 3c. Set the corresponding status bit.
|
||
|
; If it was not set, send the corresponding scancode to the kernel
|
||
|
; with bit 7 cleared.
|
||
|
bts [edi+keyboard_device_data.modifiers], eax
|
||
|
jc @f
|
||
|
movzx ecx, [control_keys+eax]
|
||
|
btr ecx, 7
|
||
|
call .send_key
|
||
|
@@:
|
||
|
.nothing:
|
||
|
ret
|
||
|
.modifier_not_pressed:
|
||
|
; The modifier is not pressed.
|
||
|
; 3d. Clear the correspodning status bit.
|
||
|
; If it was set, send the corresponding scancode to the kernel
|
||
|
; with bit 7 set.
|
||
|
btr [edi+keyboard_device_data.modifiers], eax
|
||
|
jnc @f
|
||
|
movzx ecx, [control_keys+eax]
|
||
|
bts ecx, 7
|
||
|
call .send_key
|
||
|
@@:
|
||
|
ret
|
||
|
|
||
|
; Helper procedure. Sends scancode from cl to the kernel.
|
||
|
; If CF is set, precede it with special code 0xE0.
|
||
|
.send_key:
|
||
|
jnc @f
|
||
|
push ecx
|
||
|
mov ecx, 0xE0
|
||
|
call SetKeyboardData
|
||
|
pop ecx
|
||
|
@@:
|
||
|
call SetKeyboardData
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called when HID layer ends processing a new input packet
|
||
|
; from a keyboard.
|
||
|
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device)
|
||
|
proc keyboard_driver_end_packet
|
||
|
; Nothing to do.
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; Timer callback for SetTimerHS.
|
||
|
proc autorepeat_timer
|
||
|
virtual at esp
|
||
|
dd ? ; return address
|
||
|
.data dd ?
|
||
|
end virtual
|
||
|
; Just resend the last pressed key.
|
||
|
mov eax, [.data]
|
||
|
movzx ecx, [eax+keyboard_device_data.repeatkey]
|
||
|
; Copy bit 7 to CF and send scancode with bit 7 cleared.
|
||
|
btr ecx, 7
|
||
|
call keyboard_driver_input_field.send_key
|
||
|
ret 4
|
||
|
endp
|
||
|
|
||
|
; This function is called from the keyboard layer
|
||
|
; when it is safe to free keyboard data.
|
||
|
proc CloseKeyboard
|
||
|
virtual at esp
|
||
|
dd ? ; return address
|
||
|
.device_data dd ?
|
||
|
end virtual
|
||
|
mov eax, [.device_data]
|
||
|
call Kfree
|
||
|
ret 4
|
||
|
endp
|
||
|
|
||
|
; This function is called from the keyboard layer
|
||
|
; to update LED state on the keyboard.
|
||
|
proc SetKeyboardLights stdcall uses ebx esi edi, device_data, led_state
|
||
|
locals
|
||
|
size dd ?
|
||
|
endl
|
||
|
; 1. Get the pointer to the LED report.
|
||
|
; If there is no LED report, exit from the function.
|
||
|
mov ebx, [device_data]
|
||
|
mov esi, [ebx+keyboard_device_data.led_report]
|
||
|
test esi, esi
|
||
|
jz .nothing
|
||
|
; 2. Get report size in bytes.
|
||
|
; report.size is size in bits without possible report ID;
|
||
|
; if an ID is assigned, the size is one byte greater.
|
||
|
mov eax, [esi+report.size]
|
||
|
add eax, 7
|
||
|
shr eax, 3
|
||
|
cmp [esi+report.id], 0
|
||
|
jz @f
|
||
|
inc eax
|
||
|
@@:
|
||
|
mov [size], eax
|
||
|
; 3. Allocate memory for report + 8 bytes for setup packet.
|
||
|
; Dword-align size for subsequent rep stosd and bts.
|
||
|
; If failed, exit from the function.
|
||
|
add eax, 8 + 3
|
||
|
and eax, not 3
|
||
|
push eax
|
||
|
call Kmalloc
|
||
|
pop ecx
|
||
|
test eax, eax
|
||
|
jz .nothing
|
||
|
; 4. Zero-initialize output report.
|
||
|
push eax
|
||
|
mov edi, eax
|
||
|
shr ecx, 2
|
||
|
xor eax, eax
|
||
|
rep stosd
|
||
|
pop edi
|
||
|
add edi, 8
|
||
|
; 5. Store report ID, if assigned. If not assigned, that would just write zero
|
||
|
; over zeroes.
|
||
|
mov edx, [esi+report.id]
|
||
|
mov [edi], edx
|
||
|
; 6. Set report bits corresponding to active indicators.
|
||
|
mov eax, [led_state]
|
||
|
test al, 1 ; PS/2 Scroll Lock
|
||
|
jz @f
|
||
|
mov ecx, [ebx+keyboard_device_data.scrolllock_bit]
|
||
|
test ecx, ecx
|
||
|
js @f
|
||
|
bts [edi], ecx
|
||
|
@@:
|
||
|
test al, 2 ; PS/2 Num Lock
|
||
|
jz @f
|
||
|
mov ecx, [ebx+keyboard_device_data.numlock_bit]
|
||
|
test ecx, ecx
|
||
|
js @f
|
||
|
bts [edi], ecx
|
||
|
@@:
|
||
|
test al, 4 ; PS/2 Caps Lock
|
||
|
jz @f
|
||
|
mov ecx, [ebx+keyboard_device_data.capslock_bit]
|
||
|
test ecx, ecx
|
||
|
js @f
|
||
|
bts [edi], ecx
|
||
|
@@:
|
||
|
; 7. Fill setup packet.
|
||
|
shl edx, 16 ; move Report ID to byte 2
|
||
|
or edx, 21h + \ ; Class-specific request to Interface
|
||
|
(9 shl 8) + \ ; SET_REPORT
|
||
|
(2 shl 24) ; Report Type = Output
|
||
|
lea eax, [edi-8]
|
||
|
mov ebx, [ebx+keyboard_device_data.usbdev]
|
||
|
mov dword [eax], edx
|
||
|
mov edx, [size]
|
||
|
shl edx, 16 ; move Size to last word
|
||
|
or edx, [ebx+usb_device_data.interface_number]
|
||
|
mov [eax+4], edx
|
||
|
; 8. Submit output control request.
|
||
|
stdcall USBControlTransferAsync, [ebx+usb_device_data.configpipe], \
|
||
|
eax, edi, [size], after_set_keyboard_lights, ebx, 0
|
||
|
; If failed, free the buffer now.
|
||
|
; If succeeded, the callback will free the buffer.
|
||
|
test eax, eax
|
||
|
jnz .nothing
|
||
|
lea eax, [edi-8]
|
||
|
call Kfree
|
||
|
.nothing:
|
||
|
ret
|
||
|
endp
|
||
|
|
||
|
; This procedure is called from the USB subsystem when the request initiated by
|
||
|
; SetKeyboardLights is completed, either successfully or unsuccessfully.
|
||
|
proc after_set_keyboard_lights
|
||
|
virtual at esp
|
||
|
dd ? ; return address
|
||
|
.pipe dd ?
|
||
|
.status dd ?
|
||
|
.buffer dd ?
|
||
|
.length dd ?
|
||
|
.calldata dd ?
|
||
|
end virtual
|
||
|
; Ignore status, just free the buffer allocated by SetKeyboardLights.
|
||
|
mov eax, [.buffer]
|
||
|
sub eax, 8
|
||
|
call Kfree
|
||
|
ret 20
|
||
|
endp
|