kolibrios-gitea/drivers/usb/usbhid/keyboard.inc

476 lines
17 KiB
PHP
Raw Normal View History

; 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, 2Bh, 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 56h,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
invoke 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
invoke 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:
invoke 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
invoke CancelTimerHS, [edi+keyboard_device_data.timer]
@@:
; 2. Unregister keyboard in the kernel.
invoke 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
invoke CancelTimerHS, eax
@@:
; 1h. Start the new autorepeat timer with 250 ms initial delay
; and 50 ms subsequent delays.
invoke 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
invoke 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
invoke SetKeyboardData
pop ecx
@@:
invoke 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]
invoke 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
invoke 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.
invoke 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]
invoke 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
invoke Kfree
ret 20
endp