Subversion Repositories Kolibri OS

Compare Revisions

No changes between revisions

Regard whitespace Rev 5042 → Rev 5051

/drivers/usb/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, 2Bh, 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 56h,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
invoke 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
invoke 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:
invoke 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
invoke CancelTimerHS, [edi+keyboard_device_data.timer]
@@:
; 2. Unregister keyboard in the kernel.
invoke 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
invoke CancelTimerHS, eax
@@:
; 1h. Start the new autorepeat timer with 250 ms initial delay
; and 50 ms subsequent delays.
invoke 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
invoke 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
invoke SetKeyboardData
pop ecx
@@:
invoke 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]
invoke 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
invoke 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.
invoke 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]
invoke 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
invoke Kfree
ret 20
endp
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/usbhid/mouse.inc
0,0 → 1,163
; 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
hwheel dd ?
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
invoke 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
invoke 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
mov [edi+mouse_device_data.hwheel], 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
cmp ecx, 0xC0238
jz .hwheel
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 .absolute_x
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 .absolute_y
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
.hwheel:
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE
jz .unclaimed
mov [edi+mouse_device_data.hwheel], edx
ret
.absolute_x:
mov [edi+mouse_device_data.dx], edx
or [edi+mouse_device_data.buttons], 0x80000000
ret
.absolute_y:
mov [edi+mouse_device_data.dy], edx
or [edi+mouse_device_data.buttons], 0x40000000
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.
invoke SetMouseData, \
[edi+mouse_device_data.buttons], \
[edi+mouse_device_data.dx], \
[edi+mouse_device_data.dy], \
[edi+mouse_device_data.wheel], \
[edi+mouse_device_data.hwheel]
ret
endp
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/usbhid/report.inc
0,0 → 1,1444
; 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
invoke 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]
invoke 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
invoke 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
invoke 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]
@@:
invoke 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
invoke 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
invoke 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
invoke 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
invoke 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
invoke 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:
invoke SysMsgBoardStr
.end:
; Free all global_items structures.
test edi, edi
jz @f
push [edi+global_items.next]
xchg eax, edi
invoke 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]
invoke 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_POINTER
jz .has_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
invoke 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]
invoke Kfree
pop eax
jmp .field_loop
.field_done:
mov eax, [edi+report.next]
xchg eax, edi
invoke Kfree
jmp .report_loop
.report_done:
cmp [esi+report_set.numbered], 0
jz @f
mov eax, [esi+report_set.data]
invoke 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]
}
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/usbhid/usbhid.asm
0,0 → 1,552
; standard driver stuff; version of driver model = 5
format PE DLL native 0.05
entry START
 
DEBUG = 1
 
; this is for DEBUGF macro from 'fdo.inc'
__DEBUG__ = 1
__DEBUG_LEVEL__ = 1
 
include '../../struct.inc'
 
; Compile-time settings.
; If set, the code will dump all descriptors as they are read to the debug board.
USB_DUMP_DESCRIPTORS = 1
; If set, the code will dump any unclaimed input to the debug board.
HID_DUMP_UNCLAIMED = 1
 
; USB constants
DEVICE_DESCR_TYPE = 1
CONFIG_DESCR_TYPE = 2
STRING_DESCR_TYPE = 3
INTERFACE_DESCR_TYPE = 4
ENDPOINT_DESCR_TYPE = 5
DEVICE_QUALIFIER_DESCR_TYPE = 6
 
CONTROL_PIPE = 0
ISOCHRONOUS_PIPE = 1
BULK_PIPE = 2
INTERRUPT_PIPE = 3
 
; USB HID constants
HID_DESCR_TYPE = 21h
REPORT_DESCR_TYPE = 22h
PHYSICAL_DESCR_TYPE = 23h
 
; USB structures
struct config_descr
bLength db ?
bDescriptorType db ?
wTotalLength dw ?
bNumInterfaces db ?
bConfigurationValue db ?
iConfiguration db ?
bmAttributes db ?
bMaxPower db ?
ends
 
struct interface_descr
bLength db ?
bDescriptorType db ?
bInterfaceNumber db ?
bAlternateSetting db ?
bNumEndpoints db ?
bInterfaceClass db ?
bInterfaceSubClass db ?
bInterfaceProtocol db ?
iInterface db ?
ends
 
struct endpoint_descr
bLength db ?
bDescriptorType db ?
bEndpointAddress db ?
bmAttributes db ?
wMaxPacketSize dw ?
bInterval db ?
ends
 
; USB HID structures
struct hid_descr
bLength db ?
bDescriptorType db ?
bcdHID dw ?
bCountryCode db ?
bNumDescriptors db ?
base_sizeof rb 0
; now two fields are repeated .bNumDescriptors times:
subDescriptorType db ?
subDescriptorLength dw ?
ends
 
; Include macro for parsing report descriptors/data.
macro workers_globals
{}
include 'report.inc'
 
; Driver data for all devices
struct usb_device_data
hid hid_data ; data of HID layer
epdescr dd ? ; endpoint descriptor
hiddescr dd ? ; HID descriptor
interface_number dd ? ; copy of interface_descr.bInterfaceNumber
configpipe dd ? ; config pipe handle
intpipe dd ? ; interrupt pipe handle
input_transfer_size dd ? ; input transfer size
input_buffer dd ? ; buffer for input transfers
control rb 8 ; control packet to device
ends
 
section '.flat' code readable writable executable
include '../../macros.inc'
include '../../proc32.inc'
include '../../peimport.inc'
include '../../fdo.inc'
; The start procedure.
proc START
virtual at esp
dd ? ; return address
.reason dd ?
.cmdline dd ?
end virtual
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
; If not, return 0.
xor eax, eax ; initialize return value
cmp [.reason], 1 ; compare the argument
jnz .nothing
; 2. Register self as a USB driver.
; The name is my_driver = 'usbhid'; IOCTL interface is not supported;
; usb_functions is an offset of a structure with callback functions.
invoke RegUSBDriver, my_driver, eax, usb_functions
; 3. Return the returned value of RegUSBDriver.
.nothing:
ret
endp
 
