Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 4546 → Rev 4547

/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).