349 lines
12 KiB
PHP
349 lines
12 KiB
PHP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; ;;
|
|
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;;
|
|
;; Distributed under terms of the GNU General Public License ;;
|
|
;; ;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
$Revision$
|
|
|
|
|
|
; USB Host Controller support code: hardware-independent part,
|
|
; common for all controller types.
|
|
|
|
iglobal
|
|
; USB HC support: some functions interesting only for *HCI-drivers.
|
|
align 4
|
|
usb_hc_func:
|
|
dd usb_process_gtd
|
|
dd usb_init_static_endpoint
|
|
dd usb_wakeup_if_needed
|
|
dd usb_subscribe_control
|
|
dd usb_subscription_done
|
|
dd slab_alloc
|
|
dd slab_free
|
|
dd usb_td_to_virt
|
|
dd usb_init_transfer
|
|
dd usb_undo_tds
|
|
dd usb_test_pending_port
|
|
dd usb_get_tt
|
|
dd usb_get_tt_think_time
|
|
dd usb_new_device
|
|
dd usb_disconnect_stage2
|
|
dd usb_process_wait_lists
|
|
dd usb_unlink_td
|
|
dd usb_is_final_packet
|
|
dd usb_find_ehci_companion
|
|
endg
|
|
|
|
; Initializes one controller, called by usb_init for every controller.
|
|
; eax -> PCIDEV structure for the device.
|
|
proc usb_init_controller
|
|
push ebp
|
|
mov ebp, esp
|
|
; 1. Store in the stack PCI coordinates and save pointer to PCIDEV:
|
|
; make [ebp-4] = (bus shl 8) + devfn, used by controller-specific Init funcs.
|
|
push dword [eax+PCIDEV.devfn]
|
|
push eax
|
|
mov edi, [eax+PCIDEV.owner]
|
|
test edi, edi
|
|
jz .nothing
|
|
mov edi, [edi+USBSRV.usb_func]
|
|
; 2. Allocate *hci_controller + usb_controller.
|
|
mov ebx, [edi+usb_hardware_func.DataSize]
|
|
add ebx, sizeof.usb_controller
|
|
stdcall kernel_alloc, ebx
|
|
test eax, eax
|
|
jz .nothing
|
|
; 3. Zero-initialize both structures.
|
|
push edi eax
|
|
mov ecx, ebx
|
|
shr ecx, 2
|
|
xchg edi, eax
|
|
xor eax, eax
|
|
rep stosd
|
|
; 4. Initialize usb_controller structure,
|
|
; except data known only to controller-specific code (like NumPorts)
|
|
; and link fields
|
|
; (this structure will be inserted to the overall list at step 6).
|
|
dec eax
|
|
mov [edi+usb_controller.ExistingAddresses+4-sizeof.usb_controller], eax
|
|
mov [edi+usb_controller.ExistingAddresses+8-sizeof.usb_controller], eax
|
|
mov [edi+usb_controller.ExistingAddresses+12-sizeof.usb_controller], eax
|
|
mov [edi+usb_controller.ResettingPort-sizeof.usb_controller], al ; no resetting port
|
|
dec eax ; don't allocate zero address
|
|
mov [edi+usb_controller.ExistingAddresses-sizeof.usb_controller], eax
|
|
mov eax, [ebp-4]
|
|
mov [edi+usb_controller.PCICoordinates-sizeof.usb_controller], eax
|
|
lea ecx, [edi+usb_controller.PeriodicLock-sizeof.usb_controller]
|
|
call mutex_init
|
|
add ecx, usb_controller.ControlLock - usb_controller.PeriodicLock
|
|
call mutex_init
|
|
add ecx, usb_controller.BulkLock - usb_controller.ControlLock
|
|
call mutex_init
|
|
pop eax edi
|
|
mov [eax+ebx-sizeof.usb_controller+usb_controller.HardwareFunc], edi
|
|
push eax
|
|
; 5. Call controller-specific initialization.
|
|
; If failed, free memory allocated in step 2 and return.
|
|
call [edi+usb_hardware_func.Init]
|
|
test eax, eax
|
|
jz .fail
|
|
pop ecx
|
|
; 6. Insert the controller to the global list.
|
|
xchg eax, ebx
|
|
mov ecx, usb_controllers_list_mutex
|
|
call mutex_lock
|
|
mov edx, usb_controllers_list
|
|
mov eax, [edx+usb_controller.Prev]
|
|
mov [ebx+usb_controller.Next], edx
|
|
mov [ebx+usb_controller.Prev], eax
|
|
mov [edx+usb_controller.Prev], ebx
|
|
mov [eax+usb_controller.Next], ebx
|
|
call mutex_unlock
|
|
; 7. Wakeup USB thread to call ProcessDeferred.
|
|
call usb_wakeup
|
|
.nothing:
|
|
; 8. Restore pointer to PCIDEV saved in step 1 and return.
|
|
pop eax
|
|
leave
|
|
ret
|
|
.fail:
|
|
call kernel_free
|
|
jmp .nothing
|
|
endp
|
|
|
|
; Helper function, calculates physical address including offset in page.
|
|
proc get_phys_addr
|
|
push ecx
|
|
mov ecx, eax
|
|
and ecx, 0xFFF
|
|
call get_pg_addr
|
|
add eax, ecx
|
|
pop ecx
|
|
ret
|
|
endp
|
|
|
|
; 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.
|
|
proc usb_subscribe_control
|
|
cmp [ebx+usb_pipe.NextWait], -1
|
|
jnz @f
|
|
mov eax, [esi+usb_controller.WaitPipeListAsync]
|
|
mov [ebx+usb_pipe.NextWait], eax
|
|
mov [esi+usb_controller.WaitPipeListAsync], ebx
|
|
@@:
|
|
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.
|
|
proc usb_subscription_done
|
|
mov eax, [ebx+usb_pipe.DeviceData]
|
|
cmp [eax+usb_device_data.DeviceDescrSize], 0
|
|
jz usb_after_set_address
|
|
jmp usb_after_set_endpoint_size
|
|
endp
|
|
|
|
; This function is called when a new device has either passed
|
|
; or failed first stages of configuration, so the next device
|
|
; can enter configuration process.
|
|
proc usb_test_pending_port
|
|
mov [esi+usb_controller.ResettingPort], -1
|
|
cmp [esi+usb_controller.PendingPorts], 0
|
|
jz .nothing
|
|
bsf ecx, [esi+usb_controller.PendingPorts]
|
|
btr [esi+usb_controller.PendingPorts], ecx
|
|
mov eax, [esi+usb_controller.HardwareFunc]
|
|
jmp [eax+usb_hardware_func.InitiateReset]
|
|
.nothing:
|
|
ret
|
|
endp
|
|
|
|
; This procedure is regularly called from controller-specific ProcessDeferred,
|
|
; it checks whether there are disconnected events and if so, process them.
|
|
proc usb_disconnect_stage2
|
|
bsf ecx, [esi+usb_controller.NewDisconnected]
|
|
jz .nothing
|
|
lock btr [esi+usb_controller.NewDisconnected], ecx
|
|
btr [esi+usb_controller.PendingPorts], ecx
|
|
xor ebx, ebx
|
|
xchg ebx, [esi+usb_controller.DevicesByPort+ecx*4]
|
|
test ebx, ebx
|
|
jz usb_disconnect_stage2
|
|
call usb_device_disconnected
|
|
jmp usb_disconnect_stage2
|
|
.nothing:
|
|
ret
|
|
endp
|
|
|
|
; Initial stage of disconnect processing: called when device is disconnected.
|
|
proc usb_device_disconnected
|
|
; Loop over all pipes, close everything, wait until hardware reacts.
|
|
; The final handling is done in usb_pipe_closed.
|
|
push ebx
|
|
mov ecx, [ebx+usb_pipe.DeviceData]
|
|
call mutex_lock
|
|
lea eax, [ecx+usb_device_data.OpenedPipeList-usb_pipe.NextSibling]
|
|
push eax
|
|
mov ebx, [eax+usb_pipe.NextSibling]
|
|
.pipe_loop:
|
|
call usb_close_pipe_nolock
|
|
mov ebx, [ebx+usb_pipe.NextSibling]
|
|
cmp ebx, [esp]
|
|
jnz .pipe_loop
|
|
pop eax
|
|
pop ebx
|
|
mov ecx, [ebx+usb_pipe.DeviceData]
|
|
call mutex_unlock
|
|
ret
|
|
endp
|
|
|
|
; Called from controller-specific ProcessDeferred,
|
|
; processes wait-pipe-done notifications,
|
|
; returns whether there are more items in wait queues.
|
|
; in: esi -> usb_controller
|
|
; out: eax = bitmask of pipe types with non-empty wait queue
|
|
proc usb_process_wait_lists
|
|
xor edx, edx
|
|
push edx
|
|
call usb_process_one_wait_list
|
|
jnc @f
|
|
or byte [esp], 1 shl CONTROL_PIPE
|
|
@@:
|
|
movi edx, 4
|
|
call usb_process_one_wait_list
|
|
jnc @f
|
|
or byte [esp], 1 shl INTERRUPT_PIPE
|
|
@@:
|
|
xor edx, edx
|
|
call usb_process_one_wait_list
|
|
jnc @f
|
|
or byte [esp], 1 shl CONTROL_PIPE
|
|
@@:
|
|
pop eax
|
|
ret
|
|
endp
|
|
|
|
; Helper procedure for usb_process_wait_lists;
|
|
; does the same for one wait queue.
|
|
; in: esi -> usb_controller,
|
|
; edx=0 for *Async, edx=4 for *Periodic list
|
|
; out: CF = issue new request
|
|
proc usb_process_one_wait_list
|
|
; 1. Check whether there is a pending request. If so, do nothing.
|
|
mov ebx, [esi+usb_controller.WaitPipeRequestAsync+edx]
|
|
cmp ebx, [esi+usb_controller.ReadyPipeHeadAsync+edx]
|
|
clc
|
|
jnz .nothing
|
|
; 2. Check whether there are new data. If so, issue a new request.
|
|
cmp ebx, [esi+usb_controller.WaitPipeListAsync+edx]
|
|
stc
|
|
jnz .nothing
|
|
test ebx, ebx
|
|
jz .nothing
|
|
; 3. Clear all lists.
|
|
xor ecx, ecx
|
|
mov [esi+usb_controller.WaitPipeListAsync+edx], ecx
|
|
mov [esi+usb_controller.WaitPipeRequestAsync+edx], ecx
|
|
mov [esi+usb_controller.ReadyPipeHeadAsync+edx], ecx
|
|
; 4. Loop over all pipes from the wait list.
|
|
.pipe_loop:
|
|
; For every pipe:
|
|
; 5. Save edx and next pipe in the list.
|
|
push edx
|
|
push [ebx+usb_pipe.NextWait]
|
|
; 6. If USB_FLAG_EXTRA_WAIT is set, reinsert the pipe to the list and continue.
|
|
test [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT
|
|
jz .process
|
|
mov eax, [esi+usb_controller.WaitPipeListAsync+edx]
|
|
mov [ebx+usb_pipe.NextWait], eax
|
|
mov [esi+usb_controller.WaitPipeListAsync+edx], ebx
|
|
jmp .continue
|
|
.process:
|
|
; 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.
|
|
pop ebx
|
|
pop edx
|
|
test ebx, ebx
|
|
jnz .pipe_loop
|
|
.check_new_work:
|
|
; 9. Set CF depending on whether WaitPipeList* is nonzero.
|
|
cmp [esi+usb_controller.WaitPipeListAsync+edx], 1
|
|
cmc
|
|
.nothing:
|
|
ret
|
|
endp
|
|
|
|
; Called from USB1 controller-specific initialization.
|
|
; Finds EHCI companion controller for given USB1 controller.
|
|
; in: bl = PCI device:function for USB1 controller, bh = PCI bus
|
|
; out: eax -> usb_controller for EHCI companion
|
|
proc usb_find_ehci_companion
|
|
; 1. Loop over all registered controllers.
|
|
mov eax, usb_controllers_list
|
|
.next:
|
|
mov eax, [eax+usb_controller.Next]
|
|
cmp eax, usb_controllers_list
|
|
jz .notfound
|
|
; 2. For every controller, check the type, ignore everything that is not EHCI.
|
|
mov edx, [eax+usb_controller.HardwareFunc]
|
|
cmp [edx+usb_hardware_func.ID], 'EHCI'
|
|
jnz .next
|
|
; 3. For EHCI controller, compare PCI coordinates with input data:
|
|
; bus and device must be the same, function can be different.
|
|
mov edx, [eax+usb_controller.PCICoordinates]
|
|
xor edx, ebx
|
|
cmp dx, 8
|
|
jae .next
|
|
ret
|
|
.notfound:
|
|
xor eax, eax
|
|
ret
|
|
endp
|
|
|
|
; Find Transaction Translator hub and port for the given device.
|
|
; in: edx = parent hub for the device, ecx = port for the device
|
|
; out: edx = TT hub for the device, ecx = TT port for the device.
|
|
proc usb_get_tt
|
|
; If the parent hub is high-speed, it is TT for the device.
|
|
; Otherwise, the parent hub itself is behind TT, and the device
|
|
; has the same TT hub+port as the parent hub.
|
|
mov eax, [edx+usb_hub.ConfigPipe]
|
|
mov eax, [eax+usb_pipe.DeviceData]
|
|
cmp [eax+usb_device_data.Speed], USB_SPEED_HS
|
|
jz @f
|
|
movzx ecx, [eax+usb_device_data.TTPort]
|
|
mov edx, [eax+usb_device_data.TTHub]
|
|
@@:
|
|
mov edx, [edx+usb_hub.ConfigPipe]
|
|
ret
|
|
endp
|