Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
3709 | clevermous | 1 | ; HID keyboard driver, part of USBHID driver. |
2 | |||
3 | ; Global constants. |
||
4 | ; They are assembled in a macro to separate code and data; |
||
5 | ; the code is located at the point of "include 'keyboard.inc'", |
||
6 | ; the data are collected when workers_globals is instantiated. |
||
7 | macro workers_globals |
||
8 | { |
||
9 | ; include global constants from previous workers |
||
10 | workers_globals |
||
11 | align 4 |
||
12 | ; Callbacks for HID layer. |
||
13 | keyboard_driver: |
||
14 | dd keyboard_driver_add_device |
||
15 | dd keyboard_driver_disconnect |
||
16 | dd keyboard_driver_begin_packet |
||
17 | dd keyboard_driver_array_overflow? |
||
18 | dd keyboard_driver_input_field |
||
19 | dd keyboard_driver_end_packet |
||
20 | ; Callbacks for keyboard layer. |
||
21 | kbd_functions: |
||
22 | dd 12 |
||
23 | dd CloseKeyboard |
||
24 | dd SetKeyboardLights |
||
25 | ; Kernel keyboard layer takes input in form of PS/2 scancodes. |
||
26 | ; data for keyboard: correspondence between HID usage keys and PS/2 scancodes. |
||
27 | EX = 80h ; if set, precede the scancode with special scancode 0xE0 |
||
28 | label control_keys byte |
||
29 | ; Usages 700E0h ... 700E7h: LCtrl, LShift, LAlt, LWin, RCtrl, RShift, RAlt, RWin |
||
30 | db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX |
||
31 | ; Usages 70004h ... 70004h + normal_keys_number - 1 |
||
32 | label normal_keys byte |
||
33 | db 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h, 32h, 31h, 18h, 19h |
||
34 | db 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h, 04h, 05h, 06h, 07h |
||
35 | db 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah, 1Bh, 2Bh, 0, 27h |
||
36 | db 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h, 41h, 42h, 43h, 44h |
||
37 | 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 |
||
38 | db 35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h, 51h, 4Bh, 4Ch, 4Dh, 47h, 48h, 49h, 52h, 53h |
||
39 | db 0,5Dh+EX,5Eh+EX |
||
40 | normal_keys_number = $ - normal_keys |
||
41 | } |
||
42 | |||
43 | ; Data that are specific for one keyboard device. |
||
44 | struct keyboard_device_data |
||
45 | handle dd ? ; keyboard handle from RegKeyboard |
||
46 | timer dd ? ; auto-repeat timer handle |
||
47 | repeatkey db ? ; auto-repeat key code |
||
48 | rb 3 ; padding |
||
49 | usbdev dd ? ; pointer to device_data of USB and HID layers |
||
50 | modifiers dd ? ; state of LCtrl ... RWin |
||
51 | led_report dd ? ; output report for LEDs state |
||
52 | numlock_bit dd ? ; position of NumLock bit in LED output report |
||
53 | capslock_bit dd ? |
||
54 | scrolllock_bit dd ? ; guess what |
||
55 | ends |
||
56 | |||
57 | ; This procedure is called when HID layer detects a new keyboard. |
||
58 | ; in: ebx -> usb_device_data, edi -> collection |
||
59 | ; out: eax = device-specific data or NULL on error |
||
60 | proc keyboard_driver_add_device |
||
61 | ; 1. Allocate memory for keyboard_device_data. If failed, return NULL. |
||
62 | movi eax, sizeof.keyboard_device_data |
||
63 | call Kmalloc |
||
64 | test eax, eax |
||
65 | jz .nothing |
||
66 | ; 2. Initialize keyboard_device_data: store pointer to USB layer data, |
||
67 | ; zero some fields, initialize bit positions to -1. |
||
68 | mov [eax+keyboard_device_data.usbdev], ebx |
||
69 | xor ecx, ecx |
||
70 | mov [eax+keyboard_device_data.timer], ecx |
||
71 | mov [eax+keyboard_device_data.repeatkey], cl |
||
72 | mov [eax+keyboard_device_data.modifiers], ecx |
||
73 | mov [eax+keyboard_device_data.led_report], ecx |
||
74 | dec ecx |
||
75 | mov [eax+keyboard_device_data.numlock_bit], ecx |
||
76 | mov [eax+keyboard_device_data.capslock_bit], ecx |
||
77 | mov [eax+keyboard_device_data.scrolllock_bit], ecx |
||
78 | ; 3. Look for LED report and bits corresponding to indicators. |
||
79 | ; For now, assume that all LEDs are set by the same report. |
||
80 | ; 3a. Save registers. |
||
81 | push ebx esi |
||
82 | ; 3b. Prepare for loop over output reports: get the first output report. |
||
83 | ; If there are no output records, skip step 3; |
||
84 | ; default values of led_report and *_bit were set in step 2. |
||
85 | mov edx, [edi+collection.output.first_report] |
||
86 | test edx, edx |
||
87 | jz .led_report_set |
||
88 | .scan_led_report: |
||
89 | ; Process one output report. |
||
90 | ; 3c. Prepare for loop over field groups in the current report: |
||
91 | ; get the first field group. |
||
92 | mov ecx, [edx+report.first_field] |
||
93 | .scan_led_field: |
||
94 | ; Process one field group. |
||
95 | ; 3d. If there are no more field groups, exit the loop over field groups. |
||
96 | test ecx, ecx |
||
97 | jz .next_led_report |
||
98 | ; For now, assume that all LEDs are plain variable fields, not arrays. |
||
99 | ; 3e. Ignore array field groups. |
||
100 | test byte [ecx+report_field_group.flags], HID_FIELD_VARIABLE |
||
101 | jz .next_led_field |
||
102 | ; 3f. Loop over all fields in the current group. |
||
103 | push [ecx+report_field_group.count] |
||
104 | ; esi = pointer to usage of the current field |
||
105 | lea esi, [ecx+report_field_group.common_sizeof] |
||
106 | ; ebx = bit position of the current field |
||
107 | mov ebx, [ecx+report_field_group.offset] |
||
108 | ; if report is numbered, add extra byte in the start of report |
||
109 | cmp [edx+report.id], 0 |
||
110 | jz .scan_led_usage |
||
111 | add ebx, 8 |
||
112 | .scan_led_usage: |
||
113 | ; for USAGE_LED_*LOCK, store the current bit position in the corresponding field |
||
114 | ; and store the current report as the LED report |
||
115 | cmp dword [esi], USAGE_LED_NUMLOCK |
||
116 | jz .numlock |
||
117 | cmp dword [esi], USAGE_LED_CAPSLOCK |
||
118 | jz .capslock |
||
119 | cmp dword [esi], USAGE_LED_SCROLLLOCK |
||
120 | jnz .next_field |
||
121 | .scrolllock: |
||
122 | mov [eax+keyboard_device_data.scrolllock_bit], ebx |
||
123 | jmp @f |
||
124 | .capslock: |
||
125 | mov [eax+keyboard_device_data.capslock_bit], ebx |
||
126 | jmp @f |
||
127 | .numlock: |
||
128 | mov [eax+keyboard_device_data.numlock_bit], ebx |
||
129 | @@: |
||
130 | mov [eax+keyboard_device_data.led_report], edx |
||
131 | .next_field: |
||
132 | add esi, 4 |
||
133 | add ebx, [ecx+report_field_group.size] |
||
134 | dec dword [esp] |
||
135 | jnz .scan_led_usage |
||
136 | pop ebx |
||
137 | .next_led_field: |
||
138 | ; 3g. Continue loop over field groups: get next field group. |
||
139 | mov ecx, [ecx+report_field_group.next] |
||
140 | jmp .scan_led_field |
||
141 | .next_led_report: |
||
142 | ; 3h. If the LED report has been set, break from the loop over reports. |
||
143 | ; Otherwise, get the next report and continue if the current report is not |
||
144 | ; the last for this collection. |
||
145 | cmp [eax+keyboard_device_data.led_report], 0 |
||
146 | jnz .led_report_set |
||
147 | cmp edx, [edi+collection.output.last_report] |
||
148 | mov edx, [edx+report.next] |
||
149 | jnz .scan_led_report |
||
150 | .led_report_set: |
||
151 | ; 3i. Restore registers. |
||
152 | pop esi ebx |
||
153 | ; 4. Register keyboard in the kernel. |
||
154 | ; store pointer to keyboard_device_data in the stack |
||
155 | push eax |
||
156 | ; call kernel API |
||
157 | stdcall RegKeyboard, kbd_functions, eax |
||
158 | ; restore pointer to keyboard_device_data from the stack, |
||
159 | ; putting keyboard handle from API to the stack |
||
160 | xchg eax, [esp] |
||
161 | ; put keyboard handle from API from the stack to keyboard_device_data field |
||
162 | pop [eax+keyboard_device_data.handle] |
||
163 | ; If failed, free keyboard_device_data and return NULL. |
||
164 | cmp [eax+keyboard_device_data.handle], 0 |
||
165 | jz .fail_free |
||
166 | ; 5. Return pointer to keyboard_device_data. |
||
167 | .nothing: |
||
168 | ret |
||
169 | .fail_free: |
||
170 | call Kfree |
||
171 | xor eax, eax |
||
172 | ret |
||
173 | endp |
||
174 | |||
175 | ; This procedure is called when HID layer detects disconnect of a previously |
||
176 | ; connected keyboard. |
||
177 | ; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
||
178 | proc keyboard_driver_disconnect |
||
179 | ; 1. If an autorepeat timer is active, stop it. |
||
180 | cmp [edi+keyboard_device_data.timer], 0 |
||
181 | jz @f |
||
182 | stdcall CancelTimerHS, [edi+keyboard_device_data.timer] |
||
183 | @@: |
||
184 | ; 2. Unregister keyboard in the kernel. |
||
185 | stdcall DelKeyboard, [edi+keyboard_device_data.handle] |
||
186 | ; We should free data in CloseKeyboard, not here. |
||
187 | ret |
||
188 | endp |
||
189 | |||
190 | ; This procedure is called when HID layer starts processing a new input packet |
||
191 | ; from a keyboard. |
||
192 | ; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
||
193 | proc keyboard_driver_begin_packet |
||
194 | ; Nothing to do. |
||
195 | ret |
||
196 | endp |
||
197 | |||
198 | ; This procedure is called when HID layer processes every non-empty array field group. |
||
199 | ; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
||
200 | ; in: ecx = fields count (always nonzero), edx = pointer to fields values |
||
201 | ; in: esi -> report_field_group |
||
202 | ; out: CF set => group is ok, CF cleared => group should be ignored |
||
203 | proc keyboard_driver_array_overflow? |
||
204 | ; The keyboard signals array overflow by filling the entire array with |
||
205 | ; USAGE_KBD_ROLLOVER codes. |
||
206 | mov eax, [edx] ; eax = first field in the array |
||
207 | sub eax, USAGE_KBD_ROLLOVER ; eax = 0 if overflow, nonzero otherwise |
||
208 | neg eax ; CF cleared if eax was zero, CF set if eax was nonzero |
||
209 | ret |
||
210 | endp |
||
211 | |||
212 | ; This procedure is called from HID layer for every field. |
||
213 | ; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
||
214 | ; in: ecx = field usage, edx = value, esi -> report_field_group |
||
215 | proc keyboard_driver_input_field |
||
216 | if HID_DUMP_UNCLAIMED |
||
217 | .unclaimed = default_driver_input_field |
||
218 | end if |
||
219 | ; 1. Process normal keys: |
||
220 | ; from USAGE_KBD_FIRST_KEY to USAGE_KBD_FIRST_KEY + normal_keys_number - 1, |
||
221 | ; excluding zeroes in [normal_keys]. |
||
222 | ; 1a. Test whether usage is in the range. |
||
223 | lea eax, [ecx-USAGE_KBD_FIRST_KEY] |
||
224 | cmp eax, normal_keys_number |
||
225 | jae .not_normal_key |
||
226 | ; 1b. If the corresponding entry in [normal_keys] is zero, |
||
227 | ; pass this field to the default handler - if HID_DUMP_UNCLAIMED is enabled, |
||
228 | ; default handler is default_driver_input_field, otherwise just ignore the field. |
||
229 | cmp [normal_keys + eax], 0 |
||
230 | jz .unclaimed |
||
231 | ; 1c. Get the scancode. |
||
232 | movzx ecx, [normal_keys + eax] |
||
233 | ; 1d. Further actions are slightly different for key press and key release. |
||
234 | ; Decide what to do. |
||
235 | test edx, edx |
||
236 | jz .normal_key_released |
||
237 | .normal_key_pressed: |
||
238 | ; The key is pressed. |
||
239 | ; 1e. Store the last pressed key for autorepeat. |
||
240 | mov [edi+keyboard_device_data.repeatkey], cl |
||
241 | ; 1f. Copy bit 7 to CF and send scancode with bit 7 cleared. |
||
242 | btr ecx, 7 |
||
243 | call .send_key |
||
244 | ; 1g. Stop the previous autorepeat timer, if any. |
||
245 | mov eax, [edi+keyboard_device_data.timer] |
||
246 | test eax, eax |
||
247 | jz @f |
||
248 | stdcall CancelTimerHS, eax |
||
249 | @@: |
||
250 | ; 1h. Start the new autorepeat timer with 250 ms initial delay |
||
251 | ; and 50 ms subsequent delays. |
||
252 | stdcall TimerHS, 25, 5, autorepeat_timer, edi |
||
253 | mov [edi+keyboard_device_data.timer], eax |
||
254 | if ~HID_DUMP_UNCLAIMED |
||
255 | .unclaimed: |
||
256 | end if |
||
257 | ret |
||
258 | .normal_key_released: |
||
259 | ; The key is released. |
||
260 | ; 1i. Stop the autorepeat timer if it is autorepeating the released key. |
||
261 | cmp [edi+keyboard_device_data.repeatkey], cl |
||
262 | jnz .no_stop_timer |
||
263 | push ecx |
||
264 | mov [edi+keyboard_device_data.repeatkey], 0 |
||
265 | mov eax, [edi+keyboard_device_data.timer] |
||
266 | test eax, eax |
||
267 | jz @f |
||
268 | stdcall CancelTimerHS, eax |
||
269 | mov [edi+keyboard_device_data.timer], 0 |
||
270 | @@: |
||
271 | pop ecx |
||
272 | .no_stop_timer: |
||
273 | ; 1j. Copy bit 7 to CF and send scancode with bit 7 set. |
||
274 | bts ecx, 7 |
||
275 | call .send_key |
||
276 | ret |
||
277 | .not_normal_key: |
||
278 | ; 2. USAGE_KBD_NOEVENT is simply a filler for free array fields, |
||
279 | ; ignore it. |
||
280 | cmp ecx, USAGE_KBD_NOEVENT |
||
281 | jz .nothing |
||
282 | ; 3. Process modifiers: 8 keys starting at USAGE_KBD_LCTRL. |
||
283 | ; 3a. Test whether usage is in range. |
||
284 | ; If not, we don't know what this field means, so pass it to the default handler. |
||
285 | lea eax, [ecx-USAGE_KBD_LCTRL] |
||
286 | cmp eax, 8 |
||
287 | jae .unclaimed |
||
288 | ; 3b. Further actions are slightly different for modifier press |
||
289 | ; and modifier release. Decide what to do. |
||
290 | test edx, edx |
||
291 | jz .modifier_not_pressed |
||
292 | .modifier_pressed: |
||
293 | ; The modifier is pressed. |
||
294 | ; 3c. Set the corresponding status bit. |
||
295 | ; If it was not set, send the corresponding scancode to the kernel |
||
296 | ; with bit 7 cleared. |
||
297 | bts [edi+keyboard_device_data.modifiers], eax |
||
298 | jc @f |
||
299 | movzx ecx, [control_keys+eax] |
||
300 | btr ecx, 7 |
||
301 | call .send_key |
||
302 | @@: |
||
303 | .nothing: |
||
304 | ret |
||
305 | .modifier_not_pressed: |
||
306 | ; The modifier is not pressed. |
||
307 | ; 3d. Clear the correspodning status bit. |
||
308 | ; If it was set, send the corresponding scancode to the kernel |
||
309 | ; with bit 7 set. |
||
310 | btr [edi+keyboard_device_data.modifiers], eax |
||
311 | jnc @f |
||
312 | movzx ecx, [control_keys+eax] |
||
313 | bts ecx, 7 |
||
314 | call .send_key |
||
315 | @@: |
||
316 | ret |
||
317 | |||
318 | ; Helper procedure. Sends scancode from cl to the kernel. |
||
319 | ; If CF is set, precede it with special code 0xE0. |
||
320 | .send_key: |
||
321 | jnc @f |
||
322 | push ecx |
||
323 | mov ecx, 0xE0 |
||
324 | call SetKeyboardData |
||
325 | pop ecx |
||
326 | @@: |
||
327 | call SetKeyboardData |
||
328 | ret |
||
329 | endp |
||
330 | |||
331 | ; This procedure is called when HID layer ends processing a new input packet |
||
332 | ; from a keyboard. |
||
333 | ; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
||
334 | proc keyboard_driver_end_packet |
||
335 | ; Nothing to do. |
||
336 | ret |
||
337 | endp |
||
338 | |||
339 | ; Timer callback for SetTimerHS. |
||
340 | proc autorepeat_timer |
||
341 | virtual at esp |
||
342 | dd ? ; return address |
||
343 | .data dd ? |
||
344 | end virtual |
||
345 | ; Just resend the last pressed key. |
||
346 | mov eax, [.data] |
||
347 | movzx ecx, [eax+keyboard_device_data.repeatkey] |
||
348 | ; Copy bit 7 to CF and send scancode with bit 7 cleared. |
||
349 | btr ecx, 7 |
||
350 | call keyboard_driver_input_field.send_key |
||
351 | ret 4 |
||
352 | endp |
||
353 | |||
354 | ; This function is called from the keyboard layer |
||
355 | ; when it is safe to free keyboard data. |
||
356 | proc CloseKeyboard |
||
357 | virtual at esp |
||
358 | dd ? ; return address |
||
359 | .device_data dd ? |
||
360 | end virtual |
||
361 | mov eax, [.device_data] |
||
362 | call Kfree |
||
363 | ret 4 |
||
364 | endp |
||
365 | |||
366 | ; This function is called from the keyboard layer |
||
367 | ; to update LED state on the keyboard. |
||
368 | proc SetKeyboardLights stdcall uses ebx esi edi, device_data, led_state |
||
369 | locals |
||
370 | size dd ? |
||
371 | endl |
||
372 | ; 1. Get the pointer to the LED report. |
||
373 | ; If there is no LED report, exit from the function. |
||
374 | mov ebx, [device_data] |
||
375 | mov esi, [ebx+keyboard_device_data.led_report] |
||
376 | test esi, esi |
||
377 | jz .nothing |
||
378 | ; 2. Get report size in bytes. |
||
379 | ; report.size is size in bits without possible report ID; |
||
380 | ; if an ID is assigned, the size is one byte greater. |
||
381 | mov eax, [esi+report.size] |
||
382 | add eax, 7 |
||
383 | shr eax, 3 |
||
384 | cmp [esi+report.id], 0 |
||
385 | jz @f |
||
386 | inc eax |
||
387 | @@: |
||
388 | mov [size], eax |
||
389 | ; 3. Allocate memory for report + 8 bytes for setup packet. |
||
390 | ; Dword-align size for subsequent rep stosd and bts. |
||
391 | ; If failed, exit from the function. |
||
392 | add eax, 8 + 3 |
||
393 | and eax, not 3 |
||
394 | push eax |
||
395 | call Kmalloc |
||
396 | pop ecx |
||
397 | test eax, eax |
||
398 | jz .nothing |
||
399 | ; 4. Zero-initialize output report. |
||
400 | push eax |
||
401 | mov edi, eax |
||
402 | shr ecx, 2 |
||
403 | xor eax, eax |
||
404 | rep stosd |
||
405 | pop edi |
||
406 | add edi, 8 |
||
407 | ; 5. Store report ID, if assigned. If not assigned, that would just write zero |
||
408 | ; over zeroes. |
||
409 | mov edx, [esi+report.id] |
||
410 | mov [edi], edx |
||
411 | ; 6. Set report bits corresponding to active indicators. |
||
412 | mov eax, [led_state] |
||
413 | test al, 1 ; PS/2 Scroll Lock |
||
414 | jz @f |
||
415 | mov ecx, [ebx+keyboard_device_data.scrolllock_bit] |
||
416 | test ecx, ecx |
||
417 | js @f |
||
418 | bts [edi], ecx |
||
419 | @@: |
||
420 | test al, 2 ; PS/2 Num Lock |
||
421 | jz @f |
||
422 | mov ecx, [ebx+keyboard_device_data.numlock_bit] |
||
423 | test ecx, ecx |
||
424 | js @f |
||
425 | bts [edi], ecx |
||
426 | @@: |
||
427 | test al, 4 ; PS/2 Caps Lock |
||
428 | jz @f |
||
429 | mov ecx, [ebx+keyboard_device_data.capslock_bit] |
||
430 | test ecx, ecx |
||
431 | js @f |
||
432 | bts [edi], ecx |
||
433 | @@: |
||
434 | ; 7. Fill setup packet. |
||
435 | shl edx, 16 ; move Report ID to byte 2 |
||
436 | or edx, 21h + \ ; Class-specific request to Interface |
||
437 | (9 shl 8) + \ ; SET_REPORT |
||
438 | (2 shl 24) ; Report Type = Output |
||
439 | lea eax, [edi-8] |
||
440 | mov ebx, [ebx+keyboard_device_data.usbdev] |
||
441 | mov dword [eax], edx |
||
442 | mov edx, [size] |
||
443 | shl edx, 16 ; move Size to last word |
||
444 | or edx, [ebx+usb_device_data.interface_number] |
||
445 | mov [eax+4], edx |
||
446 | ; 8. Submit output control request. |
||
447 | stdcall USBControlTransferAsync, [ebx+usb_device_data.configpipe], \ |
||
448 | eax, edi, [size], after_set_keyboard_lights, ebx, 0 |
||
449 | ; If failed, free the buffer now. |
||
450 | ; If succeeded, the callback will free the buffer. |
||
451 | test eax, eax |
||
452 | jnz .nothing |
||
453 | lea eax, [edi-8] |
||
454 | call Kfree |
||
455 | .nothing: |
||
456 | ret |
||
457 | endp |
||
458 | |||
459 | ; This procedure is called from the USB subsystem when the request initiated by |
||
460 | ; SetKeyboardLights is completed, either successfully or unsuccessfully. |
||
461 | proc after_set_keyboard_lights |
||
462 | virtual at esp |
||
463 | dd ? ; return address |
||
464 | .pipe dd ? |
||
465 | .status dd ? |
||
466 | .buffer dd ? |
||
467 | .length dd ? |
||
468 | .calldata dd ? |
||
469 | end virtual |
||
470 | ; Ignore status, just free the buffer allocated by SetKeyboardLights. |
||
471 | mov eax, [.buffer] |
||
472 | sub eax, 8 |
||
473 | call Kfree |
||
474 | ret 20 |
||
475 | endp |