Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 4546 → Rev 4547

/drivers/usb/ehci.asm
156,8 → 156,6
; 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
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
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,22 → 1912,61
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
 
proc ehci_alloc_td
push ebx
mov ebx, ehci_gtd_mutex
/drivers/usb/ehci_scheduler.inc
298,7 → 298,7
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
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
/drivers/usb/ohci.asm
64,7 → 64,7
; 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
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
; 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,20 → 1617,44
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
 
; Allocates one endpoint structure for OHCI.
; Returns pointer to software part (usb_pipe) in eax.
proc ohci_alloc_pipe
/drivers/usb/uhci.asm
274,6 → 274,8
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
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
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,20 → 1507,47
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
 
; This procedure is called from the several places in main USB code
; and allocates required packets for the given transfer stage.
; ebx = pipe, other parameters are passed through the stack
/drivers/usb/usb1_scheduler.inc
167,12 → 167,7
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
/kernel/trunk/bus/usb/common.inc
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_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_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
; 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
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
; 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 ?
/kernel/trunk/bus/usb/hccommon.inc
114,7 → 114,7
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
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,7 → 265,7
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
261,6 → 272,11
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.
/kernel/trunk/bus/usb/pipe.inc
13,6 → 13,11
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
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,6 → 276,40
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
268,30 → 316,26
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,7 → 386,16
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
349,7 → 402,7
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
.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
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
/kernel/trunk/bus/usb/protocol.inc
33,6 → 33,19
; 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,7 → 192,22
; 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
186,14 → 214,14
; 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
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
; 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
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
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.
; 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.
lea eax, [esi+usb_controller.SetAddressBuffer]
stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi
.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
; 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
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 ebx, [pipe]
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
; 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).
/kernel/trunk/docs/usbapi_ru.txt
File deleted
\ No newline at end of file
/kernel/trunk/docs/usbapi.txt
186,6 → 186,7
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
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.