forked from KolibriOS/kolibrios
full-fledged USB HID driver
git-svn-id: svn://kolibrios.org@3709 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
parent
b457ca5b59
commit
296994e3bb
@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
|
@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
|
@ -130,7 +130,7 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
|
@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
|
@ -1,693 +0,0 @@
|
||||
; standard driver stuff
|
||||
format MS COFF
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
; this is for DEBUGF macro from 'fdo.inc'
|
||||
__DEBUG__ = 1
|
||||
__DEBUG_LEVEL__ = 1
|
||||
|
||||
include 'proc32.inc'
|
||||
include 'imports.inc'
|
||||
include 'fdo.inc'
|
||||
|
||||
public START
|
||||
public version
|
||||
|
||||
; USB constants
|
||||
DEVICE_DESCR_TYPE = 1
|
||||
CONFIG_DESCR_TYPE = 2
|
||||
STRING_DESCR_TYPE = 3
|
||||
INTERFACE_DESCR_TYPE = 4
|
||||
ENDPOINT_DESCR_TYPE = 5
|
||||
DEVICE_QUALIFIER_DESCR_TYPE = 6
|
||||
|
||||
CONTROL_PIPE = 0
|
||||
ISOCHRONOUS_PIPE = 1
|
||||
BULK_PIPE = 2
|
||||
INTERRUPT_PIPE = 3
|
||||
|
||||
; USB structures
|
||||
virtual at 0
|
||||
config_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.wTotalLength dw ?
|
||||
.bNumInterfaces db ?
|
||||
.bConfigurationValue db ?
|
||||
.iConfiguration db ?
|
||||
.bmAttributes db ?
|
||||
.bMaxPower db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
virtual at 0
|
||||
interface_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.bInterfaceNumber db ?
|
||||
.bAlternateSetting db ?
|
||||
.bNumEndpoints db ?
|
||||
.bInterfaceClass db ?
|
||||
.bInterfaceSubClass db ?
|
||||
.bInterfaceProtocol db ?
|
||||
.iInterface db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
virtual at 0
|
||||
endpoint_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.bEndpointAddress db ?
|
||||
.bmAttributes db ?
|
||||
.wMaxPacketSize dw ?
|
||||
.bInterval db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for all devices
|
||||
virtual at 0
|
||||
device_data:
|
||||
.type dd ? ; 1 = keyboard, 2 = mouse
|
||||
.intpipe dd ? ; interrupt pipe handle
|
||||
.packetsize dd ?
|
||||
.packet rb 8 ; packet with data from device
|
||||
.control rb 8 ; control packet to device
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for mouse
|
||||
virtual at device_data.sizeof
|
||||
mouse_data:
|
||||
; no additional data
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for keyboard
|
||||
virtual at device_data.sizeof
|
||||
keyboard_data:
|
||||
.handle dd ? ; keyboard handle from RegKeyboard
|
||||
.configpipe dd ? ; config pipe handle
|
||||
.prevpacket rb 8 ; previous packet with data from device
|
||||
.timer dd ? ; auto-repeat timer handle
|
||||
.repeatkey db ? ; auto-repeat key code
|
||||
.ledstate db ? ; state of LEDs
|
||||
align 4
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
section '.flat' code readable align 16
|
||||
; The start procedure.
|
||||
START:
|
||||
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
|
||||
; If not, return 0.
|
||||
xor eax, eax ; initialize return value
|
||||
cmp dword [esp+4], 1 ; compare the argument
|
||||
jnz .nothing
|
||||
; 2. Register self as a USB driver.
|
||||
; The name is my_driver = 'usbhid'; IOCTL interface is not supported;
|
||||
; usb_functions is an offset of a structure with callback functions.
|
||||
stdcall RegUSBDriver, my_driver, eax, usb_functions
|
||||
; 3. Return the returned value of RegUSBDriver.
|
||||
.nothing:
|
||||
ret 4
|
||||
|
||||
; This procedure is called when new HID device is detected.
|
||||
; It initializes the device.
|
||||
AddDevice:
|
||||
; Arguments are addressed through esp. In this point of the function,
|
||||
; [esp+4] = a handle of the config pipe, [esp+8] points to config_descr
|
||||
; structure, [esp+12] points to interface_descr structure.
|
||||
; 1. Check device type. Currently only mice and keyboards with
|
||||
; boot protocol are supported.
|
||||
; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and
|
||||
; bInterfaceProtocol are subsequent in interface_descr, just one
|
||||
; memory reference is used for both.
|
||||
mov edx, [esp+12]
|
||||
push ebx ; save used register to be stdcall
|
||||
mov cx, word [edx+interface_descr.bInterfaceSubClass]
|
||||
; 1b. For boot protocol, subclass must be 1 and protocol must be either 1 for
|
||||
; a keyboard or 2 for a mouse. Check.
|
||||
cmp cx, 0x0101
|
||||
jz .keyboard
|
||||
cmp cx, 0x0201
|
||||
jz .mouse
|
||||
; 1c. If the device is neither a keyboard nor a mouse, print a message and
|
||||
; go to 6c.
|
||||
DEBUGF 1,'K : unknown HID device\n'
|
||||
jmp .nothing
|
||||
; 1d. If the device is a keyboard or a mouse, print a message and continue
|
||||
; configuring.
|
||||
.keyboard:
|
||||
DEBUGF 1,'K : USB keyboard detected\n'
|
||||
push keyboard_data.sizeof
|
||||
jmp .common
|
||||
.mouse:
|
||||
DEBUGF 1,'K : USB mouse detected\n'
|
||||
push mouse_data.sizeof
|
||||
.common:
|
||||
; 2. Allocate memory for device data.
|
||||
pop eax ; get size of device data
|
||||
; 2a. Call the kernel, saving and restoring register edx.
|
||||
push edx
|
||||
call Kmalloc
|
||||
pop edx
|
||||
; 2b. Check result. If failed, say a message and go to 6c.
|
||||
test eax, eax
|
||||
jnz @f
|
||||
DEBUGF 1,'K : no memory\n'
|
||||
jmp .nothing
|
||||
@@:
|
||||
xchg eax, ebx
|
||||
; HID devices use one IN interrupt endpoint for polling the device
|
||||
; and an optional OUT interrupt endpoint. We do not use the later,
|
||||
; but must locate the first. Look for the IN interrupt endpoint.
|
||||
; 3. Get the upper bound of all descriptors' data.
|
||||
mov eax, [esp+8+4] ; configuration descriptor
|
||||
movzx ecx, [eax+config_descr.wTotalLength]
|
||||
add eax, ecx
|
||||
; 4. Loop over all descriptors until
|
||||
; either end-of-data reached - this is fail
|
||||
; or interface descriptor found - this is fail, all further data
|
||||
; correspond to that interface
|
||||
; or endpoint descriptor found.
|
||||
; 4a. Loop start: eax points to the interface descriptor.
|
||||
.lookep:
|
||||
; 4b. Get next descriptor.
|
||||
movzx ecx, byte [edx] ; the first byte of all descriptors is length
|
||||
add edx, ecx
|
||||
; 4c. Check that at least two bytes are readable. The opposite is an error.
|
||||
inc edx
|
||||
cmp edx, eax
|
||||
jae .errorep
|
||||
dec edx
|
||||
; 4d. Check that this descriptor is not interface descriptor. The opposite is
|
||||
; an error.
|
||||
cmp byte [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
|
||||
jz .errorep
|
||||
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue
|
||||
; the loop.
|
||||
cmp byte [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
|
||||
jnz .lookep
|
||||
; 5. Check that the descriptor contains all required data and all data are
|
||||
; readable. If so, proceed to 7.
|
||||
cmp byte [edx+endpoint_descr.bLength], endpoint_descr.sizeof
|
||||
jb .errorep
|
||||
sub eax, endpoint_descr.sizeof
|
||||
cmp edx, eax
|
||||
jbe @f
|
||||
; 6. An error occured during processing endpoint descriptor.
|
||||
.errorep:
|
||||
; 6a. Print a message.
|
||||
DEBUGF 1,'K : error: invalid endpoint descriptor\n'
|
||||
; 6b. Free memory allocated for device data.
|
||||
.free:
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
.nothing:
|
||||
; 6c. Return an error.
|
||||
xor eax, eax
|
||||
pop ebx
|
||||
ret 12
|
||||
@@:
|
||||
; 7. Check that the endpoint is IN interrupt endpoint. If not, go to 6.
|
||||
test [edx+endpoint_descr.bEndpointAddress], 80h
|
||||
jz .errorep
|
||||
mov cl, [edx+endpoint_descr.bmAttributes]
|
||||
and cl, 3
|
||||
cmp cl, INTERRUPT_PIPE
|
||||
jnz .errorep
|
||||
; 8. Open pipe for the endpoint.
|
||||
; 8a. Load parameters from the descriptor.
|
||||
movzx ecx, [edx+endpoint_descr.bEndpointAddress]
|
||||
movzx eax, [edx+endpoint_descr.bInterval]
|
||||
movzx edx, [edx+endpoint_descr.wMaxPacketSize]
|
||||
; 8b. Call the kernel, saving and restoring edx.
|
||||
push edx
|
||||
stdcall USBOpenPipe, [esp+4+24], ecx, edx, INTERRUPT_PIPE, eax
|
||||
pop edx
|
||||
; 8c. Check result. If failed, go to 6b.
|
||||
test eax, eax
|
||||
jz .free
|
||||
; We use 12 bytes for device type, interrupt pipe and interrupt packet size,
|
||||
; 8 bytes for a packet and 8 bytes for previous packet, used by a keyboard.
|
||||
; 9. Initialize device data.
|
||||
mov [ebx+device_data.intpipe], eax
|
||||
movi ecx, 8
|
||||
cmp edx, ecx
|
||||
jb @f
|
||||
mov edx, ecx
|
||||
@@:
|
||||
xor eax, eax
|
||||
mov [ebx+device_data.packetsize], edx
|
||||
mov dword [ebx+device_data.packet], eax
|
||||
mov dword [ebx+device_data.packet+4], eax
|
||||
mov edx, [esp+12+4] ; interface descriptor
|
||||
movzx ecx, [edx+interface_descr.bInterfaceProtocol]
|
||||
mov [ebx+device_data.type], ecx
|
||||
cmp ecx, 1
|
||||
jnz @f
|
||||
mov [ebx+keyboard_data.handle], eax
|
||||
mov [ebx+keyboard_data.timer], eax
|
||||
mov [ebx+keyboard_data.repeatkey], al
|
||||
mov dword [ebx+keyboard_data.prevpacket], eax
|
||||
mov dword [ebx+keyboard_data.prevpacket+4], eax
|
||||
mov eax, [esp+4+4]
|
||||
mov [ebx+keyboard_data.configpipe], eax
|
||||
@@:
|
||||
; 10. Send the control packet SET_PROTOCOL(Boot Protocol) to the interface.
|
||||
lea eax, [ebx+device_data.control]
|
||||
mov dword [eax], 21h + (0Bh shl 8) + (0 shl 16) ; class request to interface + SET_PROTOCOL + Boot protocol
|
||||
and dword [eax+4], 0
|
||||
mov dl, [edx+interface_descr.bInterfaceNumber]
|
||||
mov [eax+4], dl
|
||||
; Callback function is mouse_configured for mice and keyboard_configured1 for keyboards.
|
||||
mov edx, keyboard_configured1
|
||||
cmp ecx, 1
|
||||
jz @f
|
||||
mov edx, mouse_configured
|
||||
@@:
|
||||
stdcall USBControlTransferAsync, [esp+4+28], eax, 0, 0, edx, ebx, 0
|
||||
; 11. Return with pointer to device data as returned value.
|
||||
xchg eax, ebx
|
||||
pop ebx
|
||||
ret 12
|
||||
|
||||
; This function is called when SET_PROTOCOL command for keyboard is done,
|
||||
; either successful or unsuccessful.
|
||||
keyboard_configured1:
|
||||
xor edx, edx
|
||||
; 1. Check the status of the transfer.
|
||||
; If the transfer was failed, go to the common error handler.
|
||||
cmp dword [esp+8], edx ; status is zero?
|
||||
jnz keyboard_data_ready.error
|
||||
; 2. Send the control packet SET_IDLE(infinity). HID auto-repeat is not useful.
|
||||
mov eax, [esp+20]
|
||||
push edx ; flags for USBControlTransferAsync
|
||||
push eax ; userdata for USBControlTransferAsync
|
||||
add eax, device_data.control
|
||||
mov dword [eax], 21h + (0Ah shl 8) + (0 shl 24) ; class request to interface + SET_IDLE + no autorepeat
|
||||
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
|
||||
eax, edx, edx, keyboard_configured2; , <userdata>, <flags>
|
||||
; 3. Return.
|
||||
ret 20
|
||||
|
||||
; This function is called when SET_IDLE command for keyboard is done,
|
||||
; either successful or unsuccessful.
|
||||
keyboard_configured2:
|
||||
; Check the status of the transfer and go to the corresponding label
|
||||
; in the main handler.
|
||||
cmp dword [esp+8], 0
|
||||
jnz keyboard_data_ready.error
|
||||
mov edx, [esp+20]
|
||||
push edx
|
||||
stdcall RegKeyboard, usbkbd_functions, edx
|
||||
pop edx
|
||||
mov [edx+keyboard_data.handle], eax
|
||||
jmp keyboard_data_ready.next
|
||||
|
||||
; This function is called when another interrupt packet arrives,
|
||||
; processed either successfully or unsuccessfully.
|
||||
; It should parse the packet and initiate another transfer with
|
||||
; the same callback function.
|
||||
keyboard_data_ready:
|
||||
; 1. Check the status of the transfer.
|
||||
mov eax, [esp+8]
|
||||
test eax, eax
|
||||
jnz .error
|
||||
; Parse the packet, comparing with the previous packet.
|
||||
; For boot protocol, USB keyboard packet consists of the first byte
|
||||
; with status keys that are currently pressed. The second byte should
|
||||
; be ignored, and other 5 bytes denote keys that are currently pressed.
|
||||
push esi ebx ; save used registers to be stdcall
|
||||
; 2. Process control keys.
|
||||
; 2a. Initialize before loop for control keys. edx = mask for control bits
|
||||
; that were changed.
|
||||
mov ebx, [esp+20+8]
|
||||
movzx edx, byte [ebx+device_data.packet] ; get state of control keys
|
||||
xor dl, byte [ebx+keyboard_data.prevpacket] ; compare with previous state
|
||||
; 2b. If state of control keys has not changed, advance to 3.
|
||||
jz .nocontrol
|
||||
; 2c. Otherwise, loop over control keys; esi = bit number.
|
||||
xor esi, esi
|
||||
.controlloop:
|
||||
; 2d. Skip bits that have not changed.
|
||||
bt edx, esi
|
||||
jnc .controlnext
|
||||
push edx ; save register which is possibly modified by API
|
||||
; The state of the current control key has changed.
|
||||
; 2e. For extended control keys, send the prefix 0xE0.
|
||||
mov al, [control_keys+esi]
|
||||
test al, al
|
||||
jns @f
|
||||
push eax
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop eax
|
||||
and al, 0x7F
|
||||
@@:
|
||||
; 2f. If the current state of the control key is "pressed", send normal
|
||||
; scancode. Otherwise, the key is released, so set the high bit in scancode.
|
||||
movzx ecx, al
|
||||
bt dword [ebx+device_data.packet], esi
|
||||
jc @f
|
||||
or cl, 0x80
|
||||
@@:
|
||||
call SetKeyboardData
|
||||
pop edx ; restore register which was possibly modified by API
|
||||
.controlnext:
|
||||
; 2g. We have 8 control keys.
|
||||
inc esi
|
||||
cmp esi, 8
|
||||
jb .controlloop
|
||||
.nocontrol:
|
||||
; 3. Initialize before loop for normal keys. esi = index.
|
||||
movi esi, 2
|
||||
.normalloop:
|
||||
; 4. Process one key which was pressed in the previous packet.
|
||||
; 4a. Get the next pressed key from the previous packet.
|
||||
movzx eax, byte [ebx+esi+keyboard_data.prevpacket]
|
||||
; 4b. Ignore special codes.
|
||||
cmp al, 3
|
||||
jbe .normalnext1
|
||||
; 4c. Ignore keys that are still pressed in the current packet.
|
||||
lea ecx, [ebx+device_data.packet]
|
||||
call haskey
|
||||
jz .normalnext1
|
||||
; 4d. Say warning about keys with strange codes.
|
||||
cmp eax, normal_keys_number
|
||||
jae .badkey1
|
||||
movzx ecx, [normal_keys+eax]
|
||||
jecxz .badkey1
|
||||
; 4e. For extended keys, send the prefix 0xE0.
|
||||
push ecx ; save keycode
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
@@:
|
||||
; 4f. Send the release event.
|
||||
or cl, 0x80
|
||||
call SetKeyboardData
|
||||
; 4g. If this key is autorepeating, stop the timer.
|
||||
pop ecx ; restore keycode
|
||||
cmp cl, [ebx+keyboard_data.repeatkey]
|
||||
jnz .normalnext1
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz .normalnext1
|
||||
stdcall CancelTimerHS, eax
|
||||
and [ebx+keyboard_data.timer], 0
|
||||
jmp .normalnext1
|
||||
.badkey1:
|
||||
DEBUGF 1,'K : unknown keycode: %x\n',al
|
||||
.normalnext1:
|
||||
; 5. Process one key which is pressed in the current packet.
|
||||
; 5a. Get the next pressed key from the current packet.
|
||||
movzx eax, byte [ebx+esi+device_data.packet]
|
||||
; 5b. Ignore special codes.
|
||||
cmp al, 3
|
||||
jbe .normalnext2
|
||||
; 5c. Ignore keys that were already pressed in the previous packet.
|
||||
lea ecx, [ebx+keyboard_data.prevpacket]
|
||||
call haskey
|
||||
jz .normalnext2
|
||||
; 5d. Say warning about keys with strange codes.
|
||||
cmp eax, normal_keys_number
|
||||
jae .badkey2
|
||||
movzx ecx, [normal_keys+eax]
|
||||
jecxz .badkey2
|
||||
; 5e. For extended keys, send the prefix 0xE0.
|
||||
push ecx ; save keycode
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
@@:
|
||||
; 5f. Send the press event.
|
||||
and cl, not 0x80
|
||||
call SetKeyboardData
|
||||
; 5g. Stop the current auto-repeat timer, if present.
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz @f
|
||||
stdcall CancelTimerHS, eax
|
||||
@@:
|
||||
; 5h. Start the auto-repeat timer.
|
||||
pop ecx ; restore keycode
|
||||
mov [ebx+keyboard_data.repeatkey], cl
|
||||
stdcall TimerHS, 25, 5, autorepeat_timer, ebx
|
||||
mov [ebx+keyboard_data.timer], eax
|
||||
jmp .normalnext2
|
||||
.badkey2:
|
||||
DEBUGF 1,'K : unknown keycode: %x\n',al
|
||||
.normalnext2:
|
||||
; 6. Advance to next key.
|
||||
inc esi
|
||||
cmp esi, 8
|
||||
jb .normalloop
|
||||
; 7. Save the packet data for future reference.
|
||||
mov eax, dword [ebx+device_data.packet]
|
||||
mov dword [ebx+keyboard_data.prevpacket], eax
|
||||
mov eax, dword [ebx+device_data.packet+4]
|
||||
mov dword [ebx+keyboard_data.prevpacket+4], eax
|
||||
pop ebx esi ; restore registers to be stdcall
|
||||
.next:
|
||||
; 8. Initiate transfer on the interrupt pipe.
|
||||
mov eax, [esp+20]
|
||||
push 1 ; flags for USBNormalTransferAsync
|
||||
push eax ; userdata for USBNormalTransferAsync
|
||||
add eax, device_data.packet
|
||||
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
|
||||
eax, dword [eax+device_data.packetsize-device_data.packet], \
|
||||
keyboard_data_ready;, <userdata>, <flags>
|
||||
; 9. Return.
|
||||
.nothing:
|
||||
ret 20
|
||||
.error:
|
||||
; An error has occured.
|
||||
; 10. If an error is caused by the disconnect, do nothing, it is handled
|
||||
; in DeviceDisconnected. Otherwise, say a message.
|
||||
cmp eax, 16
|
||||
jz @f
|
||||
push esi
|
||||
mov esi, errormsgkbd
|
||||
call SysMsgBoardStr
|
||||
pop esi
|
||||
@@:
|
||||
ret 20
|
||||
|
||||
; Auxiliary procedure for keyboard_data_ready.
|
||||
haskey:
|
||||
movi edx, 2
|
||||
@@:
|
||||
cmp byte [ecx+edx], al
|
||||
jz @f
|
||||
inc edx
|
||||
cmp edx, 7
|
||||
jbe @b
|
||||
@@:
|
||||
ret
|
||||
|
||||
; Timer function for auto-repeat.
|
||||
autorepeat_timer:
|
||||
mov eax, [esp+4]
|
||||
movzx ecx, [eax+keyboard_data.repeatkey]
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
and cl, not 0x80
|
||||
@@:
|
||||
call SetKeyboardData
|
||||
ret 4
|
||||
|
||||
; This function is called to update LED state on the keyboard.
|
||||
SetKeyboardLights:
|
||||
mov eax, [esp+4]
|
||||
add eax, device_data.control
|
||||
mov dword [eax], 21h + (9 shl 8) + (2 shl 24)
|
||||
; class request to interface + SET_REPORT + Output zero report
|
||||
mov byte [eax+6], 1
|
||||
mov edx, [esp+8]
|
||||
shr dl, 1
|
||||
jnc @f
|
||||
or dl, 4
|
||||
@@:
|
||||
lea ecx, [eax+keyboard_data.ledstate-device_data.control]
|
||||
mov [ecx], dl
|
||||
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
|
||||
eax, ecx, 1, keyboard_data_ready.nothing, 0, 0
|
||||
ret 8
|
||||
|
||||
; This function is called when it is safe to free keyboard data.
|
||||
CloseKeyboard:
|
||||
mov eax, [esp+4]
|
||||
push ebx
|
||||
call Kfree
|
||||
pop ebx
|
||||
ret 4
|
||||
|
||||
; This function is called when SET_PROTOCOL command for mouse is done,
|
||||
; either successful or unsuccessful.
|
||||
mouse_configured:
|
||||
; Check the status of the transfer and go to the corresponding label
|
||||
; in the main handler.
|
||||
cmp dword [esp+8], 0
|
||||
jnz mouse_data_ready.error
|
||||
mov eax, [esp+20]
|
||||
add eax, device_data.packet
|
||||
jmp mouse_data_ready.next
|
||||
|
||||
; This function is called when another interrupt packet arrives,
|
||||
; processed either successfully or unsuccessfully.
|
||||
; It should parse the packet and initiate another transfer with
|
||||
; the same callback function.
|
||||
mouse_data_ready:
|
||||
; 1. Check the status of the transfer.
|
||||
mov eax, [esp+8]
|
||||
test eax, eax
|
||||
jnz .error
|
||||
mov edx, [esp+16]
|
||||
; 2. Parse the packet.
|
||||
; For boot protocol, USB mouse packet consists of at least 3 bytes.
|
||||
; The first byte is state of mouse buttons, the next two bytes are
|
||||
; x and y movements.
|
||||
; Normal mice do not distinguish between boot protocol and report protocol;
|
||||
; in this case, scroll data are also present. Advanced mice, however,
|
||||
; support two different protocols, boot protocol is used for compatibility
|
||||
; and does not contain extended buttons or scroll data.
|
||||
mov eax, [esp+12] ; buffer
|
||||
push eax
|
||||
xor ecx, ecx
|
||||
cmp edx, 4
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+4]
|
||||
@@:
|
||||
push ecx
|
||||
xor ecx, ecx
|
||||
cmp edx, 3
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+3]
|
||||
neg ecx
|
||||
@@:
|
||||
push ecx
|
||||
xor ecx, ecx
|
||||
cmp edx, 2
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+2]
|
||||
neg ecx
|
||||
@@:
|
||||
push ecx
|
||||
movsx ecx, byte [eax+1]
|
||||
push ecx
|
||||
movzx ecx, byte [eax]
|
||||
push ecx
|
||||
call SetMouseData
|
||||
pop eax
|
||||
.next:
|
||||
; 3. Initiate transfer on the interrupt pipe.
|
||||
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
|
||||
eax, dword [eax+device_data.packetsize-device_data.packet], mouse_data_ready, eax, 1
|
||||
; 4. Return.
|
||||
ret 20
|
||||
.error:
|
||||
; An error has occured.
|
||||
; 5. If an error is caused by the disconnect, do nothing, it is handled
|
||||
; in DeviceDisconnected. Otherwise, say a message.
|
||||
cmp eax, 16
|
||||
jz @f
|
||||
push esi
|
||||
mov esi, errormsgmouse
|
||||
call SysMsgBoardStr
|
||||
pop esi
|
||||
@@:
|
||||
ret 20
|
||||
|
||||
; This function is called when the device is disconnected.
|
||||
DeviceDisconnected:
|
||||
push ebx ; save used register to be stdcall
|
||||
; 1. Say a message. Use different messages for keyboards and mice.
|
||||
mov ebx, [esp+4+4]
|
||||
push esi
|
||||
mov esi, disconnectmsgk
|
||||
cmp byte [ebx+device_data.type], 1
|
||||
jz @f
|
||||
mov esi, disconnectmsgm
|
||||
@@:
|
||||
stdcall SysMsgBoardStr
|
||||
pop esi
|
||||
; 2. If device is keyboard, then we must unregister it as a keyboard and
|
||||
; possibly stop the auto-repeat timer.
|
||||
cmp byte [ebx+device_data.type], 1
|
||||
jnz .nokbd
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz @f
|
||||
stdcall CancelTimerHS, eax
|
||||
@@:
|
||||
mov ecx, [ebx+keyboard_data.handle]
|
||||
jecxz .nokbd
|
||||
stdcall DelKeyboard, ecx
|
||||
; If keyboard is registered, then we should free data in CloseKeyboard, not here.
|
||||
jmp .nothing
|
||||
.nokbd:
|
||||
; 3. Free the device data.
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
; 4. Return.
|
||||
.nothing:
|
||||
pop ebx ; restore used register to be stdcall
|
||||
ret 4 ; purge one dword argument to be stdcall
|
||||
|
||||
; strings
|
||||
my_driver db 'usbhid',0
|
||||
errormsgmouse db 'K : USB transfer error, disabling mouse',10,0
|
||||
errormsgkbd db 'K : USB transfer error, disabling keyboard',10,0
|
||||
disconnectmsgm db 'K : USB mouse disconnected',10,0
|
||||
disconnectmsgk db 'K : USB keyboard disconnected',10,0
|
||||
|
||||
; data for keyboard: correspondence between HID usage keys and PS/2 scancodes.
|
||||
EX = 80h
|
||||
label control_keys byte
|
||||
db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX
|
||||
label normal_keys byte
|
||||
db 00h, 00h, 00h, 00h, 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h ; 0x
|
||||
db 32h, 31h, 18h, 19h, 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h ; 1x
|
||||
db 04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah ; 2x
|
||||
db 1Bh, 2Bh, 2Bh, 27h, 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h ; 3x
|
||||
db 41h, 42h, 43h, 44h, 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX ; 4x
|
||||
db 4Bh+EX,50h+EX,48h+EX,45h,35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h,51h,4Bh,4Ch,4Dh,47h ; 5x
|
||||
db 48h, 49h, 52h, 53h, 56h,5Dh+EX,5Eh+EX,59h,64h,65h,66h, 67h, 68h, 69h, 6Ah, 6Bh ; 6x
|
||||
db 6Ch, 6Dh, 6Eh, 76h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 7x
|
||||
db 00h, 00h, 00h, 00h, 00h, 7Eh, 00h, 73h, 70h, 7Dh, 79h, 7Bh, 5Ch, 00h, 00h, 00h ; 8x
|
||||
db 0F2h,0F1h,78h, 77h, 76h
|
||||
normal_keys_number = $ - normal_keys
|
||||
|
||||
; Exported variable: kernel API version.
|
||||
align 4
|
||||
version dd 50005h
|
||||
; Structure with callback functions.
|
||||
usb_functions:
|
||||
dd 12
|
||||
dd AddDevice
|
||||
dd DeviceDisconnected
|
||||
|
||||
; Structure with callback functions for keyboards.
|
||||
usbkbd_functions:
|
||||
dd 12
|
||||
dd CloseKeyboard
|
||||
dd SetKeyboardLights
|
||||
|
||||
; for DEBUGF macro
|
||||
include_debug_strings
|
||||
|
||||
; for uninitialized data
|
||||
section '.data' data readable writable align 16
|
475
kernel/trunk/drivers/usbhid/keyboard.inc
Normal file
475
kernel/trunk/drivers/usbhid/keyboard.inc
Normal file
@ -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
|
146
kernel/trunk/drivers/usbhid/mouse.inc
Normal file
146
kernel/trunk/drivers/usbhid/mouse.inc
Normal file
@ -0,0 +1,146 @@
|
||||
; HID mouse 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 'mouse.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.
|
||||
mouse_driver:
|
||||
dd mouse_driver_add_device
|
||||
dd mouse_driver_disconnect
|
||||
dd mouse_driver_begin_packet
|
||||
dd mouse_driver_array_overflow?
|
||||
dd mouse_driver_input_field
|
||||
dd mouse_driver_end_packet
|
||||
}
|
||||
|
||||
; Data that are specific for one mouse device.
|
||||
struct mouse_device_data
|
||||
buttons dd ? ; buttons that are currently pressed
|
||||
dx dd ? ; current x moving
|
||||
dy dd ? ; current y moving
|
||||
wheel dd ? ; current wheel moving
|
||||
ends
|
||||
|
||||
; This procedure is called when HID layer detects a new mouse.
|
||||
; in: ebx -> device_data from USB layer, edi -> collection
|
||||
; out: eax = device-specific data or NULL on error
|
||||
proc mouse_driver_add_device
|
||||
; Just allocate memory; no initialization needed.
|
||||
movi eax, sizeof.mouse_device_data
|
||||
call Kmalloc
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called when HID layer detects disconnect of a previously
|
||||
; connected mouse.
|
||||
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device)
|
||||
proc mouse_driver_disconnect
|
||||
; Free the allocated memory.
|
||||
mov eax, edi
|
||||
call Kfree
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called when HID layer starts processing a new input packet
|
||||
; from a mouse.
|
||||
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device)
|
||||
proc mouse_driver_begin_packet
|
||||
; Zero all variables describing the current state.
|
||||
mov [edi+mouse_device_data.buttons], 0
|
||||
mov [edi+mouse_device_data.dx], 0
|
||||
mov [edi+mouse_device_data.dy], 0
|
||||
mov [edi+mouse_device_data.wheel], 0
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called when HID layer processes every non-empty array field group.
|
||||
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device)
|
||||
; in: ecx = fields count (always nonzero), edx = pointer to fields values
|
||||
; in: esi -> report_field_group
|
||||
; out: CF set => array is ok, CF cleared => array should be ignored
|
||||
proc mouse_driver_array_overflow?
|
||||
; no array fields, no overflows
|
||||
stc
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called from HID layer for every field.
|
||||
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device)
|
||||
; in: ecx = field usage, edx = value, esi -> report_field_group
|
||||
proc mouse_driver_input_field
|
||||
; 1. Determine the handler. We process x/y moving, wheel and up to 32 buttons.
|
||||
; Pass other fields to the default handler - default_driver_input_field if
|
||||
; HID_DUMP_UNCLAIMED is enabled, just ignore otherwise.
|
||||
cmp ecx, USAGE_GD_X
|
||||
jz .x
|
||||
cmp ecx, USAGE_GD_Y
|
||||
jz .y
|
||||
cmp ecx, USAGE_GD_WHEEL
|
||||
jz .wheel
|
||||
sub ecx, USAGE_BUTTON_PAGE + 1
|
||||
jb .unclaimed
|
||||
cmp ecx, 32
|
||||
jae .unclaimed
|
||||
; 2. This is a button.
|
||||
; If a button is pressed, set the corresponding bit in the state.
|
||||
; If a button is not pressed, do nothing.
|
||||
test edx, edx
|
||||
jz @f
|
||||
bts [edi+mouse_device_data.buttons], ecx
|
||||
@@:
|
||||
if ~HID_DUMP_UNCLAIMED
|
||||
.unclaimed:
|
||||
end if
|
||||
ret
|
||||
if HID_DUMP_UNCLAIMED
|
||||
.unclaimed:
|
||||
add ecx, USAGE_BUTTON_PAGE + 1
|
||||
jmp default_driver_input_field
|
||||
end if
|
||||
.x:
|
||||
; 3. This is x moving. For relative fields, store the value in the state.
|
||||
; Pass absolute field to the default handler.
|
||||
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE
|
||||
jz .unclaimed
|
||||
mov [edi+mouse_device_data.dx], edx
|
||||
ret
|
||||
.y:
|
||||
; 4. This is y moving. For relative fields, store the value in the state,
|
||||
; changing the sign: HID uses "mathematics" scheme with Y axis increasing from
|
||||
; bottom to top, the kernel expects "programming" PS/2-style with Y axis
|
||||
; increasing from top to bottom.
|
||||
; Pass absolute fields to the default handler.
|
||||
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE
|
||||
jz .unclaimed
|
||||
neg edx
|
||||
mov [edi+mouse_device_data.dy], edx
|
||||
ret
|
||||
.wheel:
|
||||
; 5. This is wheel event. For relative fields, store the value in the state,
|
||||
; changing the sign. Pass absolute fields to the default handler.
|
||||
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE
|
||||
jz .unclaimed
|
||||
neg edx
|
||||
mov [edi+mouse_device_data.wheel], edx
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called when HID layer ends processing a new input packet
|
||||
; from a mouse.
|
||||
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device)
|
||||
proc mouse_driver_end_packet
|
||||
; Call the kernel, passing collected state.
|
||||
stdcall SetMouseData, \
|
||||
[edi+mouse_device_data.buttons], \
|
||||
[edi+mouse_device_data.dx], \
|
||||
[edi+mouse_device_data.dy], \
|
||||
[edi+mouse_device_data.wheel], \
|
||||
0
|
||||
ret
|
||||
endp
|
1442
kernel/trunk/drivers/usbhid/report.inc
Normal file
1442
kernel/trunk/drivers/usbhid/report.inc
Normal file
File diff suppressed because it is too large
Load Diff
60
kernel/trunk/drivers/usbhid/sort.inc
Normal file
60
kernel/trunk/drivers/usbhid/sort.inc
Normal file
@ -0,0 +1,60 @@
|
||||
; Sort array of unsigned dwords in non-decreasing order.
|
||||
; ecx = array size, edx = array pointer.
|
||||
; Destroys eax, ecx, esi, edi.
|
||||
sort:
|
||||
test ecx, ecx
|
||||
jz .done
|
||||
mov eax, ecx
|
||||
@@:
|
||||
push eax
|
||||
call .restore
|
||||
pop eax
|
||||
dec eax
|
||||
jnz @b
|
||||
@@:
|
||||
cmp ecx, 1
|
||||
jz .done
|
||||
mov esi, 1
|
||||
mov edi, ecx
|
||||
call .exchange
|
||||
dec ecx
|
||||
mov eax, 1
|
||||
call .restore
|
||||
jmp @b
|
||||
.done:
|
||||
ret
|
||||
|
||||
.exchange:
|
||||
push eax ecx
|
||||
mov eax, [edx+esi*4-4]
|
||||
mov ecx, [edx+edi*4-4]
|
||||
mov [edx+esi*4-4], ecx
|
||||
mov [edx+edi*4-4], eax
|
||||
pop ecx eax
|
||||
ret
|
||||
|
||||
.restore:
|
||||
lea esi, [eax+eax]
|
||||
cmp esi, ecx
|
||||
ja .doner
|
||||
mov edi, [edx+eax*4-4]
|
||||
cmp [edx+esi*4-4], edi
|
||||
ja .need_xchg
|
||||
cmp esi, ecx
|
||||
jae .doner
|
||||
mov edi, [edx+eax*4-4]
|
||||
cmp [edx+esi*4], edi
|
||||
jbe .doner
|
||||
.need_xchg:
|
||||
cmp esi, ecx
|
||||
jz .do_xchg
|
||||
mov edi, [edx+esi*4-4]
|
||||
cmp [edx+esi*4], edi
|
||||
sbb esi, -1
|
||||
.do_xchg:
|
||||
mov edi, eax
|
||||
call .exchange
|
||||
mov eax, esi
|
||||
jmp .restore
|
||||
.doner:
|
||||
ret
|
60
kernel/trunk/drivers/usbhid/unclaimed.inc
Normal file
60
kernel/trunk/drivers/usbhid/unclaimed.inc
Normal file
@ -0,0 +1,60 @@
|
||||
; HID default driver, part of USBHID driver.
|
||||
; Present only if compile-time setting HID_DUMP_UNCLAIMED is on.
|
||||
; Active for those devices when we do not have a specialized driver.
|
||||
; Just dumps everything to the debug board.
|
||||
|
||||
if HID_DUMP_UNCLAIMED
|
||||
; Global constants.
|
||||
; They are assembled in a macro to separate code and data;
|
||||
; the code is located at the point of "include 'unclaimed.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.
|
||||
default_driver:
|
||||
dd default_driver_add_device
|
||||
dd default_driver_disconnect
|
||||
dd default_driver_begin_packet
|
||||
dd default_driver_array_overflow?
|
||||
dd default_driver_input_field
|
||||
dd default_driver_end_packet
|
||||
}
|
||||
; This procedure is called when HID layer detects a new driverless device.
|
||||
; in: ebx -> usb_device_data, edi -> collection
|
||||
; out: eax = device-specific data or NULL on error
|
||||
default_driver_add_device:
|
||||
; just return something nonzero, no matter what
|
||||
xor eax, eax
|
||||
inc eax
|
||||
ret
|
||||
; 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
|
||||
default_driver_array_overflow?:
|
||||
; parse everything
|
||||
stc
|
||||
ret
|
||||
; This procedure is called from HID layer for every field.
|
||||
; in: ecx = field usage, edx = value, esi -> report_field_group
|
||||
default_driver_input_field:
|
||||
; Do not dump zero values in Variable fields,
|
||||
; they are present even if the corresponding control is inactive.
|
||||
test edx, edx
|
||||
jnz @f
|
||||
test byte [esi+report_field_group.flags], HID_FIELD_VARIABLE
|
||||
jnz .nodump
|
||||
@@:
|
||||
DEBUGF 1,'K : unclaimed HID input: usage=%x, value=%x\n',ecx,edx
|
||||
.nodump:
|
||||
; pass through
|
||||
; Three nothing-to-do procedures.
|
||||
default_driver_disconnect:
|
||||
default_driver_begin_packet:
|
||||
default_driver_end_packet:
|
||||
ret
|
||||
end if
|
553
kernel/trunk/drivers/usbhid/usbhid.asm
Normal file
553
kernel/trunk/drivers/usbhid/usbhid.asm
Normal file
@ -0,0 +1,553 @@
|
||||
; standard driver stuff
|
||||
format MS COFF
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
; this is for DEBUGF macro from 'fdo.inc'
|
||||
__DEBUG__ = 1
|
||||
__DEBUG_LEVEL__ = 1
|
||||
|
||||
include '../proc32.inc'
|
||||
include '../imports.inc'
|
||||
include '../fdo.inc'
|
||||
include '../../struct.inc'
|
||||
|
||||
public START
|
||||
public version
|
||||
|
||||
; Compile-time settings.
|
||||
; If set, the code will dump all descriptors as they are read to the debug board.
|
||||
USB_DUMP_DESCRIPTORS = 1
|
||||
; If set, the code will dump any unclaimed input to the debug board.
|
||||
HID_DUMP_UNCLAIMED = 1
|
||||
|
||||
; USB constants
|
||||
DEVICE_DESCR_TYPE = 1
|
||||
CONFIG_DESCR_TYPE = 2
|
||||
STRING_DESCR_TYPE = 3
|
||||
INTERFACE_DESCR_TYPE = 4
|
||||
ENDPOINT_DESCR_TYPE = 5
|
||||
DEVICE_QUALIFIER_DESCR_TYPE = 6
|
||||
|
||||
CONTROL_PIPE = 0
|
||||
ISOCHRONOUS_PIPE = 1
|
||||
BULK_PIPE = 2
|
||||
INTERRUPT_PIPE = 3
|
||||
|
||||
; USB HID constants
|
||||
HID_DESCR_TYPE = 21h
|
||||
REPORT_DESCR_TYPE = 22h
|
||||
PHYSICAL_DESCR_TYPE = 23h
|
||||
|
||||
; USB structures
|
||||
struct config_descr
|
||||
bLength db ?
|
||||
bDescriptorType db ?
|
||||
wTotalLength dw ?
|
||||
bNumInterfaces db ?
|
||||
bConfigurationValue db ?
|
||||
iConfiguration db ?
|
||||
bmAttributes db ?
|
||||
bMaxPower db ?
|
||||
ends
|
||||
|
||||
struct interface_descr
|
||||
bLength db ?
|
||||
bDescriptorType db ?
|
||||
bInterfaceNumber db ?
|
||||
bAlternateSetting db ?
|
||||
bNumEndpoints db ?
|
||||
bInterfaceClass db ?
|
||||
bInterfaceSubClass db ?
|
||||
bInterfaceProtocol db ?
|
||||
iInterface db ?
|
||||
ends
|
||||
|
||||
struct endpoint_descr
|
||||
bLength db ?
|
||||
bDescriptorType db ?
|
||||
bEndpointAddress db ?
|
||||
bmAttributes db ?
|
||||
wMaxPacketSize dw ?
|
||||
bInterval db ?
|
||||
ends
|
||||
|
||||
; USB HID structures
|
||||
struct hid_descr
|
||||
bLength db ?
|
||||
bDescriptorType db ?
|
||||
bcdHID dw ?
|
||||
bCountryCode db ?
|
||||
bNumDescriptors db ?
|
||||
base_sizeof rb 0
|
||||
; now two fields are repeated .bNumDescriptors times:
|
||||
subDescriptorType db ?
|
||||
subDescriptorLength dw ?
|
||||
ends
|
||||
|
||||
; Include macro for parsing report descriptors/data.
|
||||
macro workers_globals
|
||||
{}
|
||||
include 'report.inc'
|
||||
|
||||
; Driver data for all devices
|
||||
struct usb_device_data
|
||||
hid hid_data ; data of HID layer
|
||||
epdescr dd ? ; endpoint descriptor
|
||||
hiddescr dd ? ; HID descriptor
|
||||
interface_number dd ? ; copy of interface_descr.bInterfaceNumber
|
||||
configpipe dd ? ; config pipe handle
|
||||
intpipe dd ? ; interrupt pipe handle
|
||||
input_transfer_size dd ? ; input transfer size
|
||||
input_buffer dd ? ; buffer for input transfers
|
||||
control rb 8 ; control packet to device
|
||||
ends
|
||||
|
||||
section '.flat' code readable align 16
|
||||
; The start procedure.
|
||||
proc START
|
||||
virtual at esp
|
||||
dd ? ; return address
|
||||
.reason dd ?
|
||||
end virtual
|
||||
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
|
||||
; If not, return 0.
|
||||
xor eax, eax ; initialize return value
|
||||
cmp [.reason], 1 ; compare the argument
|
||||
jnz .nothing
|
||||
; 2. Register self as a USB driver.
|
||||
; The name is my_driver = 'usbhid'; IOCTL interface is not supported;
|
||||
; usb_functions is an offset of a structure with callback functions.
|
||||
stdcall RegUSBDriver, my_driver, eax, usb_functions
|
||||
; 3. Return the returned value of RegUSBDriver.
|
||||
.nothing:
|
||||
ret 4
|
||||
endp
|
||||
|
||||
; This procedure is called when new HID device is detected.
|
||||
; It initializes the device.
|
||||
proc AddDevice
|
||||
push ebx esi edi ; save used registers to be stdcall
|
||||
virtual at esp
|
||||
rd 3 ; saved registers
|
||||
dd ? ; return address
|
||||
.config_pipe dd ?
|
||||
.config_descr dd ?
|
||||
.interface dd ?
|
||||
end virtual
|
||||
DEBUGF 1,'K : USB HID device detected\n'
|
||||
; 1. Allocate memory for device data.
|
||||
movi eax, sizeof.usb_device_data
|
||||
call Kmalloc
|
||||
test eax, eax
|
||||
jnz @f
|
||||
mov esi, nomemory_msg
|
||||
call SysMsgBoardStr
|
||||
jmp .return0
|
||||
@@:
|
||||
; zero-initialize it
|
||||
mov edi, eax
|
||||
xchg eax, ebx
|
||||
xor eax, eax
|
||||
movi ecx, sizeof.usb_device_data / 4
|
||||
rep stosd
|
||||
mov edx, [.interface]
|
||||
; HID devices use one IN interrupt endpoint for polling the device
|
||||
; and an optional OUT interrupt endpoint. We do not use the later,
|
||||
; but must locate the first. Look for the IN interrupt endpoint.
|
||||
; Also, look for the HID descriptor; according to HID spec, it must be
|
||||
; located before endpoint descriptors.
|
||||
; 2. Get the upper bound of all descriptors' data.
|
||||
mov eax, [.config_descr]
|
||||
movzx ecx, [eax+config_descr.wTotalLength]
|
||||
add eax, ecx
|
||||
; 3. Loop over all descriptors until
|
||||
; either end-of-data reached - this is fail
|
||||
; or interface descriptor found - this is fail, all further data
|
||||
; correspond to that interface
|
||||
; or endpoint descriptor for IN endpoint is found
|
||||
; (HID descriptor must be located before the endpoint descriptor).
|
||||
; 3a. Loop start: edx points to the interface descriptor.
|
||||
.lookep:
|
||||
; 3b. Get next descriptor.
|
||||
movzx ecx, byte [edx] ; the first byte of all descriptors is length
|
||||
test ecx, ecx
|
||||
jz .cfgerror
|
||||
add edx, ecx
|
||||
; 3c. Check that at least two bytes are readable. The opposite is an error.
|
||||
inc edx
|
||||
cmp edx, eax
|
||||
jae .cfgerror
|
||||
dec edx
|
||||
; 3d. Check that this descriptor is not interface descriptor. The opposite is
|
||||
; an error.
|
||||
cmp [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
|
||||
jz .cfgerror
|
||||
; 3e. For HID descriptor, proceed to 4.
|
||||
; For endpoint descriptor, go to 5.
|
||||
; For other descriptors, continue the loop.
|
||||
; Note: bDescriptorType is in the same place in all descriptors.
|
||||
cmp [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
|
||||
jz .foundep
|
||||
cmp [edx+endpoint_descr.bDescriptorType], HID_DESCR_TYPE
|
||||
jnz .lookep
|
||||
; 4a. Check that the descriptor contains all required data and all data are
|
||||
; readable. The opposite is an error.
|
||||
movzx ecx, [edx+hid_descr.bLength]
|
||||
cmp ecx, hid_descr.base_sizeof + 3
|
||||
jb .cfgerror
|
||||
add ecx, edx
|
||||
cmp ecx, eax
|
||||
ja .cfgerror
|
||||
; 4b. Store the pointer in usb_device_data structure for further references.
|
||||
mov [ebx+usb_device_data.hiddescr], edx
|
||||
; 4c. Continue the loop.
|
||||
jmp .lookep
|
||||
.foundep:
|
||||
; 5a. Check that the descriptor contains all required data and all data are
|
||||
; readable. The opposite is an error.
|
||||
cmp byte [edx+endpoint_descr.bLength], sizeof.endpoint_descr
|
||||
jb .cfgerror
|
||||
lea ecx, [edx+sizeof.endpoint_descr]
|
||||
cmp ecx, eax
|
||||
jbe @f
|
||||
; 6. An error occured during processing endpoint descriptor.
|
||||
.cfgerror:
|
||||
; 6a. Print a message.
|
||||
mov esi, invalid_config_descr_msg
|
||||
call SysMsgBoardStr
|
||||
; 6b. Free memory allocated for device data.
|
||||
.free:
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
.return0:
|
||||
; 6c. Return an error.
|
||||
xor eax, eax
|
||||
.nothing:
|
||||
pop edi esi ebx ; restore used registers to be stdcall
|
||||
ret 12
|
||||
@@:
|
||||
; 5b. If this is not IN interrupt endpoint, ignore it and continue the loop.
|
||||
test [edx+endpoint_descr.bEndpointAddress], 80h
|
||||
jz .lookep
|
||||
mov cl, [edx+endpoint_descr.bmAttributes]
|
||||
and cl, 3
|
||||
cmp cl, INTERRUPT_PIPE
|
||||
jnz .lookep
|
||||
; 5c. Store the pointer in usb_device_data structure for futher references.
|
||||
mov [ebx+usb_device_data.epdescr], edx
|
||||
; 5d. Check that HID descriptor was found. If not, go to 6.
|
||||
cmp [ebx+usb_device_data.hiddescr], 0
|
||||
jz .cfgerror
|
||||
.descriptors_found:
|
||||
; 6. Configuration descriptor seems to be ok.
|
||||
; Send SET_IDLE command disabling auto-repeat feature (it is quite useless)
|
||||
; and continue configuring in SET_IDLE callback.
|
||||
lea edx, [ebx+usb_device_data.control]
|
||||
mov eax, [.interface]
|
||||
mov dword [edx], 21h + \ ; Class-specific request to Interface
|
||||
(0Ah shl 8) + \ ; SET_IDLE
|
||||
(0 shl 16) + \ ; apply to all input reports
|
||||
(0 shl 24) ; disable auto-repeat
|
||||
movzx eax, [eax+interface_descr.bInterfaceNumber]
|
||||
mov [ebx+usb_device_data.interface_number], eax
|
||||
mov [edx+4], eax ; set interface number, zero length
|
||||
mov eax, [.config_pipe]
|
||||
mov [ebx+usb_device_data.configpipe], eax
|
||||
xor ecx, ecx
|
||||
stdcall USBControlTransferAsync, eax, edx, ecx, ecx, idle_set, ebx, ecx
|
||||
; 7. Return pointer to usb_device_data.
|
||||
xchg eax, ebx
|
||||
jmp .nothing
|
||||
endp
|
||||
|
||||
; This procedure is called by USB stack when SET_IDLE request initiated by
|
||||
; AddDevice is completed, either successfully or unsuccessfully.
|
||||
proc idle_set
|
||||
push ebx esi ; save used registers to be stdcall
|
||||
virtual at esp
|
||||
rd 2 ; saved registers
|
||||
dd ? ; return address
|
||||
.pipe dd ?
|
||||
.status dd ?
|
||||
.buffer dd ?
|
||||
.length dd ?
|
||||
.calldata dd ?
|
||||
end virtual
|
||||
; Ignore status. Support for SET_IDLE is optional, so the device is free to
|
||||
; STALL the request; config pipe should remain functional without explicit cleanup.
|
||||
mov ebx, [.calldata]
|
||||
; 1. HID descriptor contains length of Report descriptor. Parse it.
|
||||
mov esi, [ebx+usb_device_data.hiddescr]
|
||||
movzx ecx, [esi+hid_descr.bNumDescriptors]
|
||||
lea eax, [hid_descr.base_sizeof+ecx*3]
|
||||
cmp eax, 100h
|
||||
jae .cfgerror
|
||||
cmp al, [esi+hid_descr.bLength]
|
||||
jb .cfgerror
|
||||
.look_report:
|
||||
dec ecx
|
||||
js .cfgerror
|
||||
cmp [esi+hid_descr.subDescriptorType], REPORT_DESCR_TYPE
|
||||
jz .found_report
|
||||
add esi, 3
|
||||
jmp .look_report
|
||||
.cfgerror:
|
||||
mov esi, invalid_config_descr_msg
|
||||
.abort_with_msg:
|
||||
call SysMsgBoardStr
|
||||
jmp .nothing
|
||||
.found_report:
|
||||
; 2. Send request for the Report descriptor.
|
||||
; 2a. Allocate memory.
|
||||
movzx eax, [esi+hid_descr.subDescriptorLength]
|
||||
test eax, eax
|
||||
jz .cfgerror
|
||||
push eax
|
||||
call Kmalloc
|
||||
pop ecx
|
||||
; If failed, say a message and stop initialization.
|
||||
mov esi, nomemory_msg
|
||||
test eax, eax
|
||||
jz .abort_with_msg
|
||||
; 2b. Submit the request.
|
||||
xchg eax, esi
|
||||
lea edx, [ebx+usb_device_data.control]
|
||||
mov eax, [ebx+usb_device_data.interface_number]
|
||||
mov dword [edx], 81h + \ ; Standard request to Interface
|
||||
(6 shl 8) + \ ; GET_DESCRIPTOR
|
||||
(0 shl 16) + \ ; descriptor index: there is only one report descriptor
|
||||
(REPORT_DESCR_TYPE shl 24); descriptor type
|
||||
mov [edx+4], ax ; Interface number
|
||||
mov [edx+6], cx ; descriptor length
|
||||
stdcall USBControlTransferAsync, [ebx+usb_device_data.configpipe], \
|
||||
edx, esi, ecx, got_report, ebx, 0
|
||||
; 2c. If failed, free the buffer and stop initialization.
|
||||
test eax, eax
|
||||
jnz .nothing
|
||||
xchg eax, esi
|
||||
call Kfree
|
||||
.nothing:
|
||||
pop esi ebx ; restore used registers to be stdcall
|
||||
ret 20
|
||||
endp
|
||||
|
||||
; This procedure is called by USB stack when the report descriptor queried
|
||||
; by idle_set is completed, either successfully or unsuccessfully.
|
||||
proc got_report stdcall uses ebx esi edi, pipe, status, buffer, length, calldata
|
||||
locals
|
||||
parse_descr_locals
|
||||
if ~HID_DUMP_UNCLAIMED
|
||||
has_driver db ?
|
||||
rb 3
|
||||
end if
|
||||
endl
|
||||
; 1. Check the status; if the request has failed, say something to the debug board
|
||||
; and stop initialization.
|
||||
cmp [status], 0
|
||||
jnz .generic_fail
|
||||
; 2. Subtract size of setup packet from the total length;
|
||||
; the rest is length of the descriptor, and it must be nonzero.
|
||||
sub [length], 8
|
||||
ja .has_something
|
||||
.generic_fail:
|
||||
push esi
|
||||
mov esi, reportfail
|
||||
call SysMsgBoardStr
|
||||
pop esi
|
||||
jmp .exit
|
||||
.has_something:
|
||||
; 3. Process descriptor.
|
||||
; 3a. Dump it to the debug board, if enabled in compile-time setting.
|
||||
if USB_DUMP_DESCRIPTORS
|
||||
mov eax, [buffer]
|
||||
mov ecx, [length]
|
||||
DEBUGF 1,'K : report descriptor:'
|
||||
@@:
|
||||
DEBUGF 1,' %x',[eax]:2
|
||||
inc eax
|
||||
dec ecx
|
||||
jnz @b
|
||||
DEBUGF 1,'\n'
|
||||
end if
|
||||
; 3b. Call the HID layer.
|
||||
parse_descr
|
||||
cmp [report_ok], 0
|
||||
jz got_report.exit
|
||||
mov ebx, [calldata]
|
||||
postprocess_descr
|
||||
; 4. Stop initialization if no driver is assigned.
|
||||
if ~HID_DUMP_UNCLAIMED
|
||||
cmp [has_driver], 0
|
||||
jz got_report.exit
|
||||
end if
|
||||
; 5. Open interrupt IN pipe. If failed, stop initialization.
|
||||
mov edx, [ebx+usb_device_data.epdescr]
|
||||
movzx ecx, [edx+endpoint_descr.bEndpointAddress]
|
||||
movzx eax, [edx+endpoint_descr.bInterval]
|
||||
movzx edx, [edx+endpoint_descr.wMaxPacketSize]
|
||||
stdcall USBOpenPipe, [ebx+usb_device_data.configpipe], ecx, edx, INTERRUPT_PIPE, eax
|
||||
test eax, eax
|
||||
jz got_report.exit
|
||||
mov [ebx+usb_device_data.intpipe], eax
|
||||
; 6. Initialize buffer for input packet.
|
||||
; 6a. Find the length of input packet.
|
||||
; This is the maximal length of all input reports.
|
||||
mov edx, [ebx+usb_device_data.hid.input.first_report]
|
||||
xor eax, eax
|
||||
.find_input_size:
|
||||
test edx, edx
|
||||
jz .found_input_size
|
||||
cmp eax, [edx+report.size]
|
||||
jae @f
|
||||
mov eax, [edx+report.size]
|
||||
@@:
|
||||
mov edx, [edx+report.next]
|
||||
jmp .find_input_size
|
||||
.found_input_size:
|
||||
; report.size is in bits, transform it to bytes
|
||||
add eax, 7
|
||||
shr eax, 3
|
||||
; if reports are numbered, the first byte is report ID, include it
|
||||
cmp [ebx+usb_device_data.hid.input.numbered], 0
|
||||
jz @f
|
||||
inc eax
|
||||
@@:
|
||||
mov [ebx+usb_device_data.input_transfer_size], eax
|
||||
; 6b. Allocate memory for input packet: dword-align and add additional dword
|
||||
; for extract_field_value.
|
||||
add eax, 4+3
|
||||
and eax, not 3
|
||||
call Kmalloc
|
||||
test eax, eax
|
||||
jnz @f
|
||||
mov esi, nomemory_msg
|
||||
call SysMsgBoardStr
|
||||
jmp got_report.exit
|
||||
@@:
|
||||
mov [ebx+usb_device_data.input_buffer], eax
|
||||
; 7. Submit a request for input packet and wait for input.
|
||||
call ask_for_input
|
||||
got_report.exit:
|
||||
mov eax, [buffer]
|
||||
call Kfree
|
||||
ret
|
||||
endp
|
||||
|
||||
; Helper procedure for got_report and got_input.
|
||||
; Submits a request for the next input packet.
|
||||
proc ask_for_input
|
||||
; just call USBNormalTransferAsync with correct parameters,
|
||||
; allow short packets
|
||||
stdcall USBNormalTransferAsync, \
|
||||
[ebx+usb_device_data.intpipe], \
|
||||
[ebx+usb_device_data.input_buffer], \
|
||||
[ebx+usb_device_data.input_transfer_size], \
|
||||
got_input, ebx, \
|
||||
1
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called by USB stack when a HID device responds with input
|
||||
; data packet.
|
||||
proc got_input stdcall uses ebx esi edi, pipe, status, buffer, length, calldata
|
||||
locals
|
||||
parse_input_locals
|
||||
endl
|
||||
; 1. Validate parameters: fail on error, ignore zero-length transfers.
|
||||
mov ebx, [calldata]
|
||||
cmp [status], 0
|
||||
jnz .fail
|
||||
cmp [length], 0
|
||||
jz .done
|
||||
; 2. Get pointer to report in esi.
|
||||
; 2a. If there are no report IDs, use hid.input.data.
|
||||
mov eax, [buffer]
|
||||
mov esi, [ebx+usb_device_data.hid.input.data]
|
||||
cmp [ebx+usb_device_data.hid.input.numbered], 0
|
||||
jz .report_found
|
||||
; 2b. Otherwise, the first byte of report is report ID;
|
||||
; locate the report by its ID, advance buffer+length to one byte.
|
||||
movzx eax, byte [eax]
|
||||
mov esi, [esi+eax*4]
|
||||
inc [buffer]
|
||||
dec [length]
|
||||
.report_found:
|
||||
; 3. Validate: ignore transfers with unregistered report IDs
|
||||
; and transfers which are too short for the corresponding report.
|
||||
test esi, esi
|
||||
jz .done
|
||||
mov eax, [esi+report.size]
|
||||
add eax, 7
|
||||
shr eax, 3
|
||||
cmp eax, [length]
|
||||
ja .done
|
||||
; 4. Pass everything to HID layer.
|
||||
parse_input
|
||||
.done:
|
||||
; 5. Query the next input.
|
||||
mov ebx, [calldata]
|
||||
call ask_for_input
|
||||
.nothing:
|
||||
ret
|
||||
.fail:
|
||||
mov esi, transfer_error_msg
|
||||
call SysMsgBoardStr
|
||||
jmp .nothing
|
||||
endp
|
||||
|
||||
; This function is called by the USB subsystem when a device is disconnected.
|
||||
proc DeviceDisconnected
|
||||
push ebx esi edi ; save used registers to be stdcall
|
||||
virtual at esp
|
||||
rd 3 ; saved registers
|
||||
dd ? ; return address
|
||||
.device_data dd ?
|
||||
end virtual
|
||||
; 1. Say a message.
|
||||
mov ebx, [.device_data]
|
||||
mov esi, disconnectmsg
|
||||
stdcall SysMsgBoardStr
|
||||
; 2. Ask HID layer to release all HID-related resources.
|
||||
hid_cleanup
|
||||
; 3. Free the device data.
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
; 4. Return.
|
||||
.nothing:
|
||||
pop edi esi ebx ; restore used registers to be stdcall
|
||||
ret 4 ; purge one dword argument to be stdcall
|
||||
endp
|
||||
|
||||
include 'sort.inc'
|
||||
include 'unclaimed.inc'
|
||||
include 'mouse.inc'
|
||||
include 'keyboard.inc'
|
||||
|
||||
; strings
|
||||
my_driver db 'usbhid',0
|
||||
nomemory_msg db 'K : no memory',13,10,0
|
||||
invalid_config_descr_msg db 'K : invalid config descriptor',13,10,0
|
||||
reportfail db 'K : failed to read report descriptor',13,10,0
|
||||
transfer_error_msg db 'K : USB transfer error, disabling HID device',13,10,0
|
||||
disconnectmsg db 'K : USB HID device disconnected',13,10,0
|
||||
invalid_report_msg db 'K : report descriptor is invalid',13,10,0
|
||||
delimiter_note db 'K : note: alternate usage ignored',13,10,0
|
||||
|
||||
; Exported variable: kernel API version.
|
||||
align 4
|
||||
version dd 50005h
|
||||
; Structure with callback functions.
|
||||
usb_functions:
|
||||
dd 12
|
||||
dd AddDevice
|
||||
dd DeviceDisconnected
|
||||
|
||||
; for DEBUGF macro
|
||||
include_debug_strings
|
||||
|
||||
; Workers data
|
||||
workers_globals
|
||||
|
||||
; for uninitialized data
|
||||
;section '.data' data readable writable align 16
|
Loading…
Reference in New Issue
Block a user