0,0 → 1,475 |
; 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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |