API to cancel all queued transfers on USB pipe; add timeout for USB device early initialization

git-svn-id: svn://kolibrios.org@4547 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
CleverMouse 2014-01-29 12:14:28 +00:00
parent 49a6c736a9
commit 241079c114
11 changed files with 467 additions and 339 deletions

View File

@ -156,8 +156,6 @@ Overlay ehci_hardware_td ?
; Working area for the current TD, if there is any. ; Working area for the current TD, if there is any.
; When TD is retired, it is written to that TD and Overlay is loaded ; When TD is retired, it is written to that TD and Overlay is loaded
; from the new TD, if any. ; from the new TD, if any.
BaseList dd ?
; Pointer to head of the corresponding pipe list.
ends ends
; This structure describes the static head of every list of pipes. ; This structure describes the static head of every list of pipes.
@ -293,6 +291,8 @@ ehci_hardware_func:
dd ehci_alloc_transfer dd ehci_alloc_transfer
dd ehci_insert_transfer dd ehci_insert_transfer
dd ehci_new_device dd ehci_new_device
dd ehci_disable_pipe
dd ehci_enable_pipe
ehci_name db 'EHCI',0 ehci_name db 'EHCI',0
endg endg
@ -998,7 +998,7 @@ end virtual
jz .return0 jz .return0
mov word [edi+ehci_pipe.Flags-sizeof.ehci_pipe], ax mov word [edi+ehci_pipe.Flags-sizeof.ehci_pipe], ax
.insert: .insert:
mov [edi+ehci_pipe.BaseList-sizeof.ehci_pipe], edx mov [edi+usb_pipe.BaseList], edx
; Insert to the head of the corresponding list. ; Insert to the head of the corresponding list.
; Note: inserting to the head guarantees that the list traverse in ; Note: inserting to the head guarantees that the list traverse in
; ehci_process_updated_schedule, once started, will not interact with new pipes. ; ehci_process_updated_schedule, once started, will not interact with new pipes.
@ -1912,19 +1912,58 @@ proc ehci_unlink_pipe
call ehci_fs_interrupt_list_unlink call ehci_fs_interrupt_list_unlink
.interrupt_common: .interrupt_common:
@@: @@:
mov edx, [ebx+usb_pipe.NextVirt] ret
mov eax, [ebx+usb_pipe.PrevVirt] endp
mov [edx+usb_pipe.PrevVirt], eax
mov [eax+usb_pipe.NextVirt], edx ; This procedure temporarily removes the given pipe from hardware queue.
; esi -> usb_controller, ebx -> usb_pipe
proc ehci_disable_pipe
mov eax, [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe]
mov ecx, [ebx+usb_pipe.PrevVirt]
mov edx, esi mov edx, esi
sub edx, eax sub edx, ecx
cmp edx, sizeof.ehci_controller cmp edx, sizeof.ehci_controller
mov edx, [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe]
jb .prev_is_static jb .prev_is_static
mov [eax+ehci_pipe.NextQH-sizeof.ehci_pipe], edx mov [ecx+ehci_pipe.NextQH-sizeof.ehci_pipe], eax
ret ret
.prev_is_static: .prev_is_static:
mov [eax+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], edx mov [ecx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax
ret
endp
; This procedure reinserts the given pipe to hardware queue
; after ehci_disable_pipe, with clearing transfer queue.
; esi -> usb_controller, ebx -> usb_pipe
; edx -> current descriptor, eax -> new last descriptor
proc ehci_enable_pipe
; 1. Clear transfer queue.
; 1a. Clear status bits so that the controller will try to advance the queue
; without doing anything, keep DataToggle and PID bits.
and [ebx+ehci_pipe.Overlay.Token-sizeof.ehci_pipe], 80000000h
; 1b. Set [Alternate]NextTD to physical address of the new last descriptor.
sub eax, sizeof.ehci_gtd
invoke GetPhysAddr
mov [ebx+ehci_pipe.HeadTD-sizeof.ehci_pipe], eax
mov [ebx+ehci_pipe.Overlay.NextTD-sizeof.ehci_pipe], eax
mov [ebx+ehci_pipe.Overlay.AlternateNextTD-sizeof.ehci_pipe], eax
; 2. Reinsert the pipe to hardware queue.
lea eax, [ebx-sizeof.ehci_pipe]
invoke GetPhysAddr
inc eax
inc eax
mov ecx, [ebx+usb_pipe.PrevVirt]
mov edx, esi
sub edx, ecx
cmp edx, sizeof.ehci_controller
jb .prev_is_static
mov edx, [ecx+ehci_pipe.NextQH-sizeof.ehci_pipe]
mov [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe], edx
mov [ecx+ehci_pipe.NextQH-sizeof.ehci_pipe], eax
ret
.prev_is_static:
mov edx, [ecx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart]
mov [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe], edx
mov [ecx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax
ret ret
endp endp

View File

@ -298,7 +298,7 @@ proc ehci_hs_interrupt_list_unlink
imul eax, ecx imul eax, ecx
movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe] movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe]
; get target list ; get target list
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe] mov edx, [ebx+usb_pipe.BaseList]
; update bandwidth ; update bandwidth
.dec_bandwidth: .dec_bandwidth:
shr ecx, 1 shr ecx, 1
@ -732,7 +732,7 @@ proc ehci_fs_interrupt_list_unlink
mov edi, esp mov edi, esp
call tt_fill_split_info call tt_fill_split_info
; get target list ; get target list
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe] mov edx, [ebx+usb_pipe.BaseList]
; update bandwidth for Start-Split ; update bandwidth for Start-Split
mov eax, [edi+usb_split_info.ssplit_bandwidth] mov eax, [edi+usb_split_info.ssplit_bandwidth]
xor ecx, ecx xor ecx, ecx

View File