; This procedure is called when new HID device is detected.
; It initializes the device.
proc AddDevice
push ebx esi edi ; save used registers to be stdcall
virtual at esp
rd 3 ; saved registers
dd ? ; return address
.config_pipe dd ?
.config_descr dd ?
.interface dd ?
end virtual
DEBUGF 1,'K : USB HID device detected\n'
; 1. Allocate memory for device data.
movi eax, sizeof.usb_device_data
invoke Kmalloc
test eax, eax
jnz @f
mov esi, nomemory_msg
invoke SysMsgBoardStr
jmp .return0
@@:
; zero-initialize it
mov edi, eax
xchg eax, ebx
xor eax, eax
movi ecx, sizeof.usb_device_data / 4
rep stosd
mov edx, [.interface]
; HID devices use one IN interrupt endpoint for polling the device
; and an optional OUT interrupt endpoint. We do not use the later,
; but must locate the first. Look for the IN interrupt endpoint.
; Also, look for the HID descriptor; according to HID spec, it must be
; located before endpoint descriptors.
; 2. Get the upper bound of all descriptors' data.
mov eax, [.config_descr]
movzx ecx, [eax+config_descr.wTotalLength]
add eax, ecx
; 3. Loop over all descriptors until
; either end-of-data reached - this is fail
; or interface descriptor found - this is fail, all further data
; correspond to that interface
; or endpoint descriptor for IN endpoint is found
; (HID descriptor must be located before the endpoint descriptor).
; 3a. Loop start: edx points to the interface descriptor.
.lookep:
; 3b. Get next descriptor.
movzx ecx, byte [edx] ; the first byte of all descriptors is length
test ecx, ecx
jz .cfgerror
add edx, ecx
; 3c. Check that at least two bytes are readable. The opposite is an error.
inc edx
cmp edx, eax
jae .cfgerror
dec edx
; 3d. Check that this descriptor is not interface descriptor. The opposite is
; an error.
cmp [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
jz .cfgerror
; 3e. For HID descriptor, proceed to 4.
; For endpoint descriptor, go to 5.
; For other descriptors, continue the loop.
; Note: bDescriptorType is in the same place in all descriptors.
cmp [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
jz .foundep
cmp [edx+endpoint_descr.bDescriptorType], HID_DESCR_TYPE
jnz .lookep
; 4a. Check that the descriptor contains all required data and all data are
; readable. The opposite is an error.
movzx ecx, [edx+hid_descr.bLength]
cmp ecx, hid_descr.base_sizeof + 3
jb .cfgerror
add ecx, edx
cmp ecx, eax
ja .cfgerror
; 4b. Store the pointer in usb_device_data structure for further references.
mov [ebx+usb_device_data.hiddescr], edx
; 4c. Continue the loop.
jmp .lookep
.foundep:
; 5a. Check that the descriptor contains all required data and all data are
; readable. The opposite is an error.
cmp byte [edx+endpoint_descr.bLength], sizeof.endpoint_descr
jb .cfgerror
lea ecx, [edx+sizeof.endpoint_descr]
cmp ecx, eax
jbe @f
; 6. An error occured during processing endpoint descriptor.
.cfgerror:
; 6a. Print a message.
mov esi, invalid_config_descr_msg
invoke SysMsgBoardStr
; 6b. Free memory allocated for device data.
.free:
xchg eax, ebx
invoke Kfree
.return0:
; 6c. Return an error.
xor eax, eax
.nothing:
pop edi esi ebx ; restore used registers to be stdcall
ret 12
@@:
; 5b. If this is not IN interrupt endpoint, ignore it and continue the loop.
test [edx+endpoint_descr.bEndpointAddress], 80h
jz .lookep
mov cl, [edx+endpoint_descr.bmAttributes]
and cl, 3
cmp cl, INTERRUPT_PIPE
jnz .lookep
; 5c. Store the pointer in usb_device_data structure for futher references.
mov [ebx+usb_device_data.epdescr], edx
; 5d. Check that HID descriptor was found. If not, go to 6.
cmp [ebx+usb_device_data.hiddescr], 0
jz .cfgerror
.descriptors_found:
; 6. Configuration descriptor seems to be ok.
; Send SET_IDLE command disabling auto-repeat feature (it is quite useless)
; and continue configuring in SET_IDLE callback.
lea edx, [ebx+usb_device_data.control]
mov eax, [.interface]
mov dword [edx], 21h + \ ; Class-specific request to Interface
(0Ah shl 8) + \ ; SET_IDLE
(0 shl 16) + \ ; apply to all input reports
(0 shl 24) ; disable auto-repeat
movzx eax, [eax+interface_descr.bInterfaceNumber]
mov [ebx+usb_device_data.interface_number], eax
mov [edx+4], eax ; set interface number, zero length
mov eax, [.config_pipe]
mov [ebx+usb_device_data.configpipe], eax
xor ecx, ecx
invoke USBControlTransferAsync, eax, edx, ecx, ecx, idle_set, ebx, ecx
; 7. Return pointer to usb_device_data.
xchg eax, ebx
jmp .nothing
endp
 
; This procedure is called by USB stack when SET_IDLE request initiated by
; AddDevice is completed, either successfully or unsuccessfully.
proc idle_set
push ebx esi ; save used registers to be stdcall
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
; Ignore status. Support for SET_IDLE is optional, so the device is free to
; STALL the request; config pipe should remain functional without explicit cleanup.
mov ebx, [.calldata]
; 1. HID descriptor contains length of Report descriptor. Parse it.
mov esi, [ebx+usb_device_data.hiddescr]
movzx ecx, [esi+hid_descr.bNumDescriptors]
lea eax, [hid_descr.base_sizeof+ecx*3]
cmp eax, 100h
jae .cfgerror
cmp al, [esi+hid_descr.bLength]
jb .cfgerror
.look_report:
dec ecx
js .cfgerror
cmp [esi+hid_descr.subDescriptorType], REPORT_DESCR_TYPE
jz .found_report
add esi, 3
jmp .look_report
.cfgerror:
mov esi, invalid_config_descr_msg
.abort_with_msg:
invoke SysMsgBoardStr
jmp .nothing
.found_report:
; 2. Send request for the Report descriptor.
; 2a. Allocate memory.
movzx eax, [esi+hid_descr.subDescriptorLength]
test eax, eax
jz .cfgerror
push eax
invoke Kmalloc
pop ecx
; If failed, say a message and stop initialization.
mov esi, nomemory_msg
test eax, eax
jz .abort_with_msg
; 2b. Submit the request.
xchg eax, esi
lea edx, [ebx+usb_device_data.control]
mov eax, [ebx+usb_device_data.interface_number]
mov dword [edx], 81h + \ ; Standard request to Interface
(6 shl 8) + \ ; GET_DESCRIPTOR
(0 shl 16) + \ ; descriptor index: there is only one report descriptor
(REPORT_DESCR_TYPE shl 24); descriptor type
mov [edx+4], ax ; Interface number
mov [edx+6], cx ; descriptor length
invoke USBControlTransferAsync, [ebx+usb_device_data.configpipe], \
edx, esi, ecx, got_report, ebx, 0
; 2c. If failed, free the buffer and stop initialization.
test eax, eax
jnz .nothing
xchg eax, esi
invoke Kfree
.nothing:
pop esi ebx ; restore used registers to be stdcall
ret 20
endp
 
; This procedure is called by USB stack when the report descriptor queried
; by idle_set is completed, either successfully or unsuccessfully.
proc got_report stdcall uses ebx esi edi, pipe, status, buffer, length, calldata
locals
parse_descr_locals
if ~HID_DUMP_UNCLAIMED
has_driver db ?
rb 3
end if
endl
; 1. Check the status; if the request has failed, say something to the debug board
; and stop initialization.
cmp [status], 0
jnz .generic_fail
; 2. Subtract size of setup packet from the total length;
; the rest is length of the descriptor, and it must be nonzero.
sub [length], 8
ja .has_something
.generic_fail:
push esi
mov esi, reportfail
invoke SysMsgBoardStr
pop esi
jmp .exit
.has_something:
; 3. Process descriptor.
; 3a. Dump it to the debug board, if enabled in compile-time setting.
if USB_DUMP_DESCRIPTORS
mov eax, [buffer]
mov ecx, [length]
DEBUGF 1,'K : report descriptor:'
@@:
DEBUGF 1,' %x',[eax]:2
inc eax
dec ecx
jnz @b
DEBUGF 1,'\n'
end if
; 3b. Call the HID layer.
parse_descr
cmp [report_ok], 0
jz got_report.exit
mov ebx, [calldata]
postprocess_descr
; 4. Stop initialization if no driver is assigned.
if ~HID_DUMP_UNCLAIMED
cmp [has_driver], 0
jz got_report.exit
end if
; 5. Open interrupt IN pipe. If failed, stop initialization.
mov edx, [ebx+usb_device_data.epdescr]
movzx ecx, [edx+endpoint_descr.bEndpointAddress]
movzx eax, [edx+endpoint_descr.bInterval]
movzx edx, [edx+endpoint_descr.wMaxPacketSize]
invoke USBOpenPipe, [ebx+usb_device_data.configpipe], ecx, edx, INTERRUPT_PIPE, eax
test eax, eax
jz got_report.exit
mov [ebx+usb_device_data.intpipe], eax
; 6. Initialize buffer for input packet.
; 6a. Find the length of input packet.
; This is the maximal length of all input reports.
mov edx, [ebx+usb_device_data.hid.input.first_report]
xor eax, eax
.find_input_size:
test edx, edx
jz .found_input_size
cmp eax, [edx+report.size]
jae @f
mov eax, [edx+report.size]
@@:
mov edx, [edx+report.next]
jmp .find_input_size
.found_input_size:
; report.size is in bits, transform it to bytes
add eax, 7
shr eax, 3
; if reports are numbered, the first byte is report ID, include it
cmp [ebx+usb_device_data.hid.input.numbered], 0
jz @f
inc eax
@@:
mov [ebx+usb_device_data.input_transfer_size], eax
; 6b. Allocate memory for input packet: dword-align and add additional dword
; for extract_field_value.
add eax, 4+3
and eax, not 3
invoke Kmalloc
test eax, eax
jnz @f
mov esi, nomemory_msg
invoke SysMsgBoardStr
jmp got_report.exit
@@:
mov [ebx+usb_device_data.input_buffer], eax
; 7. Submit a request for input packet and wait for input.
call ask_for_input
got_report.exit:
mov eax, [buffer]
invoke Kfree
ret
endp
 
; Helper procedure for got_report and got_input.
; Submits a request for the next input packet.
proc ask_for_input
; just call USBNormalTransferAsync with correct parameters,
; allow short packets
invoke USBNormalTransferAsync, \
[ebx+usb_device_data.intpipe], \
[ebx+usb_device_data.input_buffer], \
[ebx+usb_device_data.input_transfer_size], \
got_input, ebx, \
1
ret
endp
 
; This procedure is called by USB stack when a HID device responds with input
; data packet.
proc got_input stdcall uses ebx esi edi, pipe, status, buffer, length, calldata
locals
parse_input_locals
endl
; 1. Validate parameters: fail on error, ignore zero-length transfers.
mov ebx, [calldata]
cmp [status], 0
jnz .fail
cmp [length], 0
jz .done
; 2. Get pointer to report in esi.
; 2a. If there are no report IDs, use hid.input.data.
mov eax, [buffer]
mov esi, [ebx+usb_device_data.hid.input.data]
cmp [ebx+usb_device_data.hid.input.numbered], 0
jz .report_found
; 2b. Otherwise, the first byte of report is report ID;
; locate the report by its ID, advance buffer+length to one byte.
movzx eax, byte [eax]
mov esi, [esi+eax*4]
inc [buffer]
dec [length]
.report_found:
; 3. Validate: ignore transfers with unregistered report IDs
; and transfers which are too short for the corresponding report.
test esi, esi
jz .done
mov eax, [esi+report.size]
add eax, 7
shr eax, 3
cmp eax, [length]
ja .done
; 4. Pass everything to HID layer.
parse_input
.done:
; 5. Query the next input.
mov ebx, [calldata]
call ask_for_input
.nothing:
ret
.fail:
mov esi, transfer_error_msg
invoke SysMsgBoardStr
jmp .nothing
endp
 
; This function is called by the USB subsystem when a device is disconnected.
proc DeviceDisconnected
push ebx esi edi ; save used registers to be stdcall
virtual at esp
rd 3 ; saved registers
dd ? ; return address
.device_data dd ?
end virtual
; 1. Say a message.
mov ebx, [.device_data]
mov esi, disconnectmsg
invoke SysMsgBoardStr
; 2. Ask HID layer to release all HID-related resources.
hid_cleanup
; 3. Free the device data.
xchg eax, ebx
invoke Kfree
; 4. Return.
.nothing:
pop edi esi ebx ; restore used registers to be stdcall
ret 4 ; purge one dword argument to be stdcall
endp
 
include 'sort.inc'
include 'unclaimed.inc'
include 'mouse.inc'
include 'keyboard.inc'
 
; strings
my_driver db 'usbhid',0
nomemory_msg db 'K : no memory',13,10,0
invalid_config_descr_msg db 'K : invalid config descriptor',13,10,0
reportfail db 'K : failed to read report descriptor',13,10,0
transfer_error_msg db 'K : USB transfer error, disabling HID device',13,10,0
disconnectmsg db 'K : USB HID device disconnected',13,10,0
invalid_report_msg db 'K : report descriptor is invalid',13,10,0
delimiter_note db 'K : note: alternate usage ignored',13,10,0
 
align 4
; Structure with callback functions.
usb_functions:
dd 12
dd AddDevice
dd DeviceDisconnected
 
; for DEBUGF macro
include_debug_strings
 
; Workers data
workers_globals
 
align 4
data fixups
end data
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/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
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/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
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/drivers/usb/usbstor.asm
0,0 → 1,1599
; standard driver stuff; version of driver model = 5
format PE DLL native 0.05
entry START
 
DEBUG = 1
DUMP_PACKETS = 0
 
; this is for DEBUGF macro from 'fdo.inc'
__DEBUG__ = 1
__DEBUG_LEVEL__ = 1
 
include '../struct.inc'
 
; 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
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
 
; Mass storage protocol constants, USB layer
REQUEST_GETMAXLUN = 0xFE ; get max lun
REQUEST_BORESET = 0xFF ; bulk-only reset
 
; Mass storage protocol structures, USB layer
; Sent from host to device in the first stage of an operation.
struct command_block_wrapper
Signature dd ? ; the constant 'USBC'
Tag dd ? ; identifies response with request
Length dd ? ; length of data-transport phase
Flags db ? ; one of CBW_FLAG_*
CBW_FLAG_OUT = 0
CBW_FLAG_IN = 80h
LUN db ? ; addressed unit
CommandLength db ? ; the length of the following field
Command rb 16
ends
 
; Sent from device to host in the last stage of an operation.
struct command_status_wrapper
Signature dd ? ; the constant 'USBS'
Tag dd ? ; identifies response with request
LengthRest dd ? ; .Length - (size of data which were transferred)
Status db ? ; one of CSW_STATUS_*
CSW_STATUS_OK = 0
CSW_STATUS_FAIL = 1
CSW_STATUS_FATAL = 2
ends
 
; Constants of SCSI layer
SCSI_REQUEST_SENSE = 3
SCSI_INQUIRY = 12h
SCSI_READ_CAPACITY = 25h
SCSI_READ10 = 28h
SCSI_WRITE10 = 2Ah
 
; Result of SCSI REQUEST SENSE command.
SENSE_UNKNOWN = 0
SENSE_RECOVERED_ERROR = 1
SENSE_NOT_READY = 2
SENSE_MEDIUM_ERROR = 3
SENSE_HARDWARE_ERROR = 4
SENSE_ILLEGAL_REQUEST = 5
SENSE_UNIT_ATTENTION = 6
SENSE_DATA_PROTECT = 7
SENSE_BLANK_CHECK = 8
; 9 is vendor-specific
SENSE_COPY_ABORTED = 10
SENSE_ABORTED_COMMAND = 11
SENSE_EQUAL = 12
SENSE_VOLUME_OVERFLOW = 13
SENSE_MISCOMPARE = 14
; 15 is reserved
 
; Structures of SCSI layer
; Result of SCSI INQUIRY request.
struct inquiry_data
PeripheralDevice db ? ; lower 5 bits are PeripheralDeviceType
; upper 3 bits are PeripheralQualifier
RemovableMedium db ? ; upper bit is RemovableMedium
; other bits are for compatibility
Version db ? ; lower 3 bits are ANSI-Approved version
; next 3 bits are ECMA version
; upper 2 bits are ISO version
ResponseDataFormat db ? ; lower 4 bits are ResponseDataFormat
; bit 6 is TrmIOP
; bit 7 is AENC
AdditionalLength db ?
dw ? ; reserved
Flags db ?
VendorID rb 8 ; vendor ID, big-endian
ProductID rb 16 ; product ID, big-endian
ProductRevBE dd ? ; product revision, big-endian
ends
 
struct sense_data
ErrorCode db ? ; lower 7 bits are error code:
; 70h = current error,
; 71h = deferred error
; upper bit is InformationValid
SegmentNumber db ? ; number of segment descriptor
; for commands COPY [+VERIFY], COMPARE
SenseKey db ? ; bits 0-3 are one of SENSE_*
; bit 4 is reserved
; bit 5 is IncorrectLengthIndicator
; bits 6 and 7 are used by
; sequential-access devices
Information dd ? ; command-specific
AdditionalLength db ? ; length of data starting here
CommandInformation dd ? ; command-specific
AdditionalSenseCode db ? ; \ more detailed error code
AdditionalSenseQual db ? ; / standard has a large table of them
FRUCode db ? ; which part of device has failed
; (device-specific, not regulated)
SenseKeySpecific rb 3 ; depends on SenseKey
ends
 
; Device data
; USB Mass storage device has one or more logical units, identified by LUN,
; logical unit number. The highest value of LUN, that is, number of units
; minus 1, can be obtained via control request Get Max LUN.
struct usb_device_data
ConfigPipe dd ? ; configuration pipe
OutPipe dd ? ; pipe for OUT bulk endpoint
InPipe dd ? ; pipe for IN bulk endpoint
MaxLUN dd ? ; maximum Logical Unit Number
LogicalDevices dd ? ; pointer to array of usb_unit_data
; 1 for a connected USB device, 1 for each disk device
; the structure can be freed when .NumReferences decreases to zero
NumReferences dd ? ; number of references
ConfigRequest rb 8 ; buffer for configuration requests
LengthRest dd ? ; Length - (size of data which were transferred)
; All requests to a given device are serialized,
; only one request to a given device can be processed at a time.
; The current request and all pending requests are organized in the following
; queue, the head being the current request.
; NB: the queue must be device-wide due to the protocol:
; data stage is not tagged (unlike command_*_wrapper), so the only way to know
; what request the data are associated with is to guarantee that only one
; request is processing at the time.
RequestsQueue rd 2
QueueLock rd 3 ; protects .RequestsQueue
InquiryData inquiry_data ; information about device
; data for the current request
Command command_block_wrapper
DeviceDisconnected db ?
Status command_status_wrapper
Sense sense_data
ends
 
; Information about one logical device.
struct usb_unit_data
Parent dd ? ; pointer to parent usb_device_data
LUN db ? ; index in usb_device_data.LogicalDevices array
DiskIndex db ? ; for name "usbhd<index>"
MediaPresent db ?
db ? ; alignment
DiskDevice dd ? ; handle of disk device or NULL
SectorSize dd ? ; sector size
; For some devices, the first request to the medium fails with 'unit not ready'.
; When the code sees this status, it retries the command several times.
; Two following variables track the retry count and total time for those;
; total time is currently used only for debug output.
UnitReadyAttempts dd ?
TimerTicks dd ?
ends
 
; This is the structure for items in the queue usb_device_data.RequestsQueue.
struct request_queue_item
Next dd ? ; next item in the queue
Prev dd ? ; prev item in the queue
ReqBuilder dd ? ; procedure to fill command_block_wrapper
Buffer dd ? ; input or output data
; (length is command_block_wrapper.Length)
Callback dd ? ; procedure to call in the end of transfer
UserData dd ? ; passed as-is to .Callback
; There are 3 possible stages of any request, one of them optional:
; command stage (host sends command_block_wrapper to device),
; optional data stage,
; status stage (device sends command_status_wrapper to host).
; Also, if a request fails, the code queues additional request
; SCSI_REQUEST_SENSE; sense_data from SCSI_REQUEST_SENSE
; contains some information about the error.
Stage db ?
ends
 
section '.flat' code readable writable executable
include '../proc32.inc'
include '../peimport.inc'
include '../fdo.inc'
include '../macros.inc'
 
; The start procedure.
proc START
virtual at esp
dd ? ; return address
.reason dd ? ; DRV_ENTRY or DRV_EXIT
.cmdline dd ?
end virtual
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
; If not, return 0.
xor eax, eax ; initialize return value
cmp [.reason], 1 ; compare the argument
jnz .nothing
; 2. Initialize: we have one global mutex.
mov ecx, free_numbers_lock
invoke MutexInit
; 3. Register self as a USB driver.
; The name is my_driver = 'usbstor'; IOCTL interface is not supported;
; usb_functions is an offset of a structure with callback functions.
invoke RegUSBDriver, my_driver, 0, usb_functions
; 4. Return the returned value of RegUSBDriver.
.nothing:
ret
endp
 
; Helper procedures to work with requests queue.
 
; Add a request to the queue. Stdcall with 5 arguments.
proc queue_request
push ebx esi
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.device dd ? ; pointer to usb_device_data
.ReqBuilder dd ? ; request_queue_item.ReqBuilder
.Buffer dd ? ; request_queue_item.Buffer
.Callback dd ? ; request_queue_item.Callback
.UserData dd ? ; request_queue_item.UserData
end virtual
; 1. Allocate the memory for the request description.
movi eax, sizeof.request_queue_item
invoke Kmalloc
test eax, eax
jnz @f
mov esi, nomemory
invoke SysMsgBoardStr
pop esi ebx
ret 20
@@:
; 2. Fill user-provided parts of the request description.
push edi
xchg eax, ebx
lea esi, [.ReqBuilder+4]
lea edi, [ebx+request_queue_item.ReqBuilder]
movsd ; ReqBuilder
movsd ; Buffer
movsd ; Callback
movsd ; UserData
pop edi
; 3. Set stage to zero: not started.
mov [ebx+request_queue_item.Stage], 0
; 4. Lock the queue.
mov esi, [.device]
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexLock
; 5. Insert the request to the tail of the queue.
add esi, usb_device_data.RequestsQueue
mov edx, [esi+request_queue_item.Prev]
mov [ebx+request_queue_item.Next], esi
mov [ebx+request_queue_item.Prev], edx
mov [edx+request_queue_item.Next], ebx
mov [esi+request_queue_item.Prev], ebx
; 6. Test whether the queue was empty
; and the request should be started immediately.
cmp [esi+request_queue_item.Next], ebx
jnz .unlock
; 8. If the step 6 shows that the request is the first in the queue,
; start it.
sub esi, usb_device_data.RequestsQueue
call setup_request
jmp .nothing
.unlock:
invoke MutexUnlock
; 9. Return.
.nothing:
pop esi ebx
ret 20
endp
 
; The current request is completed. Call the callback,
; remove the request from the queue, start the next
; request if there is one.
; esi points to usb_device_data
proc complete_request
; 1. Print common debug messages on fails.
if DEBUG
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL
jb .normal
jz .fail
DEBUGF 1, 'K : Fatal error during execution of command %x\n', [esi+usb_device_data.Command.Command]:2
jmp .normal
.fail:
DEBUGF 1, 'K : Command %x failed\n', [esi+usb_device_data.Command.Command]:2
.normal:
end if
; 2. Get the current request.
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next]
; 3. Call the callback.
stdcall [ebx+request_queue_item.Callback], esi, [ebx+request_queue_item.UserData]
; 4. Lock the queue.
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexLock
; 5. Remove the request.
lea edx, [esi+usb_device_data.RequestsQueue]
mov eax, [ebx+request_queue_item.Next]
mov [eax+request_queue_item.Prev], edx
mov [edx+request_queue_item.Next], eax
; 6. Free the request memory.
push eax edx
xchg eax, ebx
invoke Kfree
pop edx ebx
; 7. If there is a next request, start processing.
cmp ebx, edx
jnz setup_request
; 8. Unlock the queue and return.
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexUnlock
ret
endp
 
; Start processing the request. Called either by queue_request
; or when the previous request has been processed.
; Do not call directly, use queue_request.
; Must be called when queue is locked; unlocks the queue when returns.
proc setup_request
xor eax, eax
; 1. If DeviceDisconnected has been run, then all handles of pipes
; are invalid, so we must fail immediately.
; (That is why this function needs the locked queue: this
; guarantee that either DeviceDisconnected has been already run, or
; DeviceDisconnected will not return before the queue is unlocked.)
cmp [esi+usb_device_data.DeviceDisconnected], al
jnz .fatal
; 2. If the previous command has encountered a fatal error,
; perform reset recovery.
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL
jb .norecovery
; 2a. Send Bulk-Only Mass Storage Reset command to config pipe.
lea edx, [esi+usb_device_data.ConfigRequest]
mov word [edx], (REQUEST_BORESET shl 8) + 21h ; class request
mov word [edx+6], ax ; length = 0
invoke USBControlTransferAsync, [esi+usb_device_data.ConfigPipe], edx, eax, eax, recovery_callback1, esi, eax
; 2b. Fail here = fatal error.
test eax, eax
jz .fatal
; 2c. Otherwise, unlock the queue and return. recovery_callback1 will continue processing.
.unlock_return:
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexUnlock
ret
.norecovery:
; 3. Send the command. Fail (no memory or device disconnected) = fatal error.
; Otherwise, go to 2c.
call request_stage1
test eax, eax
jnz .unlock_return
.fatal:
; 4. Fatal error. Set status = FATAL, unlock the queue, complete the request.
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexUnlock
jmp complete_request
endp
 
; Initiate USB transfer for the first stage of a request (send command).
proc request_stage1
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next]
; 1. Set the stage to 1 = command stage.
inc [ebx+request_queue_item.Stage]
; 2. Generate the command. Zero-initialize and use the caller-provided proc.
lea edx, [esi+usb_device_data.Command]
xor eax, eax
mov [edx+command_block_wrapper.CommandLength], 12
mov dword [edx+command_block_wrapper.Command], eax
mov dword [edx+command_block_wrapper.Command+4], eax
mov dword [edx+command_block_wrapper.Command+8], eax
mov dword [edx+command_block_wrapper.Command+12], eax
inc [edx+command_block_wrapper.Tag]
stdcall [ebx+request_queue_item.ReqBuilder], edx, [ebx+request_queue_item.UserData]
; 4. Initiate USB transfer.
lea edx, [esi+usb_device_data.Command]
if DUMP_PACKETS
DEBUGF 1,'K : USBSTOR out:'
mov eax, edx
mov ecx, sizeof.command_block_wrapper
call debug_dump
DEBUGF 1,'\n'
end if
invoke USBNormalTransferAsync, [esi+usb_device_data.OutPipe], edx, sizeof.command_block_wrapper, request_callback1, esi, 0
test eax, eax
jz .nothing
; 5. If the next stage is data stage in the same direction, enqueue it here.
cmp [esi+usb_device_data.Command.Flags], 0
js .nothing
cmp [esi+usb_device_data.Command.Length], 0
jz .nothing
mov edx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next]
if DUMP_PACKETS
DEBUGF 1,'K : USBSTOR out:'
mov eax, [edx+request_queue_item.Buffer]
mov ecx, [esi+usb_device_data.Command.Length]
call debug_dump
DEBUGF 1,'\n'
end if
invoke USBNormalTransferAsync, [esi+usb_device_data.OutPipe], [edx+request_queue_item.Buffer], [esi+usb_device_data.Command.Length], request_callback2, esi, 0
.nothing:
ret
endp
 
