full-fledged USB HID driver

git-svn-id: svn://kolibrios.org@3709 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
CleverMouse 2013-06-26 22:08:56 +00:00
parent b457ca5b59
commit 296994e3bb
11 changed files with 2740 additions and 697 deletions

View File

@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.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/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \

View File

@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.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/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \

View File

@ -130,7 +130,7 @@ FASM_PROGRAMS:=\
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.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/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \

View File

@ -129,7 +129,7 @@ FASM_PROGRAMS:=\
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.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/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \

View File

@ -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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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