@ -64,7 +64,7 @@ Flags dd ?
; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of ; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of
; the endpoint within the function. ; the endpoint within the function.
; 3. Next 2 bits (bits 11-12) are Direction. This 2-bit field indicates the ; 3. Next 2 bits (bits 11-12) are Direction. This 2-bit field indicates the
; direction of data flow: 1 = IN, 2 = OUT. If neither IN nor OUT is ; direction of data flow: 1 = OUT, 2 = IN. If neither IN nor OUT is
; specified, then the direction is determined from the PID field of the TD. ; specified, then the direction is determined from the PID field of the TD.
; For CONTROL endpoints, the transfer direction is different ; For CONTROL endpoints, the transfer direction is different
; for different transfers, so the value of this field is 0 ; for different transfers, so the value of this field is 0
@ -322,6 +322,8 @@ ohci_hardware_func:
dd ohci_alloc_transfer dd ohci_alloc_transfer
dd ohci_insert_transfer dd ohci_insert_transfer
dd ohci_new_device dd ohci_new_device
dd ohci_disable_pipe
dd ohci_enable_pipe
ohci_name db 'OHCI',0 ohci_name db 'OHCI',0
endg endg
@ -1014,6 +1016,7 @@ end virtual
; Inserting to tail would work as well, ; Inserting to tail would work as well,
; but let's be consistent with other controllers. ; but let's be consistent with other controllers.
.insert: .insert:
mov [edi+usb_pipe.BaseList], edx
mov ecx, [edx+usb_pipe.NextVirt] mov ecx, [edx+usb_pipe.NextVirt]
mov [edi+usb_pipe.NextVirt], ecx mov [edi+usb_pipe.NextVirt], ecx
mov [edi+usb_pipe.PrevVirt], edx mov [edi+usb_pipe.PrevVirt], edx
@ -1614,17 +1617,41 @@ proc ohci_unlink_pipe
mov eax, [ebx+ohci_pipe.Flags-sizeof.ohci_pipe] mov eax, [ebx+ohci_pipe.Flags-sizeof.ohci_pipe]
bt eax, 13 bt eax, 13
setc cl setc cl
bt eax, 11 bt eax, 12
setc ch setc ch
shr eax, 16 shr eax, 16
stdcall usb1_interrupt_list_unlink, eax, ecx stdcall usb1_interrupt_list_unlink, eax, ecx
@@: @@:
mov edx, [ebx+usb_pipe.NextVirt] ret
mov eax, [ebx+usb_pipe.PrevVirt] endp
mov [edx+usb_pipe.PrevVirt], eax
mov [eax+usb_pipe.NextVirt], edx ; This procedure temporarily removes the given pipe from hardware queue,
mov edx, [ebx+ohci_pipe.NextED-sizeof.ohci_pipe] ; keeping it in software lists.
mov [eax+ohci_pipe.NextED-sizeof.ohci_pipe], edx ; esi -> usb_controller, ebx -> usb_pipe
proc ohci_disable_pipe
mov eax, [ebx+ohci_pipe.NextED-sizeof.ohci_pipe]
mov edx, [ebx+usb_pipe.PrevVirt]
mov [edx+ohci_pipe.NextED-sizeof.ohci_pipe], eax
ret
endp
; This procedure reinserts the given pipe from hardware queue
; after ehci_disable_pipe, with clearing transfer queue.
; esi -> usb_controller, ebx -> usb_pipe
; edx -> current descriptor, eax -> new last descriptor
proc ohci_enable_pipe
sub eax, sizeof.ohci_gtd
invoke GetPhysAddr
mov edx, [ebx+ohci_pipe.HeadP-sizeof.ohci_pipe]
and edx, 2
or eax, edx
mov [ebx+ohci_pipe.HeadP-sizeof.ohci_pipe], eax
lea eax, [ebx-sizeof.ohci_pipe]
invoke GetPhysAddr
mov edx, [ebx+usb_pipe.PrevVirt]
mov ecx, [edx+ohci_pipe.NextED-sizeof.ohci_pipe]
mov [ebx+ohci_pipe.NextED-sizeof.ohci_pipe], ecx
mov [edx+ohci_pipe.NextED-sizeof.ohci_pipe], eax
ret ret
endp endp

View File

@ -274,6 +274,8 @@ uhci_hardware_func:
dd uhci_alloc_transfer dd uhci_alloc_transfer
dd uhci_insert_transfer dd uhci_insert_transfer
dd uhci_new_device dd uhci_new_device
dd uhci_disable_pipe
dd uhci_enable_pipe
uhci_name db 'UHCI',0 uhci_name db 'UHCI',0
endg endg
@ -1133,7 +1135,6 @@ proc uhci_fix_toggle
jnz .loop jnz .loop
; 5. Flip the toggle bit in uhci_pipe structure. ; 5. Flip the toggle bit in uhci_pipe structure.
xor byte [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock+2], 1 shl (19-16) xor byte [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock+2], 1 shl (19-16)
or dword [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock], eax
; 6. Unlock the transfer queue. ; 6. Unlock the transfer queue.
invoke MutexUnlock invoke MutexUnlock
.nothing: .nothing:
@ -1461,6 +1462,7 @@ end virtual
test edx, edx test edx, edx
jz .return0 jz .return0
.insert: .insert:
mov [edi+usb_pipe.BaseList], edx
; Insert to the head of the corresponding list. ; Insert to the head of the corresponding list.
; Note: inserting to the head guarantees that the list traverse in ; Note: inserting to the head guarantees that the list traverse in
; uhci_process_updated_schedule, once started, will not interact with new pipes. ; uhci_process_updated_schedule, once started, will not interact with new pipes.
@ -1505,17 +1507,44 @@ proc uhci_unlink_pipe
shr eax, 21 shr eax, 21
stdcall usb1_interrupt_list_unlink, eax, ecx stdcall usb1_interrupt_list_unlink, eax, ecx
@@: @@:
; Note: we need to ensure that NextVirt field of the pipe is not modified; ret
; this procedure can be called while uhci_process_updated_schedule processes endp
; the same pipe, and it needs a correct NextVirt field to continue.
mov edx, [ebx+usb_pipe.NextVirt] ; This procedure temporarily removes the given pipe from hardware queue,
mov eax, [ebx+usb_pipe.PrevVirt] ; keeping it in software lists.
mov [edx+usb_pipe.PrevVirt], eax ; esi -> usb_controller, ebx -> usb_pipe
mov [eax+usb_pipe.NextVirt], edx proc uhci_disable_pipe
; Note: eax could be either usb_pipe or usb_static_ep; mov eax, [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe]
mov edx, [ebx+usb_pipe.PrevVirt]
; Note: edx could be either usb_pipe or usb_static_ep;
; fortunately, NextQH and SoftwarePart have same offsets in both. ; fortunately, NextQH and SoftwarePart have same offsets in both.
mov edx, [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe] mov [edx+uhci_pipe.NextQH-sizeof.uhci_pipe], eax
mov [eax+uhci_pipe.NextQH-sizeof.uhci_pipe], edx ret
endp
; This procedure reinserts the given pipe from hardware queue
; after ehci_disable_pipe, with clearing transfer queue.
; esi -> usb_controller, ebx -> usb_pipe
; edx -> current descriptor, eax -> new last descriptor
proc uhci_enable_pipe
; 1. Copy DataToggle bit from edx to pipe.
mov ecx, [edx+uhci_gtd.Token-sizeof.uhci_gtd]
xor ecx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe]
and ecx, 1 shl 19
xor [ebx+uhci_pipe.Token-sizeof.uhci_pipe], ecx
; 2. Store new last descriptor as the current HeadTD.
sub eax, sizeof.uhci_gtd
invoke GetPhysAddr
mov [ebx+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax
; 3. Reinsert the pipe to hardware queue.
lea eax, [ebx-sizeof.uhci_pipe]
invoke GetPhysAddr
inc eax
inc eax
mov edx, [ebx+usb_pipe.PrevVirt]
mov ecx, [edx+uhci_pipe.NextQH-sizeof.uhci_pipe]
mov [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe], ecx
mov [edx+uhci_pipe.NextQH-sizeof.uhci_pipe], eax
ret ret
endp endp

View File

@ -167,12 +167,7 @@ end virtual
mov eax, [.maxpacket] mov eax, [.maxpacket]
mov ecx, dword [.lowspeed] mov ecx, dword [.lowspeed]
call calc_usb1_bandwidth call calc_usb1_bandwidth
; find list header mov edx, [ebx+usb_pipe.BaseList]
mov edx, ebx
@@:
mov edx, [edx+usb_pipe.NextVirt]
cmp [edx+usb_pipe.Controller], esi
jz @b
; subtract pipe bandwidth ; subtract pipe bandwidth
sub [edx+usb_static_ep.Bandwidth], eax sub [edx+usb_static_ep.Bandwidth], eax
ret 8 ret 8

View File

@ -6,7 +6,7 @@
; ============================================================================= ; =============================================================================
; Version of all structures related to host controllers. ; Version of all structures related to host controllers.
; Must be the same in kernel and *hci-drivers. ; Must be the same in kernel and *hci-drivers.
USBHC_VERSION = 1 USBHC_VERSION = 2
; USB device must have at least 100ms of stable power before initializing can ; USB device must have at least 100ms of stable power before initializing can
; proceed; one timer tick is 10ms, so enforce delay in 10 ticks ; proceed; one timer tick is 10ms, so enforce delay in 10 ticks
@ -46,6 +46,7 @@ USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
USB_STATUS_CLOSED = 16 ; pipe closed USB_STATUS_CLOSED = 16 ; pipe closed
; either explicitly with USBClosePipe ; either explicitly with USBClosePipe
; or implicitly due to device disconnect ; or implicitly due to device disconnect
USB_STATUS_CANCELLED = 17 ; transfer cancelled with USBAbortPipe
; Possible speeds of USB devices ; Possible speeds of USB devices
USB_SPEED_FS = 0 ; full-speed USB_SPEED_FS = 0 ; full-speed
@ -63,6 +64,9 @@ USB_FLAG_CAN_FREE = 2
USB_FLAG_EXTRA_WAIT = 4 USB_FLAG_EXTRA_WAIT = 4
; The pipe was in wait list, while another event occured; ; The pipe was in wait list, while another event occured;
; when the first wait will be done, reinsert the pipe to wait list ; when the first wait will be done, reinsert the pipe to wait list
USB_FLAG_DISABLED = 8
; The pipe is temporarily disabled so that it is not visible to hardware
; but still remains in software list. Used for usb_abort_pipe.
USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT
; ============================================================================= ; =============================================================================
@ -136,6 +140,14 @@ NewDevice dd ?
; Initiate configuration of a new device (create pseudo-pipe describing that ; Initiate configuration of a new device (create pseudo-pipe describing that
; device and call usb_new_device). ; device and call usb_new_device).
; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants). ; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants).
DisablePipe dd ?
; This procedure temporarily removes the given pipe from hardware queue.
; esi -> usb_controller, ebx -> usb_pipe
EnablePipe dd ?
; This procedure reinserts the given pipe to hardware queue
; after DisablePipe, with clearing transfer queue.
; esi -> usb_controller, ebx -> usb_pipe
; edx -> current descriptor, eax -> new last descriptor
ends ends
; pointers to kernel API functions that are called from *HCI-drivers ; pointers to kernel API functions that are called from *HCI-drivers
@ -307,6 +319,8 @@ NextVirt dd ?
PrevVirt dd ? PrevVirt dd ?
; Previous endpoint in the processing list. ; Previous endpoint in the processing list.
; See also NextVirt field and the description before NextVirt field. ; See also NextVirt field and the description before NextVirt field.
BaseList dd ?
; Pointer to head of the processing list.
; ;
; Every pipe has the associated transfer queue, that is, the double-linked ; Every pipe has the associated transfer queue, that is, the double-linked
; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt ; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt
@ -427,6 +441,8 @@ DeviceDescrSize db ?
; Size of device descriptor. ; Size of device descriptor.
Speed db ? Speed db ?
; Device speed, one of USB_SPEED_*. ; Device speed, one of USB_SPEED_*.
Timer dd ?
; Handle of timer that handles request timeout.
NumInterfaces dd ? NumInterfaces dd ?
; Number of interfaces. ; Number of interfaces.
ConfigDataSize dd ? ConfigDataSize dd ?