if DUMP_PACKETS
proc debug_dump
test ecx, ecx
jz .done
.loop:
test ecx, 0Fh
jnz @f
DEBUGF 1,'\nK :'
@@:
DEBUGF 1,' %x',[eax]:2
inc eax
dec ecx
jnz .loop
.done:
ret
endp
end if
 
; Called when the Reset command is completed,
; either successfully or not.
proc recovery_callback1
virtual at esp
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
cmp [.status], 0
jnz .error
; todo: reset pipes
push ebx esi
mov esi, [.calldata+8]
call request_stage1
pop esi ebx
test eax, eax
jz .error
ret 20
.error:
DEBUGF 1, 'K : error %d while resetting', [.status+24h]
jmp request_callback1.common_error
endp
 
; Called when the first stage of request is completed,
; either successfully or not.
proc request_callback1
virtual at esp
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
; 1. Initialize.
mov ecx, [.calldata]
mov eax, [.status]
; 2. Test for error.
test eax, eax
jnz .error
; No error.
; 3. Increment the stage.
mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next]
inc [edx+request_queue_item.Stage]
; 4. Check whether we need to send the data.
; 4a. If there is no data, skip this stage.
cmp [ecx+usb_device_data.Command.Length], 0
jz ..request_get_status
; 4b. If data were enqueued in the first stage, do nothing, wait for request_callback2.
cmp [ecx+usb_device_data.Command.Flags], 0
jns .nothing
; 5. Initiate USB transfer. If this fails, go to the error handler.
invoke USBNormalTransferAsync, [ecx+usb_device_data.InPipe], [edx+request_queue_item.Buffer], [ecx+usb_device_data.Command.Length], request_callback2, ecx, 0
test eax, eax
jz .error
; 6. The status stage goes to the same direction, enqueue it now.
mov ecx, [.calldata]
jmp ..enqueue_status
.nothing:
ret 20
.error:
; Error.
; 7. Print debug message and complete the request as failed.
DEBUGF 1,'K : error %d after %d bytes in request stage\n',eax,[.length+24h]
; If device is disconnected and data stage is enqueued, do nothing;
; data stage callback will do everything.
cmp eax, 16
jnz .common_error
cmp [ecx+usb_device_data.Command.Flags], 0
js .common_error
cmp [ecx+usb_device_data.Command.Length], 0
jz .common_error
ret 20
.common_error:
; TODO: add recovery after STALL
mov ecx, [.calldata]
mov [ecx+usb_device_data.Status.Status], CSW_STATUS_FATAL
push ebx esi
mov esi, ecx
call complete_request
pop esi ebx
ret 20
endp
 
