diff --git a/data/eng/Makefile b/data/eng/Makefile index 61ab2054de..5c146cb126 100644 --- a/data/eng/Makefile +++ b/data/eng/Makefile @@ -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 \ diff --git a/data/it/Makefile b/data/it/Makefile index a941d13db3..44b9852efc 100644 --- a/data/it/Makefile +++ b/data/it/Makefile @@ -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 \ diff --git a/data/rus/Makefile b/data/rus/Makefile index bf8b8300e7..1f2eb51858 100644 --- a/data/rus/Makefile +++ b/data/rus/Makefile @@ -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 \ diff --git a/data/sp/Makefile b/data/sp/Makefile index b8185f16dd..e074303b61 100644 --- a/data/sp/Makefile +++ b/data/sp/Makefile @@ -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 \ diff --git a/kernel/trunk/drivers/usbhid.asm b/kernel/trunk/drivers/usbhid.asm deleted file mode 100644 index eb9abc9775..0000000000 --- a/kernel/trunk/drivers/usbhid.asm +++ /dev/null @@ -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; , , -; 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;, , -; 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 diff --git a/kernel/trunk/drivers/usbhid/keyboard.inc b/kernel/trunk/drivers/usbhid/keyboard.inc new file mode 100644 index 0000000000..d906ce7e34 --- /dev/null +++ b/kernel/trunk/drivers/usbhid/keyboard.inc @@ -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 diff --git a/kernel/trunk/drivers/usbhid/mouse.inc b/kernel/trunk/drivers/usbhid/mouse.inc new file mode 100644 index 0000000000..7c1b4c6db2 --- /dev/null +++ b/kernel/trunk/drivers/usbhid/mouse.inc @@ -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 diff --git a/kernel/trunk/drivers/usbhid/report.inc b/kernel/trunk/drivers/usbhid/report.inc new file mode 100644 index 0000000000..c278429428 --- /dev/null +++ b/kernel/trunk/drivers/usbhid/report.inc @@ -0,0 +1,1442 @@ +; Parser of HID structures: parse HID report descriptor, +; parse/generate input/output/feature reports. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; Usage codes from HID specification +; Generic Desktop usage page +USAGE_GD_POINTER = 10001h +USAGE_GD_MOUSE = 10002h +USAGE_GD_JOYSTICK = 10004h +USAGE_GD_GAMEPAD = 10005h +USAGE_GD_KEYBOARD = 10006h +USAGE_GD_KEYPAD = 10007h + +USAGE_GD_X = 10030h +USAGE_GD_Y = 10031h +USAGE_GD_Z = 10032h +USAGE_GD_RX = 10033h +USAGE_GD_RY = 10034h +USAGE_GD_RZ = 10035h +USAGE_GD_SLIDER = 10036h +USAGE_GD_DIAL = 10037h +USAGE_GD_WHEEL = 10038h + +; Keyboard/Keypad usage page +USAGE_KBD_NOEVENT = 70000h +USAGE_KBD_ROLLOVER = 70001h +USAGE_KBD_POSTFAIL = 70002h +USAGE_KBD_FIRST_KEY = 70004h ; this is 'A', actually +USAGE_KBD_LCTRL = 700E0h +USAGE_KBD_LSHIFT = 700E1h +USAGE_KBD_LALT = 700E2h +USAGE_KBD_LWIN = 700E3h +USAGE_KBD_RCTRL = 700E4h +USAGE_KBD_RSHIFT = 700E5h +USAGE_KBD_RALT = 700E6h +USAGE_KBD_RWIN = 700E7h + +; LED usage page +USAGE_LED_NUMLOCK = 80001h +USAGE_LED_CAPSLOCK = 80002h +USAGE_LED_SCROLLLOCK = 80003h + +; Button usage page +; First button is USAGE_BUTTON_PAGE+1, second - USAGE_BUTTON_PAGE+2 etc. +USAGE_BUTTON_PAGE = 90000h + +; Flags for input/output/feature fields +HID_FIELD_CONSTANT = 1 ; if not, then Data field +HID_FIELD_VARIABLE = 2 ; if not, then Array field +HID_FIELD_RELATIVE = 4 ; if not, then Absolute field +HID_FIELD_WRAP = 8 +HID_FIELD_NONLINEAR = 10h +HID_FIELD_NOPREFERRED= 20h ; no preferred state +HID_FIELD_HASNULL = 40h ; has null state +HID_FIELD_VOLATILE = 80h ; for output/feature fields +HID_FIELD_BUFBYTES = 100h; buffered bytes + +; Report descriptor can easily describe gigabytes of (meaningless) data. +; Keep report size reasonable to avoid excessive memory allocations and +; calculation overflows; 1 Kb is more than enough (typical size is 3-10 bytes). +MAX_REPORT_BYTES = 1024 + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= +; Every meaningful report field group has one or more associated usages. +; Usages can be individual or joined into continuous ranges. +; This structure describes one range or one individual usage in a large array; +; individual usage is equivalent to a range of length 1. +struct usage_range +offset dd ? +; Sum of range sizes over all previous array items. +; Size of range a equals +; [a + sizeof.usage_range + usage_range.offset] - [a + usage_range.offset]. +; The total sum over all array items immediately follows the array, +; this field must be the first so that the formula above works for the last item. +first_usage dd ? +; Usage code for first item in the range. +ends + +; This structure describes one group of report fields with identical properties. +struct report_field_group +next dd ? +; All field groups in one report are organized in a single-linked list. +; This is the next group in the report or 0 for the last group. +size dd ? +; Size in bits of one field. Cannot be zero or greater than 32. +count dd ? ; field count, cannot be zero +offset dd ? ; offset from report start, in bits +; Following fields are decoded from report descriptor, see HID spec for details. +flags dd ? +logical_minimum dd ? +logical_maximum dd ? +physical_minimum dd ? +physical_maximum dd ? +unit_exponent dd ? +unit dd ? +; Following fields are used to speedup extract_field_value. +mask dd ? +; Bitmask for all data bits except sign bit: +; (1 shl .size) - 1 for unsigned fields, (1 shl (.size-1)) - 1 for signed fields +sign_mask dd ? +; Zero for unsigned fields. Bitmask with sign bit set for signed fields. +common_sizeof rd 0 +; Variable and Array field groups differ significantly. +; Variable field groups are simple. There are .count fields, each field has +; predefined Usage, the content of a field is its value. Each field is +; always present in the report. For Variable field groups, we just keep +; additional .count dwords with usages for individual fields. +; Array field groups are complicated. There are .count uniform fields. +; The content of a field determines Usage; Usages which are currently presented +; in the report have value = 1, other Usages have value = 0. The number of +; possible Usages is limited only by field .size; 32-bit field could encode any +; Usage, so it is unreasonable to keep all Usages in the plain array, as with +; Variable fields. However, many unrelated Usages in one group are meaningless, +; so usually possible values are grouped in sequential ranges; number of ranges +; is limited by report descriptor size (max 0xFFFF bytes should contain all +; information, including usage ranges and field descriptions). +; Also, for Array variables we pass changes in state to drivers, not the state +; itself, because sending information about all possible Usages is inpractical; +; so we should remember the previous state in addition to the current state. +; Thus, for Array variables keep the following information, in this order: +; * some members listed below; note that they do NOT exist for Variable groups; +; * array of usage ranges in form of usage_range structures, including +; an additional dword after array described in usage_range structure; +; * allocated memory for current values of the report; +; * values of the previous report. +num_values_prev dd ? ; number of values in the previous report +num_usage_ranges dd ? ; number of usage_range, always nonzero +usages rd 0 +ends + +; This structure describes one report. +; All reports of one type are organized into a single-linked list. +struct report +next dd ? ; pointer to next report of the same type, if any +size dd ? ; total size in bits +first_field dd ? ; pointer to first report_field_group for this report +last_field dd ? +; pointer to last report_field_group for this report, if any; +; address of .first_field, if .first_field is 0 +id dd ? +; Report ID, if assigned. Zero otherwise. +top_level_collection dd ? ; top-level collection for this report +ends + +; This structure describes a set of reports of the same type; +; there are 3 sets (possibly empty), input, output and feature. +struct report_set +data dd ? +; If .numbered is zero, this is zero for the empty set and +; a pointer to the (only) report structure otherwise. +; If .numbered is nonzero, this is a pointer to 256-dword array of pointers +; to reports organized by report ID. +first_report dd ? +; Pointer to the first report or 0 for the empty set. +numbered db ? +; If zero, report IDs are not used, there can be at most one report in the set. +; If nonzero, first byte of the report is report ID. + rb 3 ; padding +ends + +; This structure describes a range of reports of one type that belong to +; some collection. +struct collection_report_set +first_report dd ? +first_field dd ? +last_report dd ? +last_field dd ? +ends + +; This structure defines driver callbacks which are used while +; device is active; i.e. all callbacks except add_device. +struct hid_driver_active_callbacks +disconnect dd ? +; Called when an existing HID device is disconnected. +; +; Four following functions are called when a new input packet arrives +; in the following order: .begin_packet, then .input_field several times +; for each input field, interleaved with .array_overflow? for array groups, +; then .end_packet. +begin_packet dd ? +; edi -> driver data +array_overflow? dd ? +; edi -> driver data +; out: CF cleared <=> ignore this array +input_field dd ? +; edi -> driver data, ecx = usage, edx = value +end_packet dd ? +; edi -> driver data +ends + +; This structure describes one collection. +struct collection +next dd ? ; pointer to the next collection in the same level + ; must be the first field +parent dd ? ; pointer to nesting collection +first_child dd ? ; pointer to the first nested collection +last_child dd ? ; pointer to the last nested collection + ; or to .first_child, if .first_child is zero +type dd ? ; Application, Physical etc +usage dd ? ; associated Usage code +; Next fields are filled only for top-level collections. +callbacks hid_driver_active_callbacks +driver_data dd ? ; value to be passed as is to driver callbacks +input collection_report_set +output collection_report_set +feature collection_report_set +ends + +; This structure keeps all data used by the HID layer for one device. +struct hid_data +input report_set +output report_set +feature report_set +first_collection dd ? +ends + +; This structure defines callbacks required from the driver. +struct hid_driver_callbacks +add_device dd ? +; Called when a new HID device is connected. +active hid_driver_active_callbacks +ends + +; Two following structures describe temporary data; +; the corresponding objects cease to exist when HID parser completes +; state of Global items +struct global_items +next dd ? +usage_page dd ? +logical_minimum dd ? +logical_maximum dd ? +physical_minimum dd ? +physical_maximum dd ? +unit_exponent dd ? +unit dd ? +report_size dd ? +report_id dd ? +report_count dd ? +ends + +; one range of Usages +struct usage_list_item +next dd ? +first_usage dd ? +num_usages dd ? +ends + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +macro workers_globals +{ + workers_globals +; Jump tables for switch'ing in the code. +align 4 +; jump table for two lower bits which encode size of item data +parse_descr_label.fetch_jumps: + dd parse_descr_label.fetch_none ; x0, x4, x8, xC + dd parse_descr_label.fetch_byte ; x1, x5, x9, xD + dd parse_descr_label.fetch_word ; x2, x6, xA, xE + dd parse_descr_label.fetch_dword ; x3, x7, xB, xF +; jump table for two next bits which encode item type +parse_descr_label.type_jumps: + dd parse_descr_label.parse_main + dd parse_descr_label.parse_global + dd parse_descr_label.parse_local + dd parse_descr_label.parse_reserved +; jump table for 4 upper bits in the case of Main item +parse_descr_label.main_jumps: + dd parse_descr_label.input ; 80...83 + dd parse_descr_label.output ; 90...93 + dd parse_descr_label.collection ; A0...A3 + dd parse_descr_label.feature ; B0...B3 + dd parse_descr_label.end_collection ; C0...C3 +parse_descr_label.num_main_items = ($ - parse_descr_label.main_jumps) / 4 +; jump table for 4 upper bits in the case of Global item +parse_descr_label.global_jumps: + dd parse_descr_label.usage_page ; 04...07 + dd parse_descr_label.logical_minimum ; 14...17 + dd parse_descr_label.logical_maximum ; 24...27 + dd parse_descr_label.physical_minimum ; 34...37 + dd parse_descr_label.physical_maximum ; 44...47 + dd parse_descr_label.unit_exponent ; 54...57 + dd parse_descr_label.unit ; 64...67 + dd parse_descr_label.report_size ; 74...77 + dd parse_descr_label.report_id ; 84...87 + dd parse_descr_label.report_count ; 94...97 + dd parse_descr_label.push ; A4...A7 + dd parse_descr_label.pop ; B4...B7 +parse_descr_label.num_global_items = ($ - parse_descr_label.global_jumps) / 4 +; jump table for 4 upper bits in the case of Local item +parse_descr_label.local_jumps: + dd parse_descr_label.usage ; 08...0B + dd parse_descr_label.usage_minimum ; 18...1B + dd parse_descr_label.usage_maximum ; 28...2B + dd parse_descr_label.item_parsed ; 38...3B = designator item; ignore + dd parse_descr_label.item_parsed ; 48...4B = designator minimum; ignore + dd parse_descr_label.item_parsed ; 58...5B = designator maximum; ignore + dd parse_descr_label.item_parsed ; 68...6B not assigned + dd parse_descr_label.item_parsed ; 78...7B = string index; ignore + dd parse_descr_label.item_parsed ; 88...8B = string minimum; ignore + dd parse_descr_label.item_parsed ; 98...9B = string maximum; ignore + dd parse_descr_label.delimiter ; A8...AB +parse_descr_label.num_local_items = ($ - parse_descr_label.local_jumps) / 4 +} + +; Local variables for parse_descr. +macro parse_descr_locals +{ +cur_item_size dd ? ; encoded size of data for current item +report_ok db ? ; 0 on error, 1 if everything is ok +field_type db ? ; 0/1/2 for input/output/feature fields + rb 2 ; alignment +field_data dd ? ; data for current item when it describes a field group +last_reports rd 3 ; pointers to last input/output/feature records +usage_minimum dd ? ; current value of Usage Minimum +usage_list dd ? ; list head of usage_list_item +usage_tail dd ? ; list tail of usage_list_item +num_usage_ranges dd ? ; number of usage ranges, size of usage_list +delimiter_depth dd ? ; normally 0; 1 inside of Delimiter(); + ; nested Delimiter()s are not allowed +usage_variant dd ? ; 0 outside of Delimiter()s and for first Usage inside Delimiter(), + ; incremented with each new Usage inside Delimiter() +cur_collection dd ? ; current collection +last_collection dd ? ; last top-level collection +} + +; Parse report descriptor. The caller should provide local variables +; [buffer] = pointer to report descriptor, [length] = length of report descriptor, +; [calldata] = pointer to hid_data (possibly wrapped in a large structure). +macro parse_descr +{ +parse_descr_label: +; 1. Initialize. +; 1a. Set some variables to initial values. + xor edi, edi + mov dword [report_ok], edi + mov [usage_list], edi + mov [cur_collection], edi + mov eax, [calldata] + add eax, hid_data.input.first_report + mov [last_reports+0*4], eax + add eax, hid_data.output.first_report - hid_data.input.first_report + mov [last_reports+1*4], eax + add eax, hid_data.feature.first_report - hid_data.output.first_report + mov [last_reports+2*4], eax + add eax, hid_data.first_collection - hid_data.feature.first_report + mov [last_collection], eax +; 1b. Allocate state of global items. + movi eax, sizeof.global_items + call Kmalloc + test eax, eax + jz .memory_error +; 1c. Zero-initialize it and move pointer to edi. + push eax + xchg eax, edi + movi ecx, sizeof.global_items / 4 + rep stosd + pop edi +; 1d. Load pointer to data into esi and make [length] point to end of data. + mov esi, [buffer] + add [length], esi +; 2. Clear all local items. +; This is needed in the beginning and after processing any Main item. +.zero_local_items: + mov eax, [usage_list] +@@: + test eax, eax + jz @f + push [eax+usage_list_item.next] + call Kfree + pop eax + jmp @b +@@: + lea ecx, [usage_list] + mov [usage_tail], ecx + mov [ecx], eax + mov [delimiter_depth], eax + mov [usage_variant], eax + mov [usage_minimum], eax + mov [num_usage_ranges], eax +; 3. Parse items until end of data found. + cmp esi, [length] + jae .parse_end +.fetch_next_item: +; --------------------------------- Parse item -------------------------------- +; 4. Parse one item. +; 4a. Get item data. eax = first item byte = code+type+size (4+2+2 bits), +; ebx = item data interpreted as unsigned, +; ecx = item data interpreted as signed. + movzx eax, byte [esi] + mov ecx, eax + and ecx, 3 + mov [cur_item_size], ecx + jmp dword [.fetch_jumps+ecx*4] +.invalid_report: + mov esi, invalid_report_msg + jmp .end_str +.fetch_none: + xor ebx, ebx + xor ecx, ecx + inc esi + jmp .fetched +.fetch_byte: + add esi, 2 + cmp esi, [length] + ja .invalid_report + movzx ebx, byte [esi-1] + movsx ecx, bl + jmp .fetched +.fetch_word: + add esi, 3 + cmp esi, [length] + ja .invalid_report + movzx ebx, word [esi-2] + movsx ecx, bx + jmp .fetched +.fetch_dword: + add esi, 5 + cmp esi, [length] + ja .invalid_report + mov ebx, dword [esi-4] + mov ecx, ebx +.fetched: +; 4b. Select the branch according to item type. +; For every type, select the concrete handler and go there. + mov edx, eax + shr edx, 2 + and edx, 3 + shr eax, 4 + jmp dword [.type_jumps+edx*4] +; -------------------------------- Main items --------------------------------- +.parse_main: + sub eax, 8 + cmp eax, .num_main_items + jae .item_parsed + jmp dword [.main_jumps+eax*4] +; There are 5 Main items. +; Input/Output/Feature items create new field groups in the corresponding report; +; Collection item opens a new collection (possibly nested), +; End Collection item closes the most nested collection. +.output: + mov [field_type], 1 + jmp .new_field +.feature: + mov [field_type], 2 + jmp .new_field +.input: + mov [field_type], 0 +.new_field: +; Create a new field group. + mov [field_data], ebx + movzx ebx, [field_type] +if sizeof.report_set = 12 + lea ebx, [ebx*3] + shl ebx, 2 +else +err Change the code +end if + add ebx, [calldata] +; 5. Sanity checks: field size and fields count must be nonzero, +; field size cannot be more than 32 bits, +; if field count is more than MAX_REPORT_SIZE * 8, the report would be more than +; MAX_REPORT_SIZE bytes, so it is invalid too. +; More precise check for size occurs later; this check only guarantees that +; there will be no overflows during subsequent calculations. + cmp [edi+global_items.report_size], 0 + jz .invalid_report + cmp [edi+global_items.report_size], 32 + ja .invalid_report +; There are devices with Report Count(0) + Input(Constant Variable), +; zero-length padding. Thus, do not consider descriptors with Report Count(0) +; as invalid; instead, just ignore fields with Report Count(0). + cmp [edi+global_items.report_count], 0 + jz .zero_local_items + cmp [edi+global_items.report_count], MAX_REPORT_BYTES * 8 + ja .invalid_report +; 6. Get the pointer to the place for the corresponding report in ebx. +; 6a. If report ID is not assigned, ebx already points to report_set.data, +; so go to 7. + cmp [edi+global_items.report_id], 0 + jz .report_ptr_found +; 6b. If table for reports was already allocated, +; go to 6d skipping the next substep. + cmp [ebx+report_set.numbered], 0 + jnz .report_set_allocated +; 6c. This is the first report with ID; +; allocate and zero-initialize table for reports. +; Note: it is incorrect but theoretically possible that some fields were +; already allocated in report without ID; if so, abort processing with error. + cmp [ebx+report_set.data], 0 + jnz .invalid_report + mov eax, 256*4 + call Kmalloc + test eax, eax + jz .memory_error + mov [ebx+report_set.data], eax + inc [ebx+report_set.numbered] + push edi + mov edi, eax + mov ecx, 256 + xor eax, eax + rep stosd + pop edi +; 6d. Report ID is assigned, report table is allocated, +; get the pointer to the corresponding item in the report table. +.report_set_allocated: + mov ebx, [ebx+report_set.data] + mov ecx, [edi+global_items.report_id] + lea ebx, [ebx+ecx*4] +; 7. If the field group is the first one in the report, +; allocate and initialize report without fields. +.report_ptr_found: +; 7a. Check whether the report has been allocated. + cmp dword [ebx], 0 + jnz .report_allocated +; 7b. Allocate. + movi eax, sizeof.report + call Kmalloc + test eax, eax + jz .memory_error +; 7c. Initialize. + xor edx, edx + lea ecx, [eax+report.first_field] + mov [ebx], eax + mov [eax+report.next], edx + mov [eax+report.size], edx + mov [ecx], edx + mov [eax+report.last_field], ecx + mov [eax+report.top_level_collection], edx + mov ecx, [edi+global_items.report_id] + mov [eax+report.id], ecx +; 7d. Append to the overall list of reports. + movzx edx, [field_type] + lea edx, [last_reports+edx*4] + mov ecx, [edx] + mov [edx], eax + mov [ecx], eax +.report_allocated: + mov ebx, [ebx] +; ebx points to an already existing report; add new field. +; 8. Calculate total size of the group and +; check that the new group would not overflow the report. + mov eax, [edi+global_items.report_size] + mul [edi+global_items.report_count] + mov ecx, [ebx+report.size] + add ecx, eax + cmp ecx, MAX_REPORT_BYTES * 8 + ja .invalid_report +; 9. If there are no usages for this group, this is padding; +; add it's size to total report size and stop processing. + cmp [num_usage_ranges], 0 + jz .padding +; 10. Allocate memory for the group: this includes field group structure +; and additional fields depending on field type. +; See comments in report_field_group structure. + push eax + mov edx, [edi+global_items.report_count] + lea eax, [report_field_group.common_sizeof+edx*4] + test byte [field_data], HID_FIELD_VARIABLE + jnz @f + lea eax, [eax+edx*4] + mov edx, [num_usage_ranges] + lea eax, [eax+edx*sizeof.usage_range+4] +@@: + call Kmalloc + pop edx + test eax, eax + jz .memory_error +; 11. Update report data. +; Field offset is the current report size; +; get the current report size and update report size. +; Also store the pointer to new field in the previous last field +; and update the last field. + mov ecx, [ebx+report.last_field] + xadd [ebx+report.size], edx + mov [ebx+report.last_field], eax + mov [ecx], eax +; 12. Initialize field data: offset was calculated in the previous step, +; copy other characteristics from global_items data, +; calculate .mask and .sign_mask. + mov [eax+report_field_group.offset], edx + xor edx, edx + mov [eax+report_field_group.next], edx + mov [eax+report_field_group.sign_mask], edx + inc edx + mov ecx, [edi+global_items.report_size] + mov [eax+report_field_group.size], ecx + shl edx, cl + cmp [edi+global_items.logical_minimum], 0 + jge .unsigned + shr edx, 1 + mov [eax+report_field_group.sign_mask], edx +.unsigned: + dec edx + mov [eax+report_field_group.mask], edx + mov ecx, [edi+global_items.report_count] + mov [eax+report_field_group.count], ecx + mov ecx, [field_data] + mov [eax+report_field_group.flags], ecx +irps field, logical_minimum logical_maximum physical_minimum physical_maximum unit_exponent unit +\{ + mov ecx, [edi+global_items.\#field] + mov [eax+report_field_group.\#field], ecx +\} +; 13. Update the current collection; nesting collections will be updated by +; end-of-collection handler. + movzx edx, [field_type] +if sizeof.collection_report_set = 16 + shl edx, 4 +else +err Change the code +end if + mov ecx, [cur_collection] + test ecx, ecx + jz .no_collection + lea ecx, [ecx+collection.input+edx] + mov [ecx+collection_report_set.last_report], ebx + mov [ecx+collection_report_set.last_field], eax + cmp [ecx+collection_report_set.first_field], 0 + jnz .no_collection + mov [ecx+collection_report_set.first_report], ebx + mov [ecx+collection_report_set.first_field], eax +.no_collection: +; 14. Transform usage ranges. The target format depends on field type. + test byte [eax+report_field_group.flags], HID_FIELD_VARIABLE + jz .transform_usages_for_array +; For Variable field groups, expand all ranges to array with .count Usages. +; If total number of Usages in all ranges is too large, ignore excessive. +; If total number of Usages in all ranges is too small, duplicate the last +; Usage up to .count Usages (e.g. group of several indicators can have one usage +; "Generic Indicator" assigned to all fields). + mov ecx, [eax+report_field_group.count] + mov ebx, [usage_list] +.next_usage_range_for_variable: + mov edx, [ebx+usage_list_item.first_usage] + push [ebx+usage_list_item.num_usages] +.next_usage_for_variable: + mov [eax+report_field_group.common_sizeof], edx + dec ecx + jz @f + add eax, 4 + inc edx + dec dword [esp] + jnz .next_usage_for_variable + dec edx + inc dword [esp] + cmp [ebx+usage_list_item.next], 0 + jz .next_usage_for_variable + pop edx + mov ebx, [ebx+usage_list_item.next] + jmp .next_usage_range_for_variable +@@: + pop ebx + jmp .zero_local_items +.transform_usages_for_array: +; For Array field groups, leave ranges unexpanded, but recode in the form +; more convenient to value lookup, see comments in report_field_group structure. + mov ecx, [num_usage_ranges] + mov [eax+report_field_group.num_usage_ranges], ecx + and [eax+report_field_group.num_values_prev], 0 + mov ecx, [usage_list] + xor ebx, ebx +@@: + mov edx, [ecx+usage_list_item.first_usage] + mov [eax+report_field_group.usages+usage_range.offset], ebx + add ebx, [ecx+usage_list_item.num_usages] + jc .invalid_report + mov [eax+report_field_group.usages+usage_range.first_usage], edx + add eax, sizeof.usage_range + mov ecx, [ecx+usage_list_item.next] + test ecx, ecx + jnz @b + mov [eax+report_field_group.usages], ebx +; New field is initialized. + jmp .zero_local_items +.padding: + mov [ebx+report.size], ecx + jmp .zero_local_items +; Create a new collection, nested in the current one. +.collection: +; Actions are quite straightforward: +; allocate, zero-initialize, update parent, if there is one, +; make it current. + movi eax, sizeof.collection + call Kmalloc + test eax, eax + jz .memory_error + push eax edi + movi ecx, sizeof.collection / 4 + xchg edi, eax + xor eax, eax + rep stosd + pop edi eax + mov edx, [cur_collection] + mov [eax+collection.parent], edx + lea ecx, [last_collection] + test edx, edx + jz .no_parent + lea ecx, [edx+collection.last_child] +.no_parent: + mov edx, [ecx] + mov [ecx], eax + mov [edx], eax + lea ecx, [eax+collection.first_child] +; In theory, there must be at least one usage. +; In practice, some nested collections don't have any. Use zero in this case. + mov edx, [usage_list] + test edx, edx + jz @f + mov edx, [edx+usage_list_item.first_usage] +@@: + mov [eax+collection.last_child], ecx + mov [eax+collection.type], ebx + mov [eax+collection.usage], edx + mov [cur_collection], eax + jmp .zero_local_items +; Close the current collection. +.end_collection: +; There must be an opened collection. + mov eax, [cur_collection] + test eax, eax + jz .invalid_report +; Make parent collection the current one. + mov edx, [eax+collection.parent] + mov [cur_collection], edx +; Add field range of the closing collection to field range for nesting collection, +; if there is one. + test edx, edx + jz .zero_local_items + push 3 ; for each type: input, output, feature +.update_ranges: + mov ecx, [eax+collection.input.last_report] + test ecx, ecx + jz .no_fields + mov [edx+collection.input.last_report], ecx + mov ecx, [eax+collection.input.last_field] + mov [edx+collection.input.last_field], ecx + cmp [edx+collection.input.first_report], 0 + jnz .no_fields + mov ecx, [eax+collection.input.first_report] + mov [edx+collection.input.first_report], ecx + mov ecx, [eax+collection.input.first_field] + mov [edx+collection.input.first_field], ecx +.no_fields: + add eax, sizeof.collection_report_set + add edx, sizeof.collection_report_set + dec dword [esp] + jnz .update_ranges + pop eax + jmp .zero_local_items +; ------------------------------- Global items -------------------------------- +.parse_global: + cmp eax, .num_global_items + jae .item_parsed + jmp dword [.global_jumps+eax*4] +; For most global items, just store the value in the current global_items structure. +; Note 1: Usage Page will be used for upper word of Usage[| Minimum|Maximum], so +; shift it in advance. +; Note 2: the HID specification allows both signed and unsigned values for +; logical and physical minimum/maximum, but does not give a method to distinguish. +; Thus, hope that minimum comes first, parse the minimum as signed value always, +; if it is less than zero, assume signed values, otherwise assume unsigned values. +; This covers both common cases Minimum(0)/Maximum(FF) and Minimum(-7F)/Maximum(7F). +; Note 3: zero value for Report ID is forbidden by the HID specification. +; It is quite convenient, we use report_id == 0 for reports without ID. +.usage_page: + shl ebx, 16 + mov [edi+global_items.usage_page], ebx + jmp .item_parsed +.logical_minimum: + mov [edi+global_items.logical_minimum], ecx + jmp .item_parsed +.logical_maximum: + cmp [edi+global_items.logical_minimum], 0 + jge @f + mov ebx, ecx +@@: + mov [edi+global_items.logical_maximum], ebx + jmp .item_parsed +.physical_minimum: + mov [edi+global_items.physical_minimum], ecx + jmp .item_parsed +.physical_maximum: + cmp [edi+global_items.physical_maximum], 0 + jge @f + mov ebx, ecx +@@: + mov [edi+global_items.physical_maximum], ebx + jmp .item_parsed +.unit_exponent: + mov [edi+global_items.unit_exponent], ecx + jmp .item_parsed +.unit: + mov [edi+global_items.unit], ebx + jmp .item_parsed +.report_size: + mov [edi+global_items.report_size], ebx + jmp .item_parsed +.report_id: + test ebx, ebx + jz .invalid_report + cmp ebx, 0x100 + jae .invalid_report + mov [edi+global_items.report_id], ebx + jmp .item_parsed +.report_count: + mov [edi+global_items.report_count], ebx + jmp .item_parsed +; Two special global items: Push/Pop. +.push: +; For Push, allocate new global_items structure, +; initialize from the current one and make it current. + movi eax, sizeof.global_items + call Kmalloc + test eax, eax + jz .memory_error + push esi eax + movi ecx, sizeof.global_items / 4 + mov esi, edi + xchg eax, edi + rep movsd + pop edi esi + mov [edi+global_items.next], eax + jmp .item_parsed +.pop: +; For Pop, restore the last global_items structure and free the current one. + mov eax, [edi+global_items.next] + test eax, eax + jz .invalid_report + push eax + xchg eax, edi + call Kfree + pop edi + jmp .item_parsed +; -------------------------------- Local items -------------------------------- +.parse_local: + cmp eax, .num_local_items + jae .item_parsed + jmp dword [.local_jumps+eax*4] +.usage: +; Usage tag. +; If length is 0, 1, 2 bytes, append the global item Usage Page. + cmp [cur_item_size], 2 + ja @f + or ebx, [edi+global_items.usage_page] +@@: +; If inside Delimiter(), ignore everything except the first tag. + cmp [delimiter_depth], 0 + jz .usage.write + inc [usage_variant] + cmp [usage_variant], 1 + jnz .item_parsed +.usage.write: +; Add new range with start = item data and length = 1. + mov [usage_minimum], ebx + push 1 +.new_usage: + movi eax, sizeof.usage_list_item + call Kmalloc + pop edx + test eax, eax + jz .memory_error + inc [num_usage_ranges] + mov ecx, [usage_minimum] + and [eax+usage_list_item.next], 0 + mov [eax+usage_list_item.first_usage], ecx + mov [eax+usage_list_item.num_usages], edx + mov ecx, [usage_tail] + mov [usage_tail], eax + mov [ecx], eax + jmp .item_parsed +.usage_minimum: +; Usage Minimum tag. Just store in the local var. +; If length is 0, 1, 2 bytes, append the global item Usage Page. + cmp [cur_item_size], 2 + ja @f + or ebx, [edi+global_items.usage_page] +@@: + mov [usage_minimum], ebx + jmp .item_parsed +.usage_maximum: +; Usage Maximum tag. +; If length is 0, 1, 2 bytes, append the global item Usage Page. + cmp [cur_item_size], 2 + ja @f + or ebx, [edi+global_items.usage_page] +@@: +; Meaningless inside Delimiter(). + cmp [delimiter_depth], 0 + jnz .invalid_report +; Add new range with start = saved Usage Minimum and +; length = Usage Maximum - Usage Minimum + 1. + sub ebx, [usage_minimum] + inc ebx + push ebx + jmp .new_usage +.delimiter: +; Delimiter tag. + test ebx, ebx + jz .delimiter.close +; Delimiter(Opened). +; Store that we are inside Delimiter(), +; say a warning that only preferred Usage will be used. + cmp [delimiter_depth], 0 + jnz .invalid_report + inc [delimiter_depth] + push esi + mov esi, delimiter_note + call SysMsgBoardStr + pop esi + jmp .item_parsed +.delimiter.close: +; Delimiter(Closed). +; Store that we are not inside Delimiter() anymore. + dec [delimiter_depth] + js .invalid_report + and [usage_variant], 0 + jmp .item_parsed +.parse_reserved: +; Ignore reserved items, except that tag 0xFE means long item +; with first data byte = length of additional data, +; second data byte = long item tag. No long items are defined yet, +; so just skip them. + cmp eax, 0xF + jnz .item_parsed + cmp [cur_item_size], 2 + jnz .item_parsed + movzx ecx, bl + add esi, ecx + cmp esi, [length] + ja .invalid_report +.item_parsed: + cmp esi, [length] + jb .fetch_next_item +.parse_end: +;-------------------------------- End of parsing ------------------------------ +; If there are opened collections, it is invalid report. + cmp [cur_collection], 0 + jnz .invalid_report +; There must be at least one input field. + mov eax, [calldata] + add eax, hid_data.input.first_report + cmp [last_reports+0*4], eax + jz .invalid_report +; Everything is ok. + inc [report_ok] + jmp .end +.memory_error: + mov esi, nomemory_msg +.end_str: + call SysMsgBoardStr +.end: +; Free all global_items structures. + test edi, edi + jz @f + push [edi+global_items.next] + xchg eax, edi + call Kfree + pop edi + jmp .end +@@: +; Free the last Usage list, if any. + mov eax, [usage_list] +@@: + test eax, eax + jz @f + push [eax+usage_list_item.next] + call Kfree + pop eax + jmp @b +@@: +} + +; Assign drivers to top-level HID collections. +; The caller should provide ebx = pointer to hid_data and a local variable +; [has_driver], it will be initialized with 0 if no driver is present. +macro postprocess_descr +{ +postprocess_report_label: +; Assign drivers to top-level collections. +; Use mouse driver for Usage(GenericDesktop:Mouse), +; use keyboard driver for Usage(GenericDesktop:Keyboard) +; and Usage(GenericDesktop:Keypad) +; 1. Prepare for the loop: get the pointer to the first collection, +; store that no drivers were assigned yet. + mov edi, [ebx+hid_data.first_collection] +if ~HID_DUMP_UNCLAIMED + mov [has_driver], 0 +end if +.next_collection: +; 2. Test whether there is a collection to test; if no, break from the loop. + test edi, edi + jz .postprocess_done +; 3. Get pointer to driver callbacks depending on [collection.usage]. +; If [collection.usage] is unknown, use default driver if HID_DUMP_UNCLAIMED +; and do not assign a driver otherwise. + mov esi, mouse_driver + cmp [edi+collection.usage], USAGE_GD_MOUSE + jz .has_driver + mov esi, keyboard_driver + cmp [edi+collection.usage], USAGE_GD_KEYBOARD + jz .has_driver + cmp [edi+collection.usage], USAGE_GD_KEYPAD + jz .has_driver +if HID_DUMP_UNCLAIMED + mov esi, default_driver +else + xor esi, esi +end if +; 4. If no driver is assigned (possible only if not HID_DUMP_UNCLAIMED), +; go to 7 with driver data = 0; +; other code uses this as a sign that driver callbacks should not be called. +.has_driver: + xor eax, eax +if ~HID_DUMP_UNCLAIMED + test esi, esi + jz .set_driver +end if +; 5. Notify the driver about new device. + call [esi+hid_driver_callbacks.add_device] +; 6. If the driver has returned non-zero driver data, +; store that is an assigned driver. +; Otherwise, if HID_DUMP_UNCLAIMED, try to assign the default driver. +if HID_DUMP_UNCLAIMED + test eax, eax + jnz .set_driver + mov esi, default_driver + call [esi+hid_driver_callbacks.add_device] +else + test eax, eax + jz @f + mov [has_driver], 1 + jmp .set_driver +@@: + xor esi, esi +end if +.set_driver: +; 7. Store driver data. If a driver is assigned, copy driver callbacks. + mov [edi+collection.driver_data], eax + test esi, esi + jz @f + push edi + lodsd ; skip hid_driver_callbacks.add_device + add edi, collection.callbacks +repeat sizeof.hid_driver_active_callbacks / 4 + movsd +end repeat + pop edi +@@: +; 8. Store pointer to the collection in all input reports belonging to it. +; Note that the HID spec requires that reports should not cross top-level collections. + mov eax, [edi+collection.input.first_report] + test eax, eax + jz .reports_processed +.next_report: + mov [eax+report.top_level_collection], edi + cmp eax, [edi+collection.input.last_report] + mov eax, [eax+report.next] + jnz .next_report +.reports_processed: + mov edi, [edi+collection.next] + jmp .next_collection +.postprocess_done: +} + +; Cleanup all resources allocated during parse_descr and postprocess_descr. +; Called when the corresponding device is disconnected +; with ebx = pointer to hid_data. +macro hid_cleanup +{ +; 1. Notify all assigned drivers about disconnect. +; Loop over all top-level collections and call callbacks.disconnect, +; if a driver is assigned. + mov esi, [ebx+hid_data.first_collection] +.notify_drivers: + test esi, esi + jz .notify_drivers_done + mov edi, [esi+collection.driver_data] + test edi, edi + jz @f + call [esi+collection.callbacks.disconnect] +@@: + mov esi, [esi+collection.next] + jmp .notify_drivers +.notify_drivers_done: +; 2. Free all collections. + mov esi, [ebx+hid_data.first_collection] +.free_collections: + test esi, esi + jz .collections_done +; If a collection has childen, make it forget about them, +; kill all children; after last child is killed, return to +; the collection as a parent; this time, it will appear +; as childless, so it will be killed after children. + mov eax, [esi+collection.first_child] + test eax, eax + jz .no_children + and [esi+collection.first_child], 0 + xchg esi, eax + jmp .free_collections +.no_children: +; If a collection has no children (maybe there were no children at all, +; maybe all children were already killed), kill it and proceed either to +; next sibling (if any) or to the parent. + mov eax, [esi+collection.next] + test eax, eax + jnz @f + mov eax, [esi+collection.parent] +@@: + xchg eax, esi + call Kfree + jmp .free_collections +.collections_done: +; 3. Free all three report sets. + push 3 + lea esi, [ebx+hid_data.input] +; For every report set, loop over all reports, +; for every report free all field groups, then free report itself. +; When all reports in one set have been freed, free also report list table, +; if there is one (reports are numbered). +.report_set_loop: + mov edi, [esi+report_set.first_report] +.report_loop: + test edi, edi + jz .report_done + mov eax, [edi+report.first_field] +.field_loop: + test eax, eax + jz .field_done + push [eax+report_field_group.next] + call Kfree + pop eax + jmp .field_loop +.field_done: + mov eax, [edi+report.next] + xchg eax, edi + call Kfree + jmp .report_loop +.report_done: + cmp [esi+report_set.numbered], 0 + jz @f + mov eax, [esi+report_set.data] + call Kfree +@@: + add esi, sizeof.report_set + dec dword [esp] + jnz .report_set_loop + pop eax +} + +; Helper for parse_input. Extracts value of one field. +; in: esi -> report_field_group +; in: eax = offset in bits from report start +; in: report -> report data +; out: edx = value +; Note: it can read one dword past report data. +macro extract_field_value report +{ + mov ecx, eax + shr eax, 5 + shl eax, 2 + add eax, report + and ecx, 31 + mov edx, [eax] + mov eax, [eax+4] + shrd edx, eax, cl + mov ecx, [esi+report_field_group.sign_mask] + and ecx, edx + and edx, [esi+report_field_group.mask] + sub edx, ecx +} + +; Local variables for parse_input. +macro parse_input_locals +{ +count_inside_group dd ? +; Number of fields left in the current field. +field_offset dd ? +; Offset of the current field from report start, in bits. +field_range_size dd ? +; Size of range with valid values, Logical Maximum - Logical Minimum + 1. +cur_usage dd ? +; Pointer to current usage for Variable field groups. +num_values dd ? +; Number of values in the current instantiation of Array field group. +values_base dd ? +; Pointer to memory allocated for array with current values. +values_prev dd ? +; Pointer to memory allocated for array with previous values. +values_cur_ptr dd ? +; Pointer to the next value in [values_base] array. +values_end dd ? +; End of data in array with current values. +values_prev_ptr dd ? +; Pointer to the next value in [values_prev_ptr] array. +values_prev_end dd ? +; End of data in array with previous values. +} + +; Parse input report. The caller should provide esi = pointer to report, +; local variables parse_input_locals and [buffer] = report data. +macro parse_input +{ +; 1. Ignore the report if there is no driver for it. + mov ebx, [esi+report.top_level_collection] + mov edi, [ebx+collection.driver_data] + test edi, edi + jz .done +; 2. Notify the driver that a new packet arrived. + call [ebx+collection.callbacks.begin_packet] +; Loop over all field groups. +; Report without fields is meaningless, but theoretically possible: +; parse_descr does not create reports of zero size, but +; a report can consist of "padding" fields without usages and have +; no real fields. + mov esi, [esi+report.first_field] + test esi, esi + jz .packet_processed +.field_loop: +; 3. Prepare for group handling: initialize field offset, fields count +; and size of range for valid values. + mov eax, [esi+report_field_group.offset] + mov [field_offset], eax + mov ecx, [esi+report_field_group.count] + mov [count_inside_group], ecx + mov eax, [esi+report_field_group.logical_maximum] + inc eax + sub eax, [esi+report_field_group.logical_minimum] + mov [field_range_size], eax +; 4. Select handler. Variable and Array groups are handled entirely differently; +; for Variable groups, advance to 5, for Array groups, go to 6. + test byte [esi+report_field_group.flags], HID_FIELD_VARIABLE + jz .array_field +; 5. Variable groups. They are simple. Loop over all .count fields, +; for every field extract the value and get the next usage, +; if the value is within valid range, call the driver. + lea eax, [esi+report_field_group.common_sizeof] + mov [cur_usage], eax +.variable_data_loop: + mov eax, [field_offset] + extract_field_value [buffer] ; -> edx + mov ecx, [cur_usage] + mov ecx, [ecx] + call [ebx+collection.callbacks.input_field] + add [cur_usage], 4 + mov eax, [esi+report_field_group.size] + add [field_offset], eax + dec [count_inside_group] + jnz .variable_data_loop +; Variable group is processed; go to 12. + jmp .field_done +.array_field: +; Array groups. They are complicated. +; 6. Array group: extract all values in one array. +; memory was allocated during group creation, use it +; 6a. Prepare: get data pointer, initialize num_values with zero. + mov eax, [esi+report_field_group.num_usage_ranges] + lea edx, [esi+report_field_group.usages+eax*sizeof.usage_range+4] + mov eax, [esi+report_field_group.count] + mov [values_cur_ptr], edx + mov [values_base], edx + lea edx, [edx+ecx*4] + mov [values_prev], edx + mov [values_prev_ptr], edx + mov [num_values], 0 +; 6b. Start loop for every field. Note that there must be at least one field, +; parse_descr does not allow .count == 0. +.array_getval_loop: +; 6c. Extract the value of the current field. + mov eax, [field_offset] + extract_field_value [buffer] ; -> edx +; 6d. Transform the value to the usage with binary search in array of +; usage_ranges. started at [esi+report_field_group.usages] +; having [esi+report_field_group.num_usage_ranges] items. +; Ignore items outside of valid range. + sub edx, [esi+report_field_group.logical_minimum] + cmp edx, [field_range_size] + jae .array_skip_item +; If there are too few usages, use last of them. + mov ecx, [esi+report_field_group.num_usage_ranges] ; upper bound + xor eax, eax ; lower bound + cmp edx, [esi+report_field_group.usages+ecx*sizeof.usage_range+usage_range.offset] + jae .array_last_usage +; loop invariant: usages[eax].offset <= edx < usages[ecx].offset +.array_find_usage: + lea edi, [eax+ecx] + shr edi, 1 + cmp edi, eax + jz .array_found_usage_range + cmp edx, [esi+report_field_group.usages+edi*sizeof.usage_range+usage_range.offset] + jae .update_low + mov ecx, edi + jmp .array_find_usage +.update_low: + mov eax, edi + jmp .array_find_usage +.array_last_usage: + lea eax, [ecx-1] + mov edx, [esi+report_field_group.usages+ecx*sizeof.usage_range+usage_range.offset] + dec edx +.array_found_usage_range: + sub edx, [esi+report_field_group.usages+eax*sizeof.usage_range+usage_range.offset] + add edx, [esi+report_field_group.usages+eax*sizeof.usage_range+usage_range.first_usage] +; 6e. Store the usage, advance data pointer, continue loop started at 6b. + mov eax, [values_cur_ptr] + mov [eax], edx + add [values_cur_ptr], 4 + inc [num_values] +.array_skip_item: + mov eax, [esi+report_field_group.size] + add [field_offset], eax + dec [count_inside_group] + jnz .array_getval_loop +; 7. Array group: ask driver about array overflow. +; If driver says that the array is invalid, stop processing this group +; (in particular, do not update previous values). + mov ecx, [num_values] + test ecx, ecx + jz .duplicates_removed + mov edx, [values_base] + mov edi, [ebx+collection.driver_data] + call [ebx+collection.callbacks.array_overflow?] + jnc .field_done +; 8. Array group: sort the array with current values. + push esi + mov ecx, [num_values] + mov edx, [values_base] + call sort + pop esi +; 9. Array group: remove duplicates. + cmp [num_values], 1 + jbe .duplicates_removed + mov eax, [values_base] + mov edx, [eax] + add eax, 4 + mov ecx, eax +.duplicates_loop: + cmp edx, [eax] + jz @f + mov edx, [eax] + mov [ecx], edx + add ecx, 4 +@@: + add eax, 4 + cmp eax, [values_cur_ptr] + jb .duplicates_loop + mov [values_cur_ptr], ecx + sub ecx, [values_base] + shr ecx, 2 + mov [num_values], ecx +.duplicates_removed: +; 10. Array group: compare current and previous values, +; call driver for differences. + mov edi, [ebx+collection.driver_data] + mov eax, [values_cur_ptr] + mov [values_end], eax + mov eax, [values_base] + mov [values_cur_ptr], eax + mov eax, [esi+report_field_group.num_values_prev] + shl eax, 2 + add eax, [values_prev] + mov [values_prev_end], eax +.find_common: + mov eax, [values_cur_ptr] + cmp eax, [values_end] + jae .cur_done + mov ecx, [eax] + mov eax, [values_prev_ptr] + cmp eax, [values_prev_end] + jae .prev_done + mov edx, [eax] + cmp ecx, edx + jb .advance_cur + ja .advance_prev +; common item in both arrays; ignore + add [values_cur_ptr], 4 + add [values_prev_ptr], 4 + jmp .find_common +.advance_cur: +; item is present in current array but not in previous; +; call the driver with value = 1 + add [values_cur_ptr], 4 + mov edx, 1 + call [ebx+collection.callbacks.input_field] + jmp .find_common +.advance_prev: +; item is present in previous array but not in current; +; call the driver with value = 0 + add [values_prev_ptr], 4 + mov ecx, edx + xor edx, edx + call [ebx+collection.callbacks.input_field] + jmp .find_common +.prev_done: +; for all items which are left in current array +; call the driver with value = 1 + mov eax, [values_cur_ptr] +@@: + add [values_cur_ptr], 4 + mov ecx, [eax] + mov edx, 1 + call [ebx+collection.callbacks.input_field] + mov eax, [values_cur_ptr] + cmp eax, [values_end] + jb @b + jmp .copy_array +.cur_done: +; for all items which are left in previous array +; call the driver with value = 0 + mov eax, [values_prev_ptr] + add [values_prev_ptr], 4 + cmp eax, [values_prev_end] + jae @f + mov ecx, [eax] + xor edx, edx + call [ebx+collection.callbacks.input_field] + jmp .cur_done +@@: +.copy_array: +; 11. Array group: copy current values to previous values. + push esi edi + mov ecx, [num_values] + mov [esi+report_field_group.num_values_prev], ecx + mov esi, [values_base] + mov edi, [values_prev] + rep movsd + pop edi esi +; 12. Field group is processed. Repeat with the next group, if any. +.field_done: + mov esi, [esi+report_field_group.next] + test esi, esi + jnz .field_loop +.packet_processed: +; 13. Packet is processed, notify the driver. + call [ebx+collection.callbacks.end_packet] +} diff --git a/kernel/trunk/drivers/usbhid/sort.inc b/kernel/trunk/drivers/usbhid/sort.inc new file mode 100644 index 0000000000..3112e01954 --- /dev/null +++ b/kernel/trunk/drivers/usbhid/sort.inc @@ -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 diff --git a/kernel/trunk/drivers/usbhid/unclaimed.inc b/kernel/trunk/drivers/usbhid/unclaimed.inc new file mode 100644 index 0000000000..6f69e9c2f5 --- /dev/null +++ b/kernel/trunk/drivers/usbhid/unclaimed.inc @@ -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 diff --git a/kernel/trunk/drivers/usbhid/usbhid.asm b/kernel/trunk/drivers/usbhid/usbhid.asm new file mode 100644 index 0000000000..2b3974a33d --- /dev/null +++ b/kernel/trunk/drivers/usbhid/usbhid.asm @@ -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