View File

@ -114,7 +114,7 @@ proc get_phys_addr
ret ret
endp endp
; Put the given control pipe in the wait list; ; Put the given control/bulk pipe in the wait list;
; called when the pipe structure is changed and a possible hardware cache ; called when the pipe structure is changed and a possible hardware cache
; needs to be synchronized. When it will be known that the cache is updated, ; needs to be synchronized. When it will be known that the cache is updated,
; usb_subscription_done procedure will be called. ; usb_subscription_done procedure will be called.
@ -128,6 +128,17 @@ proc usb_subscribe_control
ret ret
endp endp
; Same as usb_subscribe_control, but for interrupt/isochronous pipe.
proc usb_subscribe_periodic
cmp [ebx+usb_pipe.NextWait], -1
jnz @f
mov eax, [esi+usb_controller.WaitPipeListPeriodic]
mov [ebx+usb_pipe.NextWait], eax
mov [esi+usb_controller.WaitPipeListPeriodic], ebx
@@:
ret
endp
; Called after synchronization of hardware cache with software changes. ; Called after synchronization of hardware cache with software changes.
; Continues process of device enumeration based on when it was delayed ; Continues process of device enumeration based on when it was delayed
; due to call to usb_subscribe_control. ; due to call to usb_subscribe_control.
@ -254,13 +265,18 @@ proc usb_process_one_wait_list
mov [esi+usb_controller.WaitPipeListAsync+edx], ebx mov [esi+usb_controller.WaitPipeListAsync+edx], ebx
jmp .continue jmp .continue
.process: .process:
; 7. Call the handler depending on USB_FLAG_CLOSED. ; 7. Call the handler depending on USB_FLAG_CLOSED and USB_FLAG_DISABLED.
or [ebx+usb_pipe.NextWait], -1 or [ebx+usb_pipe.NextWait], -1
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
jz .nodisconnect jz .nodisconnect
call usb_pipe_closed call usb_pipe_closed
jmp .continue jmp .continue
.nodisconnect: .nodisconnect:
test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED
jz .nodisabled
call usb_pipe_disabled
jmp .continue
.nodisabled:
call usb_subscription_done call usb_subscription_done
.continue: .continue:
; 8. Restore edx and next pipe saved in step 5 and continue the loop. ; 8. Restore edx and next pipe saved in step 5 and continue the loop.

View File