; Called when the second stage of request is completed,
; either successfully or not.
proc request_callback2
virtual at esp
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
if DUMP_PACKETS
mov eax, [.calldata]
mov eax, [eax+usb_device_data.InPipe]
cmp [.pipe], eax
jnz @f
DEBUGF 1,'K : USBSTOR in:'
push eax ecx
mov eax, [.buffer+8]
mov ecx, [.length+8]
call debug_dump
pop ecx eax
DEBUGF 1,'\n'
@@:
end if
; 1. Initialize.
mov ecx, [.calldata]
mov eax, [.status]
; 2. Test for error.
test eax, eax
jnz .error
; No error.
; If the previous stage was in same direction, do nothing; status request is already enqueued.
cmp [ecx+usb_device_data.Command.Flags], 0
js .nothing
..request_get_status:
; 3. Increment the stage.
mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next]
inc [edx+request_queue_item.Stage]
; 4. Initiate USB transfer. If this fails, go to the error handler.
..enqueue_status:
lea edx, [ecx+usb_device_data.Status]
invoke USBNormalTransferAsync, [ecx+usb_device_data.InPipe], edx, sizeof.command_status_wrapper, request_callback3, ecx, 0
test eax, eax
jz .error
.nothing:
ret 20
.error:
; Error.
; 5. Print debug message and complete the request as failed.
DEBUGF 1,'K : error %d after %d bytes in data stage\n',eax,[.length+24h]
; If device is disconnected and data stage is enqueued, do nothing;
; status stage callback will do everything.
cmp [ecx+usb_device_data.Command.Flags], 0
js .nothing
jmp request_callback1.common_error
endp
 
