mirror of
https://github.com/KolibriOS/kolibrios.git
synced 2024-12-27 00:39:41 +03:00
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:
parent
49a6c736a9
commit
241079c114
@ -156,8 +156,6 @@ Overlay ehci_hardware_td ?
|
||||
; Working area for the current TD, if there is any.
|
||||
; When TD is retired, it is written to that TD and Overlay is loaded
|
||||
; from the new TD, if any.
|
||||
BaseList dd ?
|
||||
; Pointer to head of the corresponding pipe list.
|
||||
ends
|
||||
|
||||
; This structure describes the static head of every list of pipes.
|
||||
@ -293,6 +291,8 @@ ehci_hardware_func:
|
||||
dd ehci_alloc_transfer
|
||||
dd ehci_insert_transfer
|
||||
dd ehci_new_device
|
||||
dd ehci_disable_pipe
|
||||
dd ehci_enable_pipe
|
||||
ehci_name db 'EHCI',0
|
||||
endg
|
||||
|
||||
@ -998,7 +998,7 @@ end virtual
|
||||
jz .return0
|
||||
mov word [edi+ehci_pipe.Flags-sizeof.ehci_pipe], ax
|
||||
.insert:
|
||||
mov [edi+ehci_pipe.BaseList-sizeof.ehci_pipe], edx
|
||||
mov [edi+usb_pipe.BaseList], edx
|
||||
; Insert to the head of the corresponding list.
|
||||
; Note: inserting to the head guarantees that the list traverse in
|
||||
; 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
|
||||
.interrupt_common:
|
||||
@@:
|
||||
mov edx, [ebx+usb_pipe.NextVirt]
|
||||
mov eax, [ebx+usb_pipe.PrevVirt]
|
||||
mov [edx+usb_pipe.PrevVirt], eax
|
||||
mov [eax+usb_pipe.NextVirt], edx
|
||||
ret
|
||||
endp
|
||||
|
||||
; 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
|
||||
sub edx, eax
|
||||
sub edx, ecx
|
||||
cmp edx, sizeof.ehci_controller
|
||||
mov edx, [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe]
|
||||
jb .prev_is_static
|
||||
mov [eax+ehci_pipe.NextQH-sizeof.ehci_pipe], edx
|
||||
mov [ecx+ehci_pipe.NextQH-sizeof.ehci_pipe], eax
|
||||
ret
|
||||
.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
|
||||
endp
|
||||
|
||||
|
@ -298,7 +298,7 @@ proc ehci_hs_interrupt_list_unlink
|
||||
imul eax, ecx
|
||||
movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe]
|
||||
; get target list
|
||||
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe]
|
||||
mov edx, [ebx+usb_pipe.BaseList]
|
||||
; update bandwidth
|
||||
.dec_bandwidth:
|
||||
shr ecx, 1
|
||||
@ -732,7 +732,7 @@ proc ehci_fs_interrupt_list_unlink
|
||||
mov edi, esp
|
||||
call tt_fill_split_info
|
||||
; get target list
|
||||
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe]
|
||||
mov edx, [ebx+usb_pipe.BaseList]
|
||||
; update bandwidth for Start-Split
|
||||
mov eax, [edi+usb_split_info.ssplit_bandwidth]
|
||||
xor ecx, ecx
|
||||
|
@ -64,7 +64,7 @@ Flags dd ?
|
||||
; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of
|
||||
; the endpoint within the function.
|
||||
; 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.
|
||||
; For CONTROL endpoints, the transfer direction is different
|
||||
; for different transfers, so the value of this field is 0
|
||||
@ -322,6 +322,8 @@ ohci_hardware_func:
|
||||
dd ohci_alloc_transfer
|
||||
dd ohci_insert_transfer
|
||||
dd ohci_new_device
|
||||
dd ohci_disable_pipe
|
||||
dd ohci_enable_pipe
|
||||
ohci_name db 'OHCI',0
|
||||
endg
|
||||
|
||||
@ -1014,6 +1016,7 @@ end virtual
|
||||
; Inserting to tail would work as well,
|
||||
; but let's be consistent with other controllers.
|
||||
.insert:
|
||||
mov [edi+usb_pipe.BaseList], edx
|
||||
mov ecx, [edx+usb_pipe.NextVirt]
|
||||
mov [edi+usb_pipe.NextVirt], ecx
|
||||
mov [edi+usb_pipe.PrevVirt], edx
|
||||
@ -1614,17 +1617,41 @@ proc ohci_unlink_pipe
|
||||
mov eax, [ebx+ohci_pipe.Flags-sizeof.ohci_pipe]
|
||||
bt eax, 13
|
||||
setc cl
|
||||
bt eax, 11
|
||||
bt eax, 12
|
||||
setc ch
|
||||
shr eax, 16
|
||||
stdcall usb1_interrupt_list_unlink, eax, ecx
|
||||
@@:
|
||||
mov edx, [ebx+usb_pipe.NextVirt]
|
||||
mov eax, [ebx+usb_pipe.PrevVirt]
|
||||
mov [edx+usb_pipe.PrevVirt], eax
|
||||
mov [eax+usb_pipe.NextVirt], edx
|
||||
mov edx, [ebx+ohci_pipe.NextED-sizeof.ohci_pipe]
|
||||
mov [eax+ohci_pipe.NextED-sizeof.ohci_pipe], edx
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure temporarily removes the given pipe from hardware queue,
|
||||
; keeping it in software lists.
|
||||
; 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
|
||||
endp
|
||||
|
||||
|
@ -274,6 +274,8 @@ uhci_hardware_func:
|
||||
dd uhci_alloc_transfer
|
||||
dd uhci_insert_transfer
|
||||
dd uhci_new_device
|
||||
dd uhci_disable_pipe
|
||||
dd uhci_enable_pipe
|
||||
uhci_name db 'UHCI',0
|
||||
endg
|
||||
|
||||
@ -1133,7 +1135,6 @@ proc uhci_fix_toggle
|
||||
jnz .loop
|
||||
; 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)
|
||||
or dword [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock], eax
|
||||
; 6. Unlock the transfer queue.
|
||||
invoke MutexUnlock
|
||||
.nothing:
|
||||
@ -1461,6 +1462,7 @@ end virtual
|
||||
test edx, edx
|
||||
jz .return0
|
||||
.insert:
|
||||
mov [edi+usb_pipe.BaseList], edx
|
||||
; Insert to the head of the corresponding list.
|
||||
; Note: inserting to the head guarantees that the list traverse in
|
||||
; uhci_process_updated_schedule, once started, will not interact with new pipes.
|
||||
@ -1505,17 +1507,44 @@ proc uhci_unlink_pipe
|
||||
shr eax, 21
|
||||
stdcall usb1_interrupt_list_unlink, eax, ecx
|
||||
@@:
|
||||
; Note: we need to ensure that NextVirt field of the pipe is not modified;
|
||||
; this procedure can be called while uhci_process_updated_schedule processes
|
||||
; the same pipe, and it needs a correct NextVirt field to continue.
|
||||
mov edx, [ebx+usb_pipe.NextVirt]
|
||||
mov eax, [ebx+usb_pipe.PrevVirt]
|
||||
mov [edx+usb_pipe.PrevVirt], eax
|
||||
mov [eax+usb_pipe.NextVirt], edx
|
||||
; Note: eax could be either usb_pipe or usb_static_ep;
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure temporarily removes the given pipe from hardware queue,
|
||||
; keeping it in software lists.
|
||||
; esi -> usb_controller, ebx -> usb_pipe
|
||||
proc uhci_disable_pipe
|
||||
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.
|
||||
mov edx, [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe]
|
||||
mov [eax+uhci_pipe.NextQH-sizeof.uhci_pipe], edx
|
||||
mov [edx+uhci_pipe.NextQH-sizeof.uhci_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 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
|
||||
endp
|
||||
|
||||
|
@ -167,12 +167,7 @@ end virtual
|
||||
mov eax, [.maxpacket]
|
||||
mov ecx, dword [.lowspeed]
|
||||
call calc_usb1_bandwidth
|
||||
; find list header
|
||||
mov edx, ebx
|
||||
@@:
|
||||
mov edx, [edx+usb_pipe.NextVirt]
|
||||
cmp [edx+usb_pipe.Controller], esi
|
||||
jz @b
|
||||
mov edx, [ebx+usb_pipe.BaseList]
|
||||
; subtract pipe bandwidth
|
||||
sub [edx+usb_static_ep.Bandwidth], eax
|
||||
ret 8
|
||||
|
@ -6,7 +6,7 @@
|
||||
; =============================================================================
|
||||
; Version of all structures related to host controllers.
|
||||
; 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
|
||||
; 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
|
||||
; either explicitly with USBClosePipe
|
||||
; or implicitly due to device disconnect
|
||||
USB_STATUS_CANCELLED = 17 ; transfer cancelled with USBAbortPipe
|
||||
|
||||
; Possible speeds of USB devices
|
||||
USB_SPEED_FS = 0 ; full-speed
|
||||
@ -63,6 +64,9 @@ USB_FLAG_CAN_FREE = 2
|
||||
USB_FLAG_EXTRA_WAIT = 4
|
||||
; The pipe was in wait list, while another event occured;
|
||||
; 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
|
||||
|
||||
; =============================================================================
|
||||
@ -136,6 +140,14 @@ NewDevice dd ?
|
||||
; Initiate configuration of a new device (create pseudo-pipe describing that
|
||||
; device and call usb_new_device).
|
||||
; 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
|
||||
|
||||
; pointers to kernel API functions that are called from *HCI-drivers
|
||||
@ -307,6 +319,8 @@ NextVirt dd ?
|
||||
PrevVirt dd ?
|
||||
; Previous endpoint in the processing list.
|
||||
; 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
|
||||
; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt
|
||||
@ -427,6 +441,8 @@ DeviceDescrSize db ?
|
||||
; Size of device descriptor.
|
||||
Speed db ?
|
||||
; Device speed, one of USB_SPEED_*.
|
||||
Timer dd ?
|
||||
; Handle of timer that handles request timeout.
|
||||
NumInterfaces dd ?
|
||||
; Number of interfaces.
|
||||
ConfigDataSize dd ?
|
||||
|
@ -114,7 +114,7 @@ proc get_phys_addr
|
||||
ret
|
||||
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
|
||||
; needs to be synchronized. When it will be known that the cache is updated,
|
||||
; usb_subscription_done procedure will be called.
|
||||
@ -128,6 +128,17 @@ proc usb_subscribe_control
|
||||
ret
|
||||
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.
|
||||
; Continues process of device enumeration based on when it was delayed
|
||||
; due to call to usb_subscribe_control.
|
||||
@ -254,13 +265,18 @@ proc usb_process_one_wait_list
|
||||
mov [esi+usb_controller.WaitPipeListAsync+edx], ebx
|
||||
jmp .continue
|
||||
.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
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
|
||||
jz .nodisconnect
|
||||
call usb_pipe_closed
|
||||
jmp .continue
|
||||
.nodisconnect:
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED
|
||||
jz .nodisabled
|
||||
call usb_pipe_disabled
|
||||
jmp .continue
|
||||
.nodisabled:
|
||||
call usb_subscription_done
|
||||
.continue:
|
||||
; 8. Restore edx and next pipe saved in step 5 and continue the loop.
|
||||
|
@ -13,6 +13,11 @@ else
|
||||
stdcall arg
|
||||
end if
|
||||
}
|
||||
if USB_STDCALL_VERIFY
|
||||
STDCALL_VERIFY_EXTRA = 20h
|
||||
else
|
||||
STDCALL_VERIFY_EXTRA = 0
|
||||
end if
|
||||
|
||||
; Initialization of usb_static_ep structure,
|
||||
; called from controller-specific initialization; edi -> usb_static_ep
|
||||
@ -238,8 +243,17 @@ proc usb_close_pipe_nolock
|
||||
call mutex_lock
|
||||
push ecx
|
||||
; 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]
|
||||
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.
|
||||
pop ecx
|
||||
call mutex_unlock
|
||||
@ -262,36 +276,66 @@ proc usb_close_pipe_nolock
|
||||
ret
|
||||
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
|
||||
; corresponding wait list. It means that the hardware has fully forgot about it.
|
||||
; ebx -> usb_pipe, esi -> usb_controller
|
||||
proc usb_pipe_closed
|
||||
push edi
|
||||
mov edi, [esi+usb_controller.HardwareFunc]
|
||||
; 1. Loop over all transfers, calling the driver with USB_STATUS_CLOSED
|
||||
; and freeing all descriptors.
|
||||
; 1. Notify all registered callbacks with status USB_STATUS_CLOSED, if any,
|
||||
; and free all transfer descriptors, including the last one.
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_lock
|
||||
mov edx, [ebx+usb_pipe.LastTD]
|
||||
test edx, edx
|
||||
jz .no_transfer
|
||||
mov edx, [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
|
||||
mov eax, [edx+usb_gtd.NextVirt]
|
||||
push edx
|
||||
stdcall_verify ecx, ebx, USB_STATUS_CLOSED, \
|
||||
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData]
|
||||
pop edx
|
||||
.no_callback:
|
||||
push [edx+usb_gtd.NextVirt]
|
||||
stdcall [edi+usb_hardware_func.FreeTD], edx
|
||||
pop edx
|
||||
jmp .transfer_loop
|
||||
.transfer_done:
|
||||
stdcall [edi+usb_hardware_func.FreeTD], edx
|
||||
push eax
|
||||
call mutex_unlock
|
||||
push USB_STATUS_CLOSED
|
||||
call usb_pipe_aborted
|
||||
; It is safe to free LastTD here:
|
||||
; usb_*_transfer_async do not enqueue new transfers if USB_FLAG_CLOSED is set.
|
||||
stdcall [edi+usb_hardware_func.FreeTD], [ebx+usb_pipe.LastTD]
|
||||
jmp @f
|
||||
.no_transfer:
|
||||
call mutex_unlock
|
||||
@@:
|
||||
; 2. Decrement number of pipes for the device.
|
||||
; If this pipe is the last pipe, go to 5.
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
@ -342,14 +386,23 @@ proc usb_pipe_closed
|
||||
dec eax
|
||||
jnz .notify_loop
|
||||
.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]
|
||||
test eax, eax
|
||||
jz @f
|
||||
bts [esi+usb_controller.ExistingAddresses], eax
|
||||
@@:
|
||||
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.
|
||||
; Free the remaining pipes.
|
||||
mov eax, [ebx+usb_pipe.DeviceData]
|
||||
@ -366,15 +419,74 @@ proc usb_pipe_closed
|
||||
.free_done:
|
||||
stdcall [edi+usb_hardware_func.FreePipe], ebx
|
||||
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
|
||||
call free
|
||||
; 9. Return.
|
||||
; 10. Return.
|
||||
.nothing:
|
||||
pop edi
|
||||
ret
|
||||
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.
|
||||
proc usb_normal_transfer_async stdcall uses ebx edi,\
|
||||
pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword
|
||||
@ -508,6 +620,69 @@ endl
|
||||
ret
|
||||
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.
|
||||
proc usb_get_param
|
||||
virtual at esp
|
||||
|
@ -33,6 +33,19 @@ USB_INTERFACE_POWER_DESCR = 8
|
||||
; read to the debug board.
|
||||
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 =================================
|
||||
; =============================================================================
|
||||
@ -179,21 +192,36 @@ ends
|
||||
; out: eax = 0 <=> failed, the caller should disable the port.
|
||||
proc usb_new_device
|
||||
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
|
||||
; - memory for device data
|
||||
; - pipe for zero endpoint
|
||||
; 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.
|
||||
; The order for other two allocations is quite arbitrary.
|
||||
; 1a. Allocate a bus address.
|
||||
; 2a. Allocate a bus address.
|
||||
push ecx
|
||||
call usb_set_address_request
|
||||
pop ecx
|
||||
; 1b. If failed, just return zero.
|
||||
; 2b. If failed, just return zero.
|
||||
test eax, eax
|
||||
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
|
||||
; input and output, see usb_after_set_address. Later we will reallocate it
|
||||
; to actual size needed for descriptors.
|
||||
@ -201,10 +229,10 @@ proc usb_new_device
|
||||
push ecx
|
||||
call malloc
|
||||
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
|
||||
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 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.
|
||||
@ -227,12 +255,14 @@ proc usb_new_device
|
||||
; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes.
|
||||
xchg eax, ebx
|
||||
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
|
||||
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
|
||||
; 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.TTPort], 0
|
||||
mov [eax+usb_device_data.NumInterfaces], edi
|
||||
@ -268,7 +298,7 @@ proc usb_new_device
|
||||
mov [eax+usb_device_data.Port], cl
|
||||
mov edx, [esi+usb_controller.ResettingHub]
|
||||
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.
|
||||
; Root hubs use the array inside usb_controller 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
|
||||
@@:
|
||||
call usb_reinit_pipe_list
|
||||
; 5. Issue SET_ADDRESS control request, using buffer filled in step 1a.
|
||||
; Use the return value from usb_control_async as our return value;
|
||||
; if it is zero, then something has failed.
|
||||
; 6. Issue SET_ADDRESS control request, using buffer filled in step 2a.
|
||||
; 6a. Configure timer to force reset after timeout.
|
||||
; 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]
|
||||
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:
|
||||
; 6. Return.
|
||||
; 7. Return.
|
||||
pop edi ebx ; restore used registers to be stdcall
|
||||
ret
|
||||
; Handlers of failures in steps 1b, 1d, 1f.
|
||||
; Handlers of failures in steps 2b, 2d, 2f.
|
||||
.freememory:
|
||||
call free
|
||||
jmp .freeaddr
|
||||
@ -349,16 +392,23 @@ endp
|
||||
; 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
|
||||
push ebx ; save ebx to be stdcall
|
||||
; Load data to registers for further references.
|
||||
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 eax, [esi+usb_controller.HardwareFunc]
|
||||
; 1. Check whether the device has accepted new address. If so, proceed to 2.
|
||||
; Otherwise, go to 3.
|
||||
; 2. Check whether the device has accepted new address. If so, proceed 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
|
||||
jnz .error
|
||||
; 2. Address accepted.
|
||||
; 2a. The controller-specific structure for the control pipe still uses
|
||||
; 3. Address accepted.
|
||||
; 3a. The controller-specific structure for the control pipe still uses
|
||||
; zero address. Call the controller-specific function to change it to
|
||||
; the actual address.
|
||||
; 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.
|
||||
; dbgstr 'address set in device'
|
||||
call [eax+usb_hardware_func.SetDeviceAddress]
|
||||
; 2b. If the port is in non-root hub, clear 'reset in progress' flag.
|
||||
; In any case, proceed to 4.
|
||||
; 3b. If the port is in non-root hub, clear 'reset in progress' flag.
|
||||
; In any case, proceed to 6.
|
||||
mov eax, [esi+usb_controller.ResettingHub]
|
||||
test eax, eax
|
||||
jz .return
|
||||
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
|
||||
.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 usb_test_pending_port
|
||||
.wakeup:
|
||||
push esi edi
|
||||
call usb_wakeup
|
||||
pop edi esi
|
||||
.nothing:
|
||||
pop ebx ; restore ebx to be stdcall
|
||||
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:
|
||||
; 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]
|
||||
; 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
|
||||
; 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 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).
|
||||
|
@ -186,6 +186,7 @@ USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer
|
||||
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
|
||||
USB_STATUS_CLOSED = 16 ; pipe closed, either explicitly with USBClosePipe
|
||||
; 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
|
||||
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
|
||||
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);
|
||||
Returns miscellaneous parameters of the device.
|
||||
pipe0 is the pointer to the config pipe.
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user