@ -13,6 +13,11 @@ else
stdcall arg stdcall arg
end if end if
} }
if USB_STDCALL_VERIFY
STDCALL_VERIFY_EXTRA = 20h
else
STDCALL_VERIFY_EXTRA = 0
end if
; Initialization of usb_static_ep structure, ; Initialization of usb_static_ep structure,
; called from controller-specific initialization; edi -> usb_static_ep ; called from controller-specific initialization; edi -> usb_static_ep
@ -238,8 +243,17 @@ proc usb_close_pipe_nolock
call mutex_lock call mutex_lock
push ecx push ecx
; 3b. Let the controller-specific code do its job. ; 3b. Let the controller-specific code do its job.
test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED
jnz @f
mov eax, [esi+usb_controller.HardwareFunc]
call [eax+usb_hardware_func.DisablePipe]
@@:
mov eax, [esi+usb_controller.HardwareFunc] mov eax, [esi+usb_controller.HardwareFunc]
call [eax+usb_hardware_func.UnlinkPipe] call [eax+usb_hardware_func.UnlinkPipe]
mov edx, [ebx+usb_pipe.NextVirt]
mov eax, [ebx+usb_pipe.PrevVirt]
mov [edx+usb_pipe.PrevVirt], eax
mov [eax+usb_pipe.NextVirt], edx
; 3c. Release the corresponding lock. ; 3c. Release the corresponding lock.
pop ecx pop ecx
call mutex_unlock call mutex_unlock
@ -262,36 +276,66 @@ proc usb_close_pipe_nolock
ret ret
endp endp
; This procedure is called when all transfers are aborted
; either due to call to usb_abort_pipe or due to pipe closing.
; It notifies all callbacks and frees all transfer descriptors.
; ebx -> usb_pipe, esi -> usb_controller, edi -> usb_hardware_func
; three stack parameters: status code for callback functions
; and descriptors where to start and stop.
proc usb_pipe_aborted
virtual at esp
dd ? ; return address
.status dd ? ; USB_STATUS_CLOSED or USB_STATUS_CANCELLED
.first_td dd ?
.last_td dd ?
end virtual
; Loop over all transfers, calling the driver with the given status
; and freeing all descriptors except the last one.
.loop:
mov edx, [.first_td]
cmp edx, [.last_td]
jz .done
mov ecx, [edx+usb_gtd.Callback]
test ecx, ecx
jz .no_callback
stdcall_verify ecx, ebx, [.status+12+STDCALL_VERIFY_EXTRA], \
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData]
mov edx, [.first_td]
.no_callback:
mov eax, [edx+usb_gtd.NextVirt]
mov [.first_td], eax
stdcall [edi+usb_hardware_func.FreeTD], edx
jmp .loop
.done:
ret 12
endp
; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the ; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the
; corresponding wait list. It means that the hardware has fully forgot about it. ; corresponding wait list. It means that the hardware has fully forgot about it.
; ebx -> usb_pipe, esi -> usb_controller ; ebx -> usb_pipe, esi -> usb_controller
proc usb_pipe_closed proc usb_pipe_closed
push edi push edi
mov edi, [esi+usb_controller.HardwareFunc] mov edi, [esi+usb_controller.HardwareFunc]
; 1. Loop over all transfers, calling the driver with USB_STATUS_CLOSED ; 1. Notify all registered callbacks with status USB_STATUS_CLOSED, if any,
; and freeing all descriptors. ; and free all transfer descriptors, including the last one.
lea ecx, [ebx+usb_pipe.Lock]
call mutex_lock
mov edx, [ebx+usb_pipe.LastTD] mov edx, [ebx+usb_pipe.LastTD]
test edx, edx test edx, edx
jz .no_transfer jz .no_transfer
mov edx, [edx+usb_gtd.NextVirt] mov eax, [edx+usb_gtd.NextVirt]
.transfer_loop:
cmp edx, [ebx+usb_pipe.LastTD]
jz .transfer_done
mov ecx, [edx+usb_gtd.Callback]
test ecx, ecx
jz .no_callback
push edx push edx
stdcall_verify ecx, ebx, USB_STATUS_CLOSED, \ push eax
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData] call mutex_unlock
pop edx push USB_STATUS_CLOSED
.no_callback: call usb_pipe_aborted
push [edx+usb_gtd.NextVirt] ; It is safe to free LastTD here:
stdcall [edi+usb_hardware_func.FreeTD], edx ; usb_*_transfer_async do not enqueue new transfers if USB_FLAG_CLOSED is set.
pop edx stdcall [edi+usb_hardware_func.FreeTD], [ebx+usb_pipe.LastTD]
jmp .transfer_loop jmp @f
.transfer_done:
stdcall [edi+usb_hardware_func.FreeTD], edx
.no_transfer: .no_transfer:
call mutex_unlock
@@:
; 2. Decrement number of pipes for the device. ; 2. Decrement number of pipes for the device.
; If this pipe is the last pipe, go to 5. ; If this pipe is the last pipe, go to 5.
mov ecx, [ebx+usb_pipe.DeviceData] mov ecx, [ebx+usb_pipe.DeviceData]
@ -342,14 +386,23 @@ proc usb_pipe_closed
dec eax dec eax
jnz .notify_loop jnz .notify_loop
.notify_done: .notify_done:
; 6. Bus address, if assigned, can now be reused. ; 6. Kill the timer, if active.
; (Usually not; possible if device is disconnected
; while processing SET_ADDRESS request).
mov eax, [ebx+usb_pipe.DeviceData]
cmp [eax+usb_device_data.Timer], 0
jz @f
stdcall cancel_timer_hs, [eax+usb_device_data.Timer]
mov [eax+usb_device_data.Timer], 0
@@:
; 7. Bus address, if assigned, can now be reused.
call [edi+usb_hardware_func.GetDeviceAddress] call [edi+usb_hardware_func.GetDeviceAddress]
test eax, eax test eax, eax
jz @f jz @f
bts [esi+usb_controller.ExistingAddresses], eax bts [esi+usb_controller.ExistingAddresses], eax
@@: @@:
dbgstr 'USB device disconnected' dbgstr 'USB device disconnected'
; 7. All drivers have returned from disconnect callback, ; 8. All drivers have returned from disconnect callback,
; so all drivers should not use any device-related pipes. ; so all drivers should not use any device-related pipes.
; Free the remaining pipes. ; Free the remaining pipes.
mov eax, [ebx+usb_pipe.DeviceData] mov eax, [ebx+usb_pipe.DeviceData]
@ -366,15 +419,74 @@ proc usb_pipe_closed
.free_done: .free_done:
stdcall [edi+usb_hardware_func.FreePipe], ebx stdcall [edi+usb_hardware_func.FreePipe], ebx
pop eax pop eax
; 8. Free the usb_device_data structure. ; 9. Free the usb_device_data structure.
sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling
call free call free
; 9. Return. ; 10. Return.
.nothing: .nothing:
pop edi pop edi
ret ret
endp endp
; This procedure is called when a pipe with USB_FLAG_DISABLED is removed from the
; corresponding wait list. It means that the hardware has fully forgot about it.
; ebx -> usb_pipe, esi -> usb_controller
proc usb_pipe_disabled
push edi
mov edi, [esi+usb_controller.HardwareFunc]
; 1. Acquire pipe lock.
lea ecx, [ebx+usb_pipe.Lock]
call mutex_lock
; 2. Clear USB_FLAG_DISABLED in pipe state.
and [ebx+usb_pipe.Flags], not USB_FLAG_DISABLED
; 3. Sanity check: ignore uninitialized pipes.
cmp [ebx+usb_pipe.LastTD], 0
jz .no_transfer
; 4. Acquire the first and last to-be-cancelled transfer descriptor,
; save them in stack for the step 6,
; ask the controller driver to enable the pipe for hardware,
; removing transfers between first and last to-be-cancelled descriptors.
lea ecx, [esi+usb_controller.ControlLock]
cmp [ebx+usb_pipe.Type], BULK_PIPE
jb @f ; control pipe
lea ecx, [esi+usb_controller.BulkLock]
jz @f ; bulk pipe
lea ecx, [esi+usb_controller.PeriodicLock]
@@:
call mutex_lock
mov eax, [ebx+usb_pipe.BaseList]
mov edx, [eax+usb_pipe.NextVirt]
mov [ebx+usb_pipe.NextVirt], edx
mov [ebx+usb_pipe.PrevVirt], eax
mov [edx+usb_pipe.PrevVirt], ebx
mov [eax+usb_pipe.NextVirt], ebx
mov eax, [ebx+usb_pipe.LastTD]
mov edx, [eax+usb_gtd.NextVirt]
mov [eax+usb_gtd.NextVirt], eax
mov [eax+usb_gtd.PrevVirt], eax
push eax
push edx
push ecx
call [edi+usb_hardware_func.EnablePipe]
pop ecx
call mutex_unlock
; 5. Release pipe lock acquired at step 1.
; Callbacks called at step 6 can insert new transfers,
; so we cannot call usb_pipe_aborted while holding pipe lock.
lea ecx, [ebx+usb_pipe.Lock]
call mutex_unlock
; 6. Notify all registered callbacks with status USB_STATUS_CANCELLED, if any.
; Two arguments describing transfers range were pushed at step 4.
push USB_STATUS_CANCELLED
call usb_pipe_aborted
pop edi
ret
.no_transfer:
call mutex_unlock
pop edi
ret
endp
; Part of API for drivers, see documentation for USBNormalTransferAsync. ; Part of API for drivers, see documentation for USBNormalTransferAsync.
proc usb_normal_transfer_async stdcall uses ebx edi,\ proc usb_normal_transfer_async stdcall uses ebx edi,\
pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword
@ -508,6 +620,69 @@ endl
ret ret
endp endp
; Part of API for drivers, see documentation for USBAbortPipe.
proc usb_abort_pipe
push ebx esi ; save used registers to be stdcall
virtual at esp
rd 2 ; saved registers
dd ? ; return address
.pipe dd ?
end virtual
mov ebx, [.pipe]
; 1. Acquire pipe lock.
lea ecx, [ebx+usb_pipe.Lock]
call mutex_lock
; 2. If the pipe is already closed or abort is in progress,
; just release pipe lock and return.
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + USB_FLAG_DISABLED
jnz .nothing
; 3. Mark the pipe as aborting.
or [ebx+usb_pipe.Flags], USB_FLAG_DISABLED
; 4. We cannot do anything except adding new transfers concurrently with hardware.
; Ask the controller driver to (temporarily) remove the pipe from hardware queue.
mov esi, [ebx+usb_pipe.Controller]
; 4a. Acquire queue lock.
lea ecx, [esi+usb_controller.ControlLock]
cmp [ebx+usb_pipe.Type], BULK_PIPE
jb @f ; control pipe
lea ecx, [esi+usb_controller.BulkLock]
jz @f ; bulk pipe
lea ecx, [esi+usb_controller.PeriodicLock]
@@:
call mutex_lock
push ecx
; 4b. Call the driver.
mov eax, [esi+usb_controller.HardwareFunc]
call [eax+usb_hardware_func.DisablePipe]
; 4c. Remove the pipe from software list.
mov eax, [ebx+usb_pipe.NextVirt]
mov edx, [ebx+usb_pipe.PrevVirt]
mov [eax+usb_pipe.PrevVirt], edx
mov [edx+usb_pipe.NextVirt], eax
; 4c. Register the pipe in corresponding wait list.
test [ebx+usb_pipe.Type], 1
jz .control_bulk
call usb_subscribe_periodic
jmp @f
.control_bulk:
call usb_subscribe_control
@@:
; 4d. Release queue lock.
pop ecx
call mutex_unlock
; 4e. Notify the USB thread about new work.
push ebx esi edi
call usb_wakeup
pop edi esi ebx
; That's all for now. To be continued in usb_pipe_disabled.
; 5. Release pipe lock acquired at step 1 and return.
.nothing:
lea ecx, [ebx+usb_pipe.Lock]
call mutex_unlock
pop esi ebx
ret 4
endp
; Part of API for drivers, see documentation for USBGetParam. ; Part of API for drivers, see documentation for USBGetParam.
proc usb_get_param proc usb_get_param
virtual at esp virtual at esp

View File

@ -33,6 +33,19 @@ USB_INTERFACE_POWER_DESCR = 8
; read to the debug board. ; read to the debug board.
USB_DUMP_DESCRIPTORS = 1 USB_DUMP_DESCRIPTORS = 1
; According to the USB specification (9.2.6.3),
; any device must response to SET_ADDRESS in 50 ms, or 5 timer ticks.
; Of course, our world is far from ideal.
; I have seen devices that just NAK everything when being reset from working
; state, but start to work after second reset.
; Our strategy is as follows: give 2 seconds for the first attempt,
; this should be enough for normal devices and not too long to detect buggy ones.
; If the device continues NAKing, reset it and retry several times,
; doubling the interval: 2s -> 4s -> 8s -> 16s. Give up after that.
; Numbers are quite arbitrary.
TIMEOUT_SET_ADDRESS_INITIAL = 200
TIMEOUT_SET_ADDRESS_LAST = 1600
; ============================================================================= ; =============================================================================
; ================================ Structures ================================= ; ================================ Structures =================================
; ============================================================================= ; =============================================================================
@ -179,21 +192,36 @@ ends
; out: eax = 0 <=> failed, the caller should disable the port. ; out: eax = 0 <=> failed, the caller should disable the port.
proc usb_new_device proc usb_new_device
push ebx edi ; save used registers to be stdcall push ebx edi ; save used registers to be stdcall
; 1. Allocate resources. Any device uses the following resources: ; 1. Check whether we're here because we were trying to reset
; already-registered device in hope to fix something serious.
; If so, skip allocation and go to 6.
movzx eax, [esi+usb_controller.ResettingPort]
mov edx, [esi+usb_controller.ResettingHub]
test edx, edx
jz .test_roothub
mov edx, [edx+usb_hub.ConnectedDevicesPtr]
mov ebx, [edx+eax*4]
jmp @f
.test_roothub:
mov ebx, [esi+usb_controller.DevicesByPort+eax*4]
@@:
test ebx, ebx
jnz .try_set_address
; 2. Allocate resources. Any device uses the following resources:
; - device address in the bus ; - device address in the bus
; - memory for device data ; - memory for device data
; - pipe for zero endpoint ; - pipe for zero endpoint
; If some allocation fails, we must undo our actions. Closing the pipe ; If some allocation fails, we must undo our actions. Closing the pipe
; is a hard task, so we avoid it and open the pipe as the last resource. ; is a hard task, so we avoid it and open the pipe as the last resource.
; The order for other two allocations is quite arbitrary. ; The order for other two allocations is quite arbitrary.
; 1a. Allocate a bus address. ; 2a. Allocate a bus address.
push ecx push ecx
call usb_set_address_request call usb_set_address_request
pop ecx pop ecx
; 1b. If failed, just return zero. ; 2b. If failed, just return zero.
test eax, eax test eax, eax
jz .nothing jz .nothing
; 1c. Allocate memory for device data. ; 2c. Allocate memory for device data.
; For now, we need sizeof.usb_device_data and extra 8 bytes for GET_DESCRIPTOR ; For now, we need sizeof.usb_device_data and extra 8 bytes for GET_DESCRIPTOR
; input and output, see usb_after_set_address. Later we will reallocate it ; input and output, see usb_after_set_address. Later we will reallocate it
; to actual size needed for descriptors. ; to actual size needed for descriptors.
@ -201,10 +229,10 @@ proc usb_new_device
push ecx push ecx
call malloc call malloc
pop ecx pop ecx
; 1d. If failed, free the bus address and return zero. ; 2d. If failed, free the bus address and return zero.
test eax, eax test eax, eax
jz .nomemory jz .nomemory
; 1e. Open pipe for endpoint zero. ; 2e. Open pipe for endpoint zero.
; For now, we do not know the actual maximum packet size; ; For now, we do not know the actual maximum packet size;
; for full-speed devices it can be any of 8, 16, 32, 64 bytes, ; for full-speed devices it can be any of 8, 16, 32, 64 bytes,
; low-speed devices must have 8 bytes, high-speed devices must have 64 bytes. ; low-speed devices must have 8 bytes, high-speed devices must have 64 bytes.
@ -227,12 +255,14 @@ proc usb_new_device
; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes. ; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes.
xchg eax, ebx xchg eax, ebx
pop eax pop eax
; 1f. If failed, free the memory, the bus address and return zero. ; 2f. If failed, free the memory, the bus address and return zero.
test ebx, ebx test ebx, ebx
jz .freememory jz .freememory
; 2. Store pointer to device data in the pipe structure. ; 3. Store pointer to device data in the pipe structure.
mov [ebx+usb_pipe.DeviceData], eax mov [ebx+usb_pipe.DeviceData], eax
; 3. Init device data, using usb_controller.Resetting* variables. ; 4. Init device data, using usb_controller.Resetting* variables.
mov [eax+usb_device_data.Timer], edi
mov dword [eax+usb_device_data.DeviceDescriptor], TIMEOUT_SET_ADDRESS_INITIAL
mov [eax+usb_device_data.TTHub], edi mov [eax+usb_device_data.TTHub], edi
mov [eax+usb_device_data.TTPort], 0 mov [eax+usb_device_data.TTPort], 0
mov [eax+usb_device_data.NumInterfaces], edi mov [eax+usb_device_data.NumInterfaces], edi
@ -268,7 +298,7 @@ proc usb_new_device
mov [eax+usb_device_data.Port], cl mov [eax+usb_device_data.Port], cl
mov edx, [esi+usb_controller.ResettingHub] mov edx, [esi+usb_controller.ResettingHub]
mov [eax+usb_device_data.Hub], edx mov [eax+usb_device_data.Hub], edx
; 4. Store pointer to the config pipe in the hub data. ; 5. Store pointer to the config pipe in the hub data.
; Config pipe serves as device identifier. ; Config pipe serves as device identifier.
; Root hubs use the array inside usb_controller structure, ; Root hubs use the array inside usb_controller structure,
; non-root hubs use the array immediately after usb_hub structure. ; non-root hubs use the array immediately after usb_hub structure.
@ -281,16 +311,29 @@ proc usb_new_device
mov [esi+usb_controller.DevicesByPort+ecx*4], ebx mov [esi+usb_controller.DevicesByPort+ecx*4], ebx
@@: @@:
call usb_reinit_pipe_list call usb_reinit_pipe_list
; 5. Issue SET_ADDRESS control request, using buffer filled in step 1a. ; 6. Issue SET_ADDRESS control request, using buffer filled in step 2a.
; Use the return value from usb_control_async as our return value; ; 6a. Configure timer to force reset after timeout.
; if it is zero, then something has failed. ; Note: we can't use self-destructing timer, because we need to be able to cancel it,
; and for self-destructing timer we could have race condition in cancelling/destructing.
; DEBUGF 1,'K : pipe %x\n',ebx
.try_set_address:
xor edi, edi
mov edx, [ebx+usb_pipe.DeviceData]
stdcall timer_hs, [edx+usb_device_data.DeviceDescriptor], 7FFFFFFFh, usb_abort_pipe, ebx
test eax, eax
jz .nothing
mov edx, [ebx+usb_pipe.DeviceData]
mov [edx+usb_device_data.Timer], eax
; 6b. If it succeeded, setup timer to configure wait timeout.
lea eax, [esi+usb_controller.SetAddressBuffer] lea eax, [esi+usb_controller.SetAddressBuffer]
stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi
; Use the return value from usb_control_async as our return value;
; if it is zero, then something has failed.
.nothing: .nothing:
; 6. Return. ; 7. Return.
pop edi ebx ; restore used registers to be stdcall pop edi ebx ; restore used registers to be stdcall
ret ret
; Handlers of failures in steps 1b, 1d, 1f. ; Handlers of failures in steps 2b, 2d, 2f.
.freememory: .freememory:
call free call free
jmp .freeaddr jmp .freeaddr
@ -349,16 +392,23 @@ endp
; Note that USB stack uses esi = pointer to usb_controller. ; Note that USB stack uses esi = pointer to usb_controller.
proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
push ebx ; save ebx to be stdcall push ebx ; save ebx to be stdcall
; Load data to registers for further references.
mov ebx, [pipe] mov ebx, [pipe]
; 1. In any case, cancel the timer.
mov eax, [ebx+usb_pipe.DeviceData]
stdcall cancel_timer_hs, [eax+usb_device_data.Timer]
mov eax, [ebx+usb_pipe.DeviceData]
mov [eax+usb_device_data.Timer], 0
; Load data to registers for further references.
mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] mov ecx, dword [esi+usb_controller.SetAddressBuffer+2]
mov eax, [esi+usb_controller.HardwareFunc] mov eax, [esi+usb_controller.HardwareFunc]
; 1. Check whether the device has accepted new address. If so, proceed to 2. ; 2. Check whether the device has accepted new address. If so, proceed to 3.
; Otherwise, go to 3. ; Otherwise, go to 4 if killed by usb_set_address_timeout or to 5 otherwise.
cmp [status], USB_STATUS_CANCELLED
jz .timeout
cmp [status], 0 cmp [status], 0
jnz .error jnz .error
; 2. Address accepted. ; 3. Address accepted.
; 2a. The controller-specific structure for the control pipe still uses ; 3a. The controller-specific structure for the control pipe still uses
; zero address. Call the controller-specific function to change it to ; zero address. Call the controller-specific function to change it to
; the actual address. ; the actual address.
; Note that the hardware could cache the controller-specific structure, ; Note that the hardware could cache the controller-specific structure,
@ -367,25 +417,49 @@ proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, l
; be safe to continue. ; be safe to continue.
; dbgstr 'address set in device' ; dbgstr 'address set in device'
call [eax+usb_hardware_func.SetDeviceAddress] call [eax+usb_hardware_func.SetDeviceAddress]
; 2b. If the port is in non-root hub, clear 'reset in progress' flag. ; 3b. If the port is in non-root hub, clear 'reset in progress' flag.
; In any case, proceed to 4. ; In any case, proceed to 6.
mov eax, [esi+usb_controller.ResettingHub] mov eax, [esi+usb_controller.ResettingHub]
test eax, eax test eax, eax
jz .return jz .return
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
.return: .return:
; 4. Address configuration done, we can proceed with other ports. ; 6. Address configuration done, we can proceed with other ports.
; Call the worker function for that. ; Call the worker function for that.
call usb_test_pending_port call usb_test_pending_port
.wakeup:
push esi edi
call usb_wakeup
pop edi esi
.nothing: .nothing:
pop ebx ; restore ebx to be stdcall pop ebx ; restore ebx to be stdcall
ret ret
.timeout:
; 4. Device continues to NAK the request. Reset it and retry.
mov edx, [ebx+usb_pipe.DeviceData]
mov ecx, [edx+usb_device_data.DeviceDescriptor]
add ecx, ecx
cmp ecx, TIMEOUT_SET_ADDRESS_LAST
ja .error
mov [edx+usb_device_data.DeviceDescriptor], ecx
dbgstr 'Timeout in USB device initialization, trying to reset...'
cmp [esi+usb_controller.ResettingHub], 0
jz .reset_roothub
push esi
mov esi, [esi+usb_controller.ResettingHub]
call usb_hub_initiate_reset
pop esi
jmp .nothing
.reset_roothub:
movzx ecx, [esi+usb_controller.ResettingPort]
call [eax+usb_hardware_func.InitiateReset]
jmp .wakeup
.error: .error:
; 3. Device error: device not responding, disconnect etc. ; 5. Device error: device not responding, disconnect etc.
DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status] DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status]
; 3a. The address has not been accepted. Mark it as free. ; 5a. The address has not been accepted. Mark it as free.
bts dword [esi+usb_controller.ExistingAddresses], ecx bts dword [esi+usb_controller.ExistingAddresses], ecx
; 3b. Disable the port with bad device. ; 5b. Disable the port with bad device.
; For the root hub, call the controller-specific function and go to 6. ; For the root hub, call the controller-specific function and go to 6.
; For non-root hubs, let the hub code do its work and return (the request ; For non-root hubs, let the hub code do its work and return (the request
; could take some time, the hub code is responsible for proceeding). ; could take some time, the hub code is responsible for proceeding).