; Called when the third stage of request is completed,
; either successfully or not.
proc request_callback3
virtual at esp
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
if DUMP_PACKETS
DEBUGF 1,'K : USBSTOR in:'
mov eax, [.buffer]
mov ecx, [.length]
call debug_dump
DEBUGF 1,'\n'
end if
; 1. Initialize.
mov eax, [.status]
; 2. Test for error.
test eax, eax
jnz .transfer_error
; Transfer is OK.
; 3. Validate the status. Invalid status = fatal error.
push ebx esi
mov esi, [.calldata+8]
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next]
cmp [esi+usb_device_data.Status.Signature], 'USBS'
jnz .invalid
mov eax, [esi+usb_device_data.Command.Tag]
cmp [esi+usb_device_data.Status.Tag], eax
jnz .invalid
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL
ja .invalid
; 4. The status block is valid. Check the status code.
jz .complete
; 5. If this command was not REQUEST_SENSE, copy status data to safe place.
; Otherwise, the original command has failed, so restore the fail status.
cmp byte [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE
jz .request_sense
mov eax, [esi+usb_device_data.Status.LengthRest]
mov [esi+usb_device_data.LengthRest], eax
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL
jz .fail
.complete:
call complete_request
.nothing:
pop esi ebx
ret 20
.request_sense:
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL
jmp .complete
.invalid:
; 6. Invalid status block. Say error, set status to fatal and complete request.
push esi
mov esi, invresponse
invoke SysMsgBoardStr
pop esi
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL
jmp .complete
.fail:
; 7. The command has failed.
; If this command was not REQUEST_SENSE, schedule the REQUEST_SENSE command
; to determine the reason of fail. Otherwise, assume that there is no error data.
cmp [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE
jz .fail_request_sense
mov [ebx+request_queue_item.ReqBuilder], request_sense_req
lea eax, [esi+usb_device_data.Sense]
mov [ebx+request_queue_item.Buffer], eax
call request_stage1
test eax, eax
jnz .nothing
.fail_request_sense:
DEBUGF 1,'K : fail during REQUEST SENSE\n'
mov byte [esi+usb_device_data.Sense], 0
jmp .complete
.transfer_error:
; TODO: add recovery after STALL
DEBUGF 1,'K : error %d after %d bytes in status stage\n',eax,[.length+24h]
jmp request_callback1.common_error
endp
 
; Builder for SCSI_REQUEST_SENSE request.
; edx = first argument = pointer to usb_device_data.Command,
; second argument = custom data given to queue_request (ignored).
proc request_sense_req
mov [edx+command_block_wrapper.Length], sizeof.sense_data
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN
mov byte [edx+command_block_wrapper.Command+0], SCSI_REQUEST_SENSE
mov byte [edx+command_block_wrapper.Command+4], sizeof.sense_data
ret 8
endp
 
; This procedure is called when new mass-storage device is detected.
; It initializes the device.
; Technically, initialization implies sending several USB queries,
; so it is split in several procedures. The first is AddDevice,
; other are callbacks which will be called at some time in the future,
; when the device will respond.
; The general scheme:
; * AddDevice parses descriptors, opens pipes; if everything is ok,
; AddDevice sends REQUEST_GETMAXLUN with callback known_lun_callback;
; * known_lun_callback allocates memory for LogicalDevices and sends
; SCSI_TEST_UNIT_READY to all logical devices with test_unit_ready_callback;
; * test_unit_ready_callback checks whether the unit is ready;
; if not, it repeats the same request several times;
; if ok or there were too many attempts, it sends SCSI_INQUIRY with
; callback inquiry_callback;
; * inquiry_callback checks that a logical device is a block device
; and the unit was ready; if so, it notifies the kernel about new disk device.
proc AddDevice
push ebx esi
virtual at esp
rd 2 ; saved registers ebx, esi
dd ? ; return address
.pipe0 dd ? ; handle of the config pipe
.config dd ? ; pointer to config_descr
.interface dd ? ; pointer to interface_descr
end virtual
; 1. Check device type. Currently only SCSI-command-set Bulk-only devices
; 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 esi, [.interface]
xor ebx, ebx
mov cx, word [esi+interface_descr.bInterfaceSubClass]
; 1b. For Mass-storage SCSI-command-set Bulk-only devices subclass must be 6
; and protocol must be 50h. Check.
cmp cx, 0x5006
jz .known
; There are devices with subclass 5 which use the same protocol 50h.
; The difference is not important for the code except for this test,
; so allow them to proceed also.
cmp cx, 0x5005
jz .known
; 1c. If the device is unknown, print a message and go to 11c.
mov esi, unkdevice
invoke SysMsgBoardStr
jmp .nothing
; 1d. If the device uses known command set, print a message and continue
; configuring.
.known:
push esi
mov esi, okdevice
invoke SysMsgBoardStr
pop esi
; 2. Allocate memory for internal device data.
; 2a. Call the kernel.
mov eax, sizeof.usb_device_data
invoke Kmalloc
; 2b. Check return value.
test eax, eax
jnz @f
; 2c. If failed, say a message and go to 11c.
mov esi, nomemory
invoke SysMsgBoardStr
jmp .nothing
@@:
; 2d. If succeeded, zero the contents and continue configuring.
xchg ebx, eax ; ebx will point to usb_device_data
xor eax, eax
mov [ebx+usb_device_data.OutPipe], eax
mov [ebx+usb_device_data.InPipe], eax
mov [ebx+usb_device_data.MaxLUN], eax
mov [ebx+usb_device_data.LogicalDevices], eax
mov dword [ebx+usb_device_data.ConfigRequest], eax
mov dword [ebx+usb_device_data.ConfigRequest+4], eax
mov [ebx+usb_device_data.Status.Status], al
mov [ebx+usb_device_data.DeviceDisconnected], al
; 2e. There is one reference: a connected USB device.
inc eax
mov [ebx+usb_device_data.NumReferences], eax
; 2f. Save handle of configuration pipe for reset recovery.
mov eax, [.pipe0]
mov [ebx+usb_device_data.ConfigPipe], eax
; 2g. Save the interface number for configuration requests.
mov al, [esi+interface_descr.bInterfaceNumber]
mov [ebx+usb_device_data.ConfigRequest+4], al
; 2h. Initialize common fields in command wrapper.
mov [ebx+usb_device_data.Command.Signature], 'USBC'
mov [ebx+usb_device_data.Command.Tag], 'xxxx'
; 2i. Initialize requests queue.
lea eax, [ebx+usb_device_data.RequestsQueue]
mov [eax+request_queue_item.Next], eax
mov [eax+request_queue_item.Prev], eax
lea ecx, [ebx+usb_device_data.QueueLock]
invoke MutexInit
; Bulk-only mass storage devices use one OUT bulk endpoint for sending
; command/data and one IN bulk endpoint for receiving data/status.
; Look for those endpoints.
; 3. Get the upper bound of all descriptors' data.
mov edx, [.config] ; configuration descriptor
movzx ecx, [edx+config_descr.wTotalLength]
add edx, 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 both endpoint descriptors found.
; 4a. Loop start: esi points to the interface descriptor,
.lookep:
; 4b. Get next descriptor.
movzx ecx, byte [esi] ; the first byte of all descriptors is length
add esi, ecx
; 4c. Check that at least two bytes are readable. The opposite is an error.
inc esi
cmp esi, edx
jae .errorep
dec esi
; 4d. Check that this descriptor is not interface descriptor. The opposite is
; an error.
cmp byte [esi+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
jz .errorep
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue
; the loop.
cmp byte [esi+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
jnz .lookep
; 5. Check that the descriptor contains all required data and all data are
; readable. The opposite is an error.
cmp byte [esi+endpoint_descr.bLength], sizeof.endpoint_descr
jb .errorep
lea ecx, [esi+sizeof.endpoint_descr]
cmp ecx, edx
ja .errorep
; 6. Check that the endpoint is bulk endpoint. The opposite is an error.
mov cl, [esi+endpoint_descr.bmAttributes]
and cl, 3
cmp cl, BULK_PIPE
jnz .errorep
; 7. Get the direction of this endpoint.
movzx ecx, [esi+endpoint_descr.bEndpointAddress]
shr ecx, 7
; 8. Test whether a pipe for this direction is already opened. If so, continue
; the loop.
cmp [ebx+usb_device_data.OutPipe+ecx*4], 0
jnz .lookep
; 9. Open pipe for this endpoint.
; 9a. Save registers.
push ecx edx
; 9b. Load parameters from the descriptor.
movzx ecx, [esi+endpoint_descr.bEndpointAddress]
movzx edx, [esi+endpoint_descr.wMaxPacketSize]
movzx eax, [esi+endpoint_descr.bInterval] ; not used for USB1, may be important for USB2
; 9c. Call the kernel.
invoke USBOpenPipe, [ebx+usb_device_data.ConfigPipe], ecx, edx, BULK_PIPE, eax
; 9d. Restore registers.
pop edx ecx
; 9e. Check result. If failed, go to 11b.
test eax, eax
jz .free
; 9f. Save result.
mov [ebx+usb_device_data.OutPipe+ecx*4], eax
; 10. Test whether the second pipe is already opened. If not, continue loop.
xor ecx, 1
cmp [ebx+usb_device_data.OutPipe+ecx*4], 0
jz .lookep
jmp .created
; 11. An error occured during processing endpoint descriptor.
.errorep:
; 11a. Print a message.
DEBUGF 1,'K : error: invalid endpoint descriptor\n'
.free:
; 11b. Free the allocated usb_device_data.
xchg eax, ebx
invoke Kfree
.nothing:
; 11c. Return an error.
xor eax, eax
jmp .return
.created:
; 12. Pipes are opened. Send GetMaxLUN control request.
lea eax, [ebx+usb_device_data.ConfigRequest]
mov byte [eax], 0A1h ; class request from interface
mov byte [eax+1], REQUEST_GETMAXLUN
mov byte [eax+6], 1 ; transfer 1 byte
lea ecx, [ebx+usb_device_data.MaxLUN]
if DUMP_PACKETS
DEBUGF 1,'K : GETMAXLUN: %x %x %x %x %x %x %x %x\n',[eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2
end if
invoke USBControlTransferAsync, [ebx+usb_device_data.ConfigPipe], eax, ecx, 1, known_lun_callback, ebx, 0
; 13. Return with pointer to device data as returned value.
xchg eax, ebx
.return:
pop esi ebx
ret 12
endp
 
; This function is called when REQUEST_GETMAXLUN is done,
; either successful or unsuccessful.
proc known_lun_callback
push ebx esi
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.pipe dd ?
.status dd ?
.buffer dd ?
.length dd ?
.calldata dd ?
end virtual
; 1. Check the status. If the request failed, assume that MaxLUN is zero.
mov ebx, [.calldata]
mov eax, [.status]
test eax, eax
jz @f
DEBUGF 1, 'K : GETMAXLUN failed with status %d, assuming zero\n', eax
mov [ebx+usb_device_data.MaxLUN], 0
@@:
; 2. Allocate the memory for logical devices.
mov eax, [ebx+usb_device_data.MaxLUN]
inc eax
DEBUGF 1,'K : %d logical unit(s)\n',eax
imul eax, sizeof.usb_unit_data
push ebx
invoke Kmalloc
pop ebx
; If failed, print a message and do nothing.
test eax, eax
jnz @f
mov esi, nomemory
invoke SysMsgBoardStr
pop esi ebx
ret 20
@@:
mov [ebx+usb_device_data.LogicalDevices], eax
; 3. Initialize logical devices and initiate TEST_UNIT_READY request.
xchg esi, eax
xor ecx, ecx
.looplun:
mov [esi+usb_unit_data.Parent], ebx
mov [esi+usb_unit_data.LUN], cl
xor eax, eax
mov [esi+usb_unit_data.MediaPresent], al
mov [esi+usb_unit_data.DiskDevice], eax
mov [esi+usb_unit_data.SectorSize], eax
mov [esi+usb_unit_data.UnitReadyAttempts], eax
push ecx
invoke GetTimerTicks
mov [esi+usb_unit_data.TimerTicks], eax
stdcall queue_request, ebx, test_unit_ready_req, 0, test_unit_ready_callback, esi
pop ecx
inc ecx
add esi, sizeof.usb_unit_data
cmp ecx, [ebx+usb_device_data.MaxLUN]
jbe .looplun
; 4. Return.
pop esi ebx
ret 20
endp
 
; Builder for SCSI INQUIRY request.
; edx = first argument = pointer to usb_device_data.Command,
; second argument = custom data given to queue_request.
proc inquiry_req
mov eax, [esp+8]
mov al, [eax+usb_unit_data.LUN]
mov [edx+command_block_wrapper.Length], sizeof.inquiry_data
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN
mov [edx+command_block_wrapper.LUN], al
mov byte [edx+command_block_wrapper.Command+0], SCSI_INQUIRY
mov byte [edx+command_block_wrapper.Command+4], sizeof.inquiry_data
ret 8
endp
 
; Called when SCSI INQUIRY request is completed.
proc inquiry_callback
; 1. Check the status.
mov ecx, [esp+4]
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK
jnz .fail
; 2. The command has completed successfully.
; Print a message showing device type, ignore anything but block devices.
mov al, [ecx+usb_device_data.InquiryData.PeripheralDevice]
and al, 1Fh
DEBUGF 1,'K : peripheral device type is %x\n',al
test al, al
jnz .nothing
DEBUGF 1,'K : direct-access mass storage device detected\n'
; 3. We have found a new disk device. Increment number of references.
lock inc [ecx+usb_device_data.NumReferences]
; Unfortunately, we are now in the context of the USB thread,
; so we can't notify the kernel immediately: it would try to do something
; with a new disk, those actions would be synchronous and would require
; waiting for results of USB requests, but we need to exit this callback
; to allow the USB thread to continue working and handling those requests.
; 4. Thus, create a temporary kernel thread which would do it.
mov edx, [esp+8]
push ebx ecx esi edi
movi ebx, 1
mov ecx, new_disk_thread
; edx = parameter
invoke CreateThread
pop edi esi ecx ebx
cmp eax, -1
jnz .nothing
; on error, reverse step 3
lock dec [ecx+usb_device_data.NumReferences]
.nothing:
ret 8
.fail:
; 4. The command has failed. Print a message and do nothing.
push esi
mov esi, inquiry_fail
invoke SysMsgBoardStr
pop esi
ret 8
endp
 
; Builder for SCSI TEST_UNIT_READY request.
; edx = first argument = pointer to usb_device_data.Command,
; second argument = custom data given to queue_request.
proc test_unit_ready_req
mov eax, [esp+8]
mov al, [eax+usb_unit_data.LUN]
mov [edx+command_block_wrapper.Length], 0
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN
mov [edx+command_block_wrapper.LUN], al
ret 8
endp
 
; Called when SCSI TEST_UNIT_READY request is completed.
proc test_unit_ready_callback
virtual at esp
dd ? ; return address
.device dd ?
.calldata dd ?
end virtual
; 1. Check the status.
mov ecx, [.device]
mov edx, [.calldata]
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK
jnz .fail
; 2. The command has completed successfully,
; possibly after some repetitions. Print a debug message showing
; number and time of those. Remember that media is ready and go to 4.
DEBUGF 1,'K : media is ready\n'
invoke GetTimerTicks
sub eax, [edx+usb_unit_data.TimerTicks]
DEBUGF 1,'K : %d attempts, %d ticks\n',[edx+usb_unit_data.UnitReadyAttempts],eax
inc [edx+usb_unit_data.MediaPresent]
jmp .inquiry
.fail:
; 3. The command has failed.
; Retry the same request up to 3 times with 10ms delay;
; if limit of retries is not reached, exit from the function.
; Otherwise, go to 4.
inc [edx+usb_unit_data.UnitReadyAttempts]
cmp [edx+usb_unit_data.UnitReadyAttempts], 3
jz @f
push ecx edx esi
movi esi, 10
invoke Sleep
pop esi edx ecx
stdcall queue_request, ecx, test_unit_ready_req, 0, test_unit_ready_callback, edx
ret 8
@@:
DEBUGF 1,'K : media not ready\n'
.inquiry:
; 4. initiate INQUIRY request.
lea eax, [ecx+usb_device_data.InquiryData]
stdcall queue_request, ecx, inquiry_req, eax, inquiry_callback, edx
ret 8
endp
 
; Temporary thread for initial actions with a new disk device.
proc new_disk_thread
sub esp, 32
virtual at esp
.name rb 32 ; device name
.param dd ? ; contents of edx at the moment of int 0x40/eax=51
dd ? ; stack segment
end virtual
; We are ready to notify the kernel about a new disk device.
mov esi, [.param]
; 1. Generate name.
; 1a. Find a free index.
mov ecx, free_numbers_lock
invoke MutexLock
xor eax, eax
@@:
bsf edx, [free_numbers+eax]
jnz @f
add eax, 4
cmp eax, 4*4
jnz @b
invoke MutexUnlock
push esi
mov esi, noindex
invoke SysMsgBoardStr
pop esi
jmp .drop_reference
@@:
; 1b. Mark the index as busy.
btr [free_numbers+eax], edx
lea eax, [eax*8+edx]
push eax
invoke MutexUnlock
pop eax
; 1c. Generate a name of the form "usbhd<index>" in the stack.
mov dword [esp], 'usbh'
lea edi, [esp+5]
mov byte [edi-1], 'd'
push eax
push -'0'
movi ecx, 10
@@:
cdq
div ecx
push edx
test eax, eax
jnz @b
@@:
pop eax
add al, '0'
stosb
jnz @b
pop ecx
mov edx, esp
; 3d. Store the index in usb_unit_data to free it later.
mov [esi+usb_unit_data.DiskIndex], cl
; 4. Notify the kernel about a new disk.
; 4a. Add a disk.
; stdcall queue_request, ecx, read_capacity_req, eax, read_capacity_callback, eax
invoke DiskAdd, disk_functions, edx, esi, 0
mov ebx, eax
; 4b. If it failed, release the index and do nothing.
test eax, eax
jz .free_index
; 4c. Notify the kernel that a media is present.
invoke DiskMediaChanged, eax, 1
; 5. Lock the requests queue, check that device is not disconnected,
; store the disk handle, unlock the requests queue.
mov ecx, [esi+usb_unit_data.Parent]
add ecx, usb_device_data.QueueLock
invoke MutexLock
cmp byte [ecx+usb_device_data.DeviceDisconnected-usb_device_data.QueueLock], 0
jnz .disconnected
mov [esi+usb_unit_data.DiskDevice], ebx
invoke MutexUnlock
jmp .exit
.disconnected:
invoke MutexUnlock
stdcall disk_close, ebx
jmp .exit
.free_index:
mov ecx, free_numbers_lock
invoke MutexLock
movzx eax, [esi+usb_unit_data.DiskIndex]
bts [free_numbers], eax
invoke MutexUnlock
.drop_reference:
mov esi, [esi+usb_unit_data.Parent]
lock dec [esi+usb_device_data.NumReferences]
jnz .exit
mov eax, [esi+usb_device_data.LogicalDevices]
invoke Kfree
xchg eax, esi
invoke Kfree
.exit:
or eax, -1
int 0x40
endp
 
; This function is called when the device is disconnected.
proc DeviceDisconnected
push ebx esi
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.device dd ?
end virtual
; 1. Say a message.
mov esi, disconnectmsg
invoke SysMsgBoardStr
; 2. Lock the requests queue, set .DeviceDisconnected to 1,
; unlock the requests queue.
; Locking is required for synchronization with queue_request:
; all USB callbacks are executed in the same thread and are
; synchronized automatically, but queue_request can be running
; from any thread which wants to do something with a filesystem.
; Without locking, it would be possible that queue_request has
; been started, has checked that device is not yet disconnected,
; then DeviceDisconnected completes and all handles become invalid,
; then queue_request tries to use them.
mov esi, [.device]
lea ecx, [esi+usb_device_data.QueueLock]
invoke MutexLock
mov [esi+usb_device_data.DeviceDisconnected], 1
invoke MutexUnlock
; 3. Drop one reference to the structure and check whether
; that was the last reference.
lock dec [esi+usb_device_data.NumReferences]
jz .free
; 4. If not, there are some additional references due to disk devices;
; notify the kernel that those disks are deleted.
; Note that new disks cannot be added while we are looping here,
; because new_disk_thread checks for .DeviceDisconnected.
mov ebx, [esi+usb_device_data.MaxLUN]
mov esi, [esi+usb_device_data.LogicalDevices]
inc ebx
.diskdel:
mov eax, [esi+usb_unit_data.DiskDevice]
test eax, eax
jz @f
invoke DiskDel, eax
@@:
add esi, sizeof.usb_unit_data
dec ebx
jnz .diskdel
; In this case, some operations with those disks are still possible,
; so we can't do anything more now. disk_close will take care of the rest.
.return:
pop esi ebx
ret 4
; 5. If there are no disk devices, free all resources which were allocated.
.free:
mov eax, [esi+usb_device_data.LogicalDevices]
test eax, eax
jz @f
invoke Kfree
@@:
xchg eax, esi
invoke Kfree
jmp .return
endp
 
; Disk functions.
DISK_STATUS_OK = 0 ; success
DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable
DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters
DISK_STATUS_NO_MEDIA = 2 ; no media present
DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data
 
; Called when all operations with the given disk are done.
proc disk_close
push ebx esi
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.userdata dd ?
end virtual
mov esi, [.userdata]
mov ecx, free_numbers_lock
invoke MutexLock
movzx eax, [esi+usb_unit_data.DiskIndex]
bts [free_numbers], eax
invoke MutexUnlock
mov esi, [esi+usb_unit_data.Parent]
lock dec [esi+usb_device_data.NumReferences]
jnz .nothing
mov eax, [esi+usb_device_data.LogicalDevices]
invoke Kfree
xchg eax, esi
invoke Kfree
.nothing:
pop esi ebx
ret 4
endp
 
; Returns sector size, capacity and flags of the media.
proc disk_querymedia stdcall uses ebx esi edi, \
userdata:dword, mediainfo:dword
; 1. Create event for waiting.
xor esi, esi
xor ecx, ecx
invoke CreateEvent
test eax, eax
jz .generic_fail
push eax
push edx
push ecx
push 0
push 0
virtual at ebp-.localsize
.locals:
; two following dwords are the output of READ_CAPACITY
.LastLBABE dd ?
.SectorSizeBE dd ?
.Status dd ?
; two following dwords identify an event
.event_code dd ?
.event dd ?
rd 3 ; saved registers
.localsize = $ - .locals
dd ? ; saved ebp
dd ? ; return address
.userdata dd ?
.mediainfo dd ?
end virtual
; 2. Initiate SCSI READ_CAPACITY request.
mov eax, [userdata]
mov ecx, [eax+usb_unit_data.Parent]
mov edx, esp
stdcall queue_request, ecx, read_capacity_req, edx, read_capacity_callback, edx
; 3. Wait for event. This destroys it.
mov eax, [.event]
mov ebx, [.event_code]
invoke WaitEvent
; 4. Get the status and results.
pop ecx
bswap ecx ; .LastLBA
pop edx
bswap edx ; .SectorSize
pop eax ; .Status
; 5. If the request has completed successfully, store results.
test eax, eax
jnz @f
DEBUGF 1,'K : sector size is %d, last sector is %d\n',edx,ecx
mov ebx, [mediainfo]
mov [ebx], eax ; flags = 0
mov [ebx+4], edx ; sectorsize
add ecx, 1
adc eax, 0
mov [ebx+8], ecx
mov [ebx+12], eax ; capacity
mov eax, [userdata]
mov [eax+usb_unit_data.SectorSize], edx
xor eax, eax
@@:
; 6. Restore the stack and return.
pop ecx
pop ecx
ret
.generic_fail:
or eax, -1
ret
endp
 
; Builder for SCSI READ_CAPACITY request.
; edx = first argument = pointer to usb_device_data.Command,
; second argument = custom data given to queue_request,
; pointer to disk_querymedia.locals.
proc read_capacity_req
mov eax, [esp+8]
mov eax, [eax+disk_querymedia.userdata-disk_querymedia.locals]
mov al, [eax+usb_unit_data.LUN]
mov [edx+command_block_wrapper.Length], 8
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN
mov [edx+command_block_wrapper.LUN], al
mov byte [edx+command_block_wrapper.Command+0], SCSI_READ_CAPACITY
ret 8
endp
 
; Called when SCSI READ_CAPACITY request is completed.
proc read_capacity_callback
; Transform the status to return value of disk_querymedia
; and set the event.
mov ecx, [esp+4]
xor eax, eax
cmp [ecx+usb_device_data.Status.Status], al
jz @f
or eax, -1
@@:
mov ecx, [esp+8]
mov [ecx+disk_querymedia.Status-disk_querymedia.locals], eax
push ebx esi edi
mov eax, [ecx+disk_querymedia.event-disk_querymedia.locals]
mov ebx, [ecx+disk_querymedia.event_code-disk_querymedia.locals]
xor edx, edx
xor esi, esi
invoke RaiseEvent
pop edi esi ebx
ret 8
endp
 
disk_write:
mov al, SCSI_WRITE10
jmp disk_read_write
 
disk_read:
mov al, SCSI_READ10
 
; Reads from the device or writes to the device.
proc disk_read_write stdcall uses ebx esi edi, \
userdata:dword, buffer:dword, startsector:qword, numsectors:dword
; 1. Initialize.
push eax ; .command
mov eax, [userdata]
mov eax, [eax+usb_unit_data.SectorSize]
push eax ; .SectorSize
push 0 ; .processed
mov eax, [numsectors]
mov eax, [eax]
; 2. The transfer length for SCSI_{READ,WRITE}10 commands can not be greater
; than 0xFFFF, so split the request to slices with <= 0xFFFF sectors.
max_sectors_at_time = 0xFFFF
.split:
push eax ; .length_rest
cmp eax, max_sectors_at_time
jb @f
mov eax, max_sectors_at_time
@@:
sub [esp], eax
push eax ; .length_cur
; 3. startsector must fit in 32 bits, otherwise abort the request.
cmp dword [startsector+4], 0
jnz .generic_fail
; 4. Create event for waiting.
xor esi, esi
xor ecx, ecx
invoke CreateEvent
test eax, eax
jz .generic_fail
push eax ; .event
push edx ; .event_code
push ecx ; .status
virtual at ebp-.localsize
.locals:
.status dd ?
.event_code dd ?
.event dd ?
.length_cur dd ?
.length_rest dd ?
.processed dd ?
.SectorSize dd ?
.command db ?
rb 3
rd 3 ; saved registers
.localsize = $ - .locals
dd ? ; saved ebp
dd ? ; return address
.userdata dd ?
.buffer dd ?
.startsector dq ?
.numsectors dd ?
end virtual
; 5. Initiate SCSI READ10 or WRITE10 request.
mov eax, [userdata]
mov ecx, [eax+usb_unit_data.Parent]
stdcall queue_request, ecx, read_write_req, [buffer], read_write_callback, esp
; 6. Wait for event. This destroys it.
mov eax, [.event]
mov ebx, [.event_code]
invoke WaitEvent
; 7. Get the status. If the operation has failed, abort.
pop eax ; .status
pop ecx ecx ; cleanup .event_code, .event
pop ecx ; .length_cur
test eax, eax
jnz .return
; 8. Otherwise, continue the loop started at step 2.
add dword [startsector], ecx
adc dword [startsector+4], eax
imul ecx, [.SectorSize]
add [buffer], ecx
pop eax
test eax, eax
jnz .split
push eax
.return:
; 9. Restore the stack, store .processed to [numsectors], return.
pop ecx ; .length_rest
pop ecx ; .processed
mov edx, [numsectors]
mov [edx], ecx
pop ecx ; .SectorSize
pop ecx ; .command
ret
.generic_fail:
or eax, -1
pop ecx ; .length_cur
jmp .return
endp
 
; Builder for SCSI READ10 or WRITE10 request.
; edx = first argument = pointer to usb_device_data.Command,
; second argument = custom data given to queue_request,
; pointer to disk_read_write.locals.
proc read_write_req
mov eax, [esp+8]
mov ecx, [eax+disk_read_write.userdata-disk_read_write.locals]
mov cl, [ecx+usb_unit_data.LUN]
mov [edx+command_block_wrapper.LUN], cl
mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals]
imul ecx, [eax+disk_read_write.SectorSize-disk_read_write.locals]
mov [edx+command_block_wrapper.Length], ecx
mov cl, [eax+disk_read_write.command-disk_read_write.locals]
mov [edx+command_block_wrapper.Flags], CBW_FLAG_OUT
cmp cl, SCSI_READ10
jnz @f
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN
@@:
mov byte [edx+command_block_wrapper.Command], cl
mov ecx, dword [eax+disk_read_write.startsector-disk_read_write.locals]
bswap ecx
mov dword [edx+command_block_wrapper.Command+2], ecx
mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals]
xchg cl, ch
mov word [edx+command_block_wrapper.Command+7], cx
ret 8
endp
 
; Called when SCSI READ10 or WRITE10 request is completed.
proc read_write_callback
; 1. Initialize.
push ebx esi edi
virtual at esp
rd 3 ; saved registers
dd ? ; return address
.device dd ?
.calldata dd ?
end virtual
mov ecx, [.device]
mov esi, [.calldata]
; 2. Get the number of sectors which were read.
; If the status is OK or FAIL, the field .LengthRest is valid.
; Otherwise, it is invalid, so assume zero sectors.
xor eax, eax
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_FAIL
ja .sectors_calculated
mov eax, [ecx+usb_device_data.LengthRest]
xor edx, edx
div [esi+disk_read_write.SectorSize-disk_read_write.locals]
test edx, edx
jz @f
inc eax
@@:
mov edx, eax
mov eax, [esi+disk_read_write.length_cur-disk_read_write.locals]
sub eax, edx
jae .sectors_calculated
xor eax, eax
.sectors_calculated:
; 3. Increase the total number of processed sectors.
add [esi+disk_read_write.processed-disk_read_write.locals], eax
; 4. Set status to OK if all sectors were read, to ERROR otherwise.
cmp eax, [esi+disk_read_write.length_cur-disk_read_write.locals]
setz al
movzx eax, al
dec eax
mov [esi+disk_read_write.status-disk_read_write.locals], eax
; 5. Set the event.
mov eax, [esi+disk_read_write.event-disk_read_write.locals]
mov ebx, [esi+disk_read_write.event_code-disk_read_write.locals]
xor edx, edx
xor esi, esi
invoke RaiseEvent
; 6. Return.
pop edi esi ebx
ret 8
endp
 
; strings
my_driver db 'usbstor',0
disconnectmsg db 'K : USB mass storage device disconnected',13,10,0
nomemory db 'K : no memory',13,10,0
unkdevice db 'K : unknown mass storage device',13,10,0
okdevice db 'K : USB mass storage device detected',13,10,0
transfererror db 'K : USB transfer error, disabling mass storage',13,10,0
invresponse db 'K : invalid response from mass storage device',13,10,0
fatalerr db 'K : mass storage device reports fatal error',13,10,0
inquiry_fail db 'K : INQUIRY command failed',13,10,0
;read_capacity_fail db 'K : READ CAPACITY command failed',13,10,0
;read_fail db 'K : READ command failed',13,10,0
noindex db 'K : failed to generate disk name',13,10,0
 
align 4
; Structure with callback functions.
usb_functions:
dd usb_functions_end - usb_functions
dd AddDevice
dd DeviceDisconnected
usb_functions_end:
 
disk_functions:
dd disk_functions_end - disk_functions
dd disk_close
dd 0 ; closemedia
dd disk_querymedia
dd disk_read
dd disk_write
dd 0 ; flush
dd 0 ; adjust_cache_size: use default cache
disk_functions_end:
 
data fixups
end data
 
free_numbers_lock rd 3
; 128 devices should be enough for everybody
free_numbers dd -1, -1, -1, -1
 
; for DEBUGF macro
include_debug_strings
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property