kolibrios-gitea/drivers/usb/usbhid/usbhid.asm

554 lines
18 KiB
NASM
Raw Permalink Normal View History

; standard driver stuff; version of driver model = 5
format PE DLL native 0.05
entry START
DEBUG = 1
; this is for DEBUGF macro from 'fdo.inc'
__DEBUG__ = 1
__DEBUG_LEVEL__ = 1
include '../../struct.inc'
; 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 writable executable
include '../../macros.inc'
include '../../proc32.inc'
include '../../peimport.inc'
include '../../fdo.inc'
; The start procedure.
proc START
virtual at esp
dd ? ; return address
.reason dd ?
.cmdline 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.
invoke RegUSBDriver, my_driver, eax, usb_functions
; 3. Return the returned value of RegUSBDriver.
.nothing:
ret
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
invoke Kmalloc
test eax, eax
jnz @f
mov esi, nomemory_msg
invoke 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
invoke SysMsgBoardStr
; 6b. Free memory allocated for device data.
.free:
xchg eax, ebx
invoke 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
invoke 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:
invoke 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
invoke 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
invoke 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
invoke 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
invoke 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]
invoke 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
invoke Kmalloc
test eax, eax
jnz @f
mov esi, nomemory_msg
invoke 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]
invoke 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
invoke 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
invoke 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
invoke SysMsgBoardStr
; 2. Ask HID layer to release all HID-related resources.
hid_cleanup
; 3. Free the device data.
xchg eax, ebx
invoke 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'
include 'multimedia.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
align 4
; Structure with callback functions.
usb_functions:
dd 12
dd AddDevice
dd DeviceDisconnected
; for DEBUGF macro
include_debug_strings
; Workers data
workers_globals
align 4
data fixups
end data