View File

@ -186,6 +186,7 @@ USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
USB_STATUS_CLOSED = 16 ; pipe closed, either explicitly with USBClosePipe USB_STATUS_CLOSED = 16 ; pipe closed, either explicitly with USBClosePipe
; or due to device disconnect ; or due to device disconnect
USB_STATUS_CANCELLED = 17 ; transfer cancelled with USBAbortPipe
If several transfers are queued for the same pipe, their callback functions If several transfers are queued for the same pipe, their callback functions
are called in the same order as they were queued. are called in the same order as they were queued.
@ -194,6 +195,11 @@ implicitly due to device disconnect, all callback functions are called
with USB_STATUS_CLOSED. The call to DeviceDisconnected() occurs after with USB_STATUS_CLOSED. The call to DeviceDisconnected() occurs after
all callbacks. all callbacks.
void __stdcall USBAbortPipe(void* pipe);
Initiates cancellation of all active transfers for the given pipe. Asynchronous.
When a transfer will be cancelled, the associated callback function
will be called with USB_STATUS_CANCELLED.
void* __stdcall USBGetParam(void* pipe0, int param); void* __stdcall USBGetParam(void* pipe0, int param);
Returns miscellaneous parameters of the device. Returns miscellaneous parameters of the device.
pipe0 is the pointer to the config pipe. pipe0 is the pointer to the config pipe.

View File

@ -1,249 +0,0 @@
Когда ядро ​​обнаруживает подключенное устройство USB, оно настраивает его
согласно USB-протокола - SET_ADDRESS + SET_CONFIGURATION. Всегда
устанавливается первая конфигурация. Ядро также читает дескриптор
устройства, чтобы показать некоторую информацию, читает и анализирует
дескриптор конфигурации. Для каждого интерфейса ядро будет искать класс этого
интерфейса и попытается загрузить соответствующий драйвер COFF. В настоящее
время соответствие кодов классов и имен драйверов жестко прописано в коде ядра
и выглядит следующим образом:
3 = usbhid.obj,
7 = usbprint.obj,
8 = usbstor.obj,
9 = поддерживаются самим ядром,
другие = usbother.obj.
Драйвер должен быть стандартным драйвером в формате COFF, экспортирующим
процедуру под названием "START" и переменную "version". Загрузчик вызывает
процедуру "START" как STDCALL с одним параметром DRV_ENTRY = 1. При завершении
работы системы, если инициализация драйвера была успешна, "START" процедуру
также вызывает код остановки системы с одним параметром DRV_EXIT = -1.
Драйвер должен зарегистрировать себя в качестве драйвера USB в процедуре
"START". Это делается путем вызова экспортируемой ядром функции RegUSBDriver и
возврата её результата в качестве результата "START" процедуры.
void* __stdcall RegUSBDriver(
const char* name,
void* handler,
const USBFUNC* usbfunc
);
Параметр 'name' должен совпадать с именем драйвера, например "usbhid" для
usbhid.obj.
Параметр 'handler' является необязательным. Если он не NULL, то он должен
указывать на стандартный обработчик IOCTL интерфейса, как в обычном (не-USB)
драйвере.
Параметр "Usbfunc" представляет собой указатель на следующую структуру:
struc USBFUNC
{
.strucsize dd ? ; размер структуры, включая это поле
.add_device dd ? ; указатель на AddDevice процедуру в драйвере
; (необходимо)
.device_disconnect dd ? ; указатель на DeviceDisconnected процедуру в драйвере
; опционально, может быть NULL
; В будущем могут быть добавлены другие функции
}
Драйвер ДОЛЖЕН реализовать функцию:
void* __stdcall AddDevice(
void* pipe0,
void* configdescr,
void* interfacedescr
);
Параметр "Pipe0" - хэндл контрольного канала для нулевой конечной точки
устройства. Он может быть использован в качестве аргумента для
USBControlTransferAsync (см. далее).
Параметр 'configdescr' указывает на дескриптор конфигурации и все связанные с
ним данные, представленные так, как их возвращает запрос GET_DESCRIPTOR.
Полный размер данных содержится в поле Length самого дескриптора.
(см. USB2.0 spec.)
Параметр 'interfacedescr' указывает на дескриптор интерфейса инициализируемого
в данный момент. Это указатель на данные находящиеся внутри структуры
"configdescr". (Помним, что структура INTERFACE_DESCRIPTOR, находится внутри
структуры CONFIGURATION_DESCRIPTOR. См. USB2.0 Spec.) Обратите внимание, что
одно устройство может реализовывать много интерфейсов и AddDevice может быть
вызвана несколько раз с одним "configdescr" но разными "interfacedescr".
Возвращенное значение NULL показывает, что инициализация не была успешной.
Любое другое значение означает инициализацию устройства. Ядро не делает попыток
как-то интерпретировать это значение. Это может быть, например, указатель на
внутренние данные драйвера в памяти, выделенной с помощью Kmalloc или индексом
в какой-то своей таблице. (Помните, что Kmalloc() НЕ stdcall-функция! Она
портит регистр ebx!)
Драйвер МОЖЕТ реализовать функцию:
void __stdcall DeviceDisconnected(
void* devicedata
);
Если данная функция реализована, то ядро вызывает её, когда устройство
отключено, посылая ей в качестве параметра "devicedata" то, что было возвращено
ему функцией "AddDevice" при старте драйвера.
Драйвер может использовать следующие функции экспортируемые ядром:
void* __stdcall USBOpenPipe(
void* pipe0,
int endpoint,
int maxpacketsize,
int type,
int interval
);
Параметр "Pipe0" - хэндл контрольного канала для нулевой конечной точки
устройства. Используется для идентификации устройства.
Параметр "endpoint" номер конечной точки USB. Младшие 4 бита, собственно, номер
точки, а бит 7 имеет следующее значение: 0 - для OUT точки, 1 - для IN точки.
Остальные биты должны быть равны нулю.
Параметр "maxpacketsize" устанавливает максимальный размер пакета для канала.
Параметр "type" устанавливает тип передачи для конечной точки, как это прописано
в USB спецификации:
0 = control,
1 = isochronous (сейчас не поддерживается),
2 = bulk,
3 = interrupt.
Параметр "interval" игнорируется для control и bulk передач. Для конечных точек
по прерываниям устанавливает периодичность опроса в миллисекундах.
Функция возвращает хэндл канала при успешном его открытии либо NULL при ошибке.
Хэндл канала обращается в NULL когда:
а) канал будет явно закрыт функцией USBClosePipe (см. ниже);
б) была выполнена предоставленная драйвером функция "DeviceDisconnected".
void __stdcall USBClosePipe(
void* pipe
);
Освобождает все ресурсы, связанные с выбранным каналом. Единственный параметр -
указатель на хэндл, который был возвращен функцией USBOpenPipe при открытии
канала. Когда устройство отключается, все связанные с ним каналы закрываются
ядром; нет необходимости в самостоятельном вызове этой функции.
void* __stdcall USBNormalTransferAsync(
void* pipe,
void* buffer,
int size,
void* callback,
void* calldata,
int flags
);
void* __stdcall USBControlTransferAsync(
void* pipe,
void* setup,
void* buffer,
int size,
void* callback,
void* calldata,
int flags
);
Первая функция ставит в очередь bulk или interrupt передачу для выбранного
канала. Тип и направление передачи фиксированы для bulk и interrupt типов
конечных точек, как это было выбрано функцией USBOpenPipe.
Вторая функция ставит в очередь control передачу для выбранного канала.
Направление этой передачи определяется битом 7 байта 0 пакета "setup"
(0 - для OUT, 1 - для IN передачи). Эта функция возвращает управление немедленно.
По окончании передачи вызывается функция "callback" заданная как аргумент
USB______TransferAsync.
Параметр "pipe" - хэндл, возвращенный функцией USBOpenPipe.
Параметр 'setup' функции USBControlTransferAsync указывает на 8-байтный
конфигурационный пакет (см. USB2.0 Spec).
Параметр "buffer" - это указатель на буфер. Для IN передач он будет заполнен
принятыми данными. Для OUT передач он должен быть заполнен данными, которые мы
хотим передать. Указатель может быть NULL для пустых передач, либо для передач
control, если дополнительных данных не требуется.
Параметр "size" - это размер данных для передачи. Он может быть равен 0 для
пустых передач, либо для передач control, если дополнительных данных не требуется.
Параметр "callback" - это указатель на функцию, которая будет вызвана по
окончании передачи.
Параметр "calldata" будет передан функции "callback" вызываемой по окончании
передачи. Например, он может быть NULL или указывать на данные устройства или
указывать на данные используемые как дополнительные параметры, передаваемые от
вызывающей USB_____TransferAsync функции в callback функцию.
Другие данные, связанные с передачей, могут быть помещены до буфера (по смещению)
или после него. Они могут быть использованы из callback-функции, при необходимости.
Параметр "flags" - это битовое поле. Бит 0 игнорируется для OUT передач. Для IN
передач он означает, может ли устройство передать меньше данных (бит=1), чем
определено в "size" или нет (бит=0). Остальные биты не используются и должны
быть равны 0.
Возвращаемое функциями значение равно NULL в случае ошибки и не NULL если
передача успешно поставлена в очередь. Если происходит ошибка при передаче, то
callback функция будет об этом оповещена.
void __stdcall CallbackFunction(
void* pipe,
int status,
void* buffer,
int length,
void* calldata
);
Параметры 'pipe', 'buffer', 'calldata' значат то же, что и для
USB_____TransferAsync.
Параметр "length" это счетчик переданных байт. Для control передач он отражает
дополнительные 8 байт этапа SETUP. Т.е. 0 означает ошибку на этапе SETUP, а
"size"+8 успешную передачу.
Параметр "status" не равен 0 в случае ошибки:
USB_STATUS_OK = 0 ; без ошибок
USB_STATUS_CRC = 1 ; ошибка контрольной суммы
USB_STATUS_BITSTUFF = 2 ; ошибка инверсии битов (bitstuffing)
USB_STATUS_TOGGLE = 3 ; data toggle mismatch
; (Нарушение последовательности DAT0/DAT1)
USB_STATUS_STALL = 4 ; устройство возвратило STALL статус (остановлено)
USB_STATUS_NORESPONSE = 5 ; устройство не отвечает
USB_STATUS_PIDCHECK = 6 ; ошибка в поле PacketID (PID)
USB_STATUS_WRONGPID = 7 ; неожидаемое PacketID (PID) значение
USB_STATUS_OVERRUN = 8 ; слишком много данных от конечной точки
USB_STATUS_UNDERRUN = 9 ; слишком мало данных от конечной точки
USB_STATUS_BUFOVERRUN = 12 ; переполнение внутреннего буфера контроллера
; возможна только для изохронных передач
USB_STATUS_BUFUNDERRUN = 13 ; опустошение внутреннего буфера контроллера
; возможна только для изохронных передач
USB_STATUS_CLOSED = 16 ; канал закрыт либо через ClosePipe, либо в
; результате отключения устройства
Если несколько передач были поставлены в очередь для одного канала, то callback
функции для них будут вызываться в порядке постановки передач в очередь.
Если канал был закрыт ввиду USBClosePipe или отключения устройства, то callback
функции (если очередь передач не пуста) получат USB_STATUS_CLOSED.
Вызов DeviceDisconnected() последует после отработки всех оставшихся в очереди
callback функций.
void* __stdcall USBGetParam(void* pipe0, int param);
Возвращает указатель на некоторые параметры устройства запомненные ядром при
инициализации первой конфигурации. Не передает ничего устройству по шине.
pipe0 - хэндл контрольного канала для нулевой конечной точки устройства.
param - выбор возвращаемого параметра:
0 - возвратить указатель на дескриптор устройства;
1 - возвратить указатель на дескриптор конфигурации;
2 - возвратить режим шины устройства:
USB_SPEED_FS = 0 ; full-speed
USB_SPEED_LS = 1 ; low-speed
USB_SPEED_HS = 2 ; high-speed