0,0 → 1,1275 |
; Support for USB (non-root) hubs: |
; powering up/resetting/disabling ports, |
; watching for adding/removing devices. |
|
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; Hub constants |
; USB hub descriptor type |
USB_HUB_DESCRIPTOR = 29h |
|
; Features for CLEAR_FEATURE commands to the hub. |
C_HUB_LOCAL_POWER = 0 |
C_HUB_OVER_CURRENT = 1 |
|
; Bits in result of GET_STATUS command for a port. |
; Also suitable for CLEAR_FEATURE/SET_FEATURE commands, where applicable, |
; except TEST/INDICATOR. |
PORT_CONNECTION = 0 |
PORT_ENABLE = 1 |
PORT_SUSPEND = 2 |
PORT_OVER_CURRENT = 3 |
PORT_RESET = 4 |
PORT_POWER = 8 |
PORT_LOW_SPEED = 9 |
PORT_HIGH_SPEED = 10 |
PORT_TEST_BIT = 11 |
PORT_INDICATOR_BIT = 12 |
C_PORT_CONNECTION = 16 |
C_PORT_ENABLE = 17 |
C_PORT_SUSPEND = 18 |
C_PORT_OVER_CURRENT = 19 |
C_PORT_RESET = 20 |
PORT_TEST_FEATURE = 21 |
PORT_INDICATOR_FEATURE = 22 |
|
; Internal constants |
; Bits in usb_hub.Actions |
HUB_WAIT_POWERED = 1 |
; ports were powered, wait until power is stable |
HUB_WAIT_CONNECT = 2 |
; some device was connected, wait initial debounce interval |
HUB_RESET_IN_PROGRESS = 4 |
; reset in progress, so buffer for config requests is owned |
; by reset process; this includes all stages from initial disconnect test |
; to end of setting address (fail on any stage should lead to disabling port, |
; which requires a config request) |
HUB_RESET_WAITING = 8 |
; the port is ready for reset, but another device somewhere on the bus |
; is resetting. Implies HUB_RESET_IN_PROGRESS |
HUB_RESET_SIGNAL = 10h |
; reset signalling is active for some port in the hub |
; Implies HUB_RESET_IN_PROGRESS |
HUB_RESET_RECOVERY = 20h |
; reset recovery is active for some port in the hub |
; Implies HUB_RESET_IN_PROGRESS |
|
; Well, I think that those 5 flags WAIT_CONNECT and RESET_* require additional |
; comments. So that is the overview of what happens with a new device assuming |
; no errors. |
; * device is connected; |
; * hub notifies us about connect event; after some processing |
; usb_hub_port_change finally processes that event, setting the flag |
; HUB_WAIT_CONNECT and storing time when the device was connected; |
; * 100 ms delay; |
; * usb_hub_process_deferred clears HUB_WAIT_CONNECT, |
; sets HUB_RESET_IN_PROGRESS, stores the port index in ConfigBuffer and asks |
; the hub whether there was a disconnect event for that port during those |
; 100 ms (on the hardware level notifications are obtained using polling |
; with some intervals, so it is possible that the corresponding notification |
; has not arrived yet); |
; * usb_hub_connect_port_status checks that there was no disconnect event |
; and sets HUB_RESET_WAITING flag (HUB_RESET_IN_PROGRESS is still set, |
; ConfigBuffer still contains the port index); |
; * usb_hub_process_deferred checks whether there is another device currently |
; resetting. If so, it waits until reset is done |
; (with HUB_RESET_WAITING and HUB_RESET_IN_PROGRESS bits set); |
; * usb_hub_process_deferred clears HUB_RESET_WAITING, sets HUB_RESET_SIGNAL |
; and initiates reset signalling on the port; |
; * usb_hub_process_deferred checks the status every tick; |
; when reset signalling is stopped by the hub, usb_hub_resetting_port_status |
; callback clears HUB_RESET_SIGNAL and sets HUB_RESET_RECOVERY; |
; * 10 ms (at least) delay; |
; * usb_hub_process_deferred clears HUB_RESET_RECOVERY and notifies other code |
; that the new device is ready to be configured; |
; * when it is possible to reset another device, the protocol layer |
; clears HUB_RESET_IN_PROGRESS bit. |
|
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; This structure contains all used data for one hub. |
struct usb_hub |
; All configured hubs are organized in the global usb_hub_list. |
; Two following fields give next/prev items in that list. |
; While the hub is unconfigured, they point to usb_hub itself. |
Next dd ? |
Prev dd ? |
Controller dd ? |
; Pointer to usb_controller for the bus. |
; |
; Handles of two pipes: configuration control pipe for zero endpoint opened by |
; the common code and status interrupt pipe opened by us. |
ConfigPipe dd ? |
StatusPipe dd ? |
NumPorts dd ? |
; Number of downstream ports; from 1 to 255. |
MaxPacketSize dd ? |
; Maximum packet size for interrupt endpoint. |
; Usually equals ceil((1+NumPorts)/8), but some hubs give additional bytes. |
Actions dd ? |
; Bitfield with HUB_* constants. |
PoweredOnTime dd ? |
; Time (in ticks) when all downstream ports were powered up. |
ResetTime dd ? |
; Time (in ticks) when the current port was reset; |
; when a port is resetting, contains the last tick of status check; |
; when reset recovery for a port is active, contains the time when |
; reset was completed. |
; |
; There are two possible reasons for configuration requests: |
; synchronous, when certain time is passed after something, |
; and asynchronous, when the hub is notifying about some change and |
; config request needs to be issued in order to query details. |
; Use two different buffers to avoid unnecessary dependencies. |
ConfigBuffer rb 8 |
; Buffer for configuration requests for synchronous events. |
ChangeConfigBuffer rb 8 |
; Buffer for configuration requests for status changes. |
AccStatusChange db ? |
; Accumulated status change. See 11.12.3 of USB2 spec or comments in code. |
HubCharacteristics dw ? |
; Copy of usb_hub_descr.wHubCharacteristics. |
PowerOnInterval db ? |
; Copy of usb_hub_descr.bPwrOn2PwrGood. |
; |
; Two following fields are written at once by GET_STATUS request |
; and must remain in this order. |
StatusData dw ? |
; Bitfield with 1 shl PORT_* indicating status of the current port. |
StatusChange dw ? |
; Bitfield with 1 shl PORT_* indicating change in status of the current port. |
; Two following fields are written at once by GET_STATUS request |
; and must remain in this order. |
; The meaning is the same as of StatusData/StatusChange; two following fields |
; are used by the synchronous requests to avoid unnecessary interactions with |
; the asynchronous handler. |
ResetStatusData dw ? |
ResetStatusChange dw ? |
StatusChangePtr dd ? |
; Pointer to StatusChangeBuf. |
ConnectedDevicesPtr dd ? |
; Pointer to ConnectedDevices. |
ConnectedTimePtr dd ? |
; Pointer to ConnectedTime. |
; |
; Variable-length parts: |
; DeviceRemovable rb (NumPorts+8)/8 |
; Bit i+1 = device at port i (zero-based) is non-removable. |
; StatusChangeBuf rb (NumPorts+8)/8 |
; Buffer for status interrupt pipe. Bit 0 = hub status change, |
; other bits = status change of the corresponding ports. |
; ConnectedDevices rd NumPorts |
; Pointers to config pipes for connected devices or zero if no device connected. |
; ConnectedTime rd NumPorts |
; For initial debounce interval: |
; time (in ticks) when a device was connected at that port. |
; Normally: -1 |
ends |
|
; Hub descriptor. |
struct usb_hub_descr usb_descr |
bNbrPorts db ? |
; Number of downstream ports. |
wHubCharacteristics dw ? |
; Bit 0: 0 = all ports are powered at once, 1 = individual port power switching |
; Bit 1: reserved, must be zero |
; Bit 2: 1 = the hub is part of a compound device |
; Bits 3-4: 00 = global overcurrent protection, |
; 01 = individual port overcurrent protection, |
; 1x = no overcurrent protection |
; Bits 5-6: Transaction Translator Think Time, 8*(value+1) full-speed bit times |
; Bit 7: 1 = port indicators supported |
; Other bits are reserved. |
bPwrOn2PwrGood db ? |
; Time in 2ms intervals between powering up a port and a port becoming ready. |
bHubContrCurrent db ? |
; Maximum current requirements of the Hub Controller electronics in mA. |
; DeviceRemovable - variable length |
; Bit 0 is reserved, bit i+1 = device at port i is non-removable. |
; PortPwrCtrlMask - variable length |
; Obsolete, exists for compatibility. We ignore it. |
ends |
|
iglobal |
align 4 |
; Implementation of struct USBFUNC for hubs. |
usb_hub_callbacks: |
dd usb_hub_callbacks_end - usb_hub_callbacks |
dd usb_hub_init |
dd usb_hub_disconnect |
usb_hub_callbacks_end: |
usb_hub_pseudosrv dd usb_hub_callbacks |
endg |
|
; This procedure is called when new hub is detected. |
; It initializes the device. |
; Technically, initialization implies sending several USB queries, |
; so it is split in several procedures. The first is usb_hub_init, |
; other are callbacks which will be called at some time in the future, |
; when the device will respond. |
; edx = usb_interface_descr, ecx = length rest |
proc usb_hub_init |
push ebx esi ; save used registers to be stdcall |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? ; handle of the config pipe |
.config dd ? ; pointer to usb_config_descr |
.interface dd ? ; pointer to usb_interface_descr |
end virtual |
; 1. Check that the maximal nesting is not exceeded: |
; 5 non-root hubs is the maximum according to the spec. |
mov ebx, [.pipe] |
push 5 |
mov eax, ebx |
.count_parents: |
mov eax, [eax+usb_pipe.DeviceData] |
mov eax, [eax+usb_device_data.Hub] |
test eax, eax |
jz .depth_ok |
mov eax, [eax+usb_hub.ConfigPipe] |
dec dword [esp] |
jnz .count_parents |
pop eax |
dbgstr 'Hub chain is too long' |
jmp .return0 |
.depth_ok: |
pop eax |
; Hubs use one IN interrupt endpoint for polling the device |
; 2. Locate the descriptor of the interrupt endpoint. |
; Loop over all descriptors owned by this interface. |
.lookep: |
; 2a. Skip the current descriptor. |
movzx eax, [edx+usb_descr.bLength] |
add edx, eax |
sub ecx, eax |
jb .errorep |
; 2b. Length of data left must be at least sizeof.usb_endpoint_descr. |
cmp ecx, sizeof.usb_endpoint_descr |
jb .errorep |
; 2c. If we have found another interface descriptor but not found our endpoint, |
; this is an error: all subsequent descriptors belong to that interface |
; (or further interfaces). |
cmp [edx+usb_endpoint_descr.bDescriptorType], USB_INTERFACE_DESCR |
jz .errorep |
; 2d. Ignore all interface-related descriptors except endpoint descriptor. |
cmp [edx+usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR |
jnz .lookep |
; 2e. Length of endpoint descriptor must be at least sizeof.usb_endpoint_descr. |
cmp [edx+usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr |
jb .errorep |
; 2f. Ignore all endpoints except for INTERRUPT IN. |
cmp [edx+usb_endpoint_descr.bEndpointAddress], 0 |
jge .lookep |
mov al, [edx+usb_endpoint_descr.bmAttributes] |
and al, 3 |
cmp al, INTERRUPT_PIPE |
jnz .lookep |
; We have located the descriptor for INTERRUPT IN endpoint, |
; the pointer is in edx. |
; 3. Allocate memory for the hub descriptor. |
; Maximum length (assuming 255 downstream ports) is 40 bytes. |
; Allocate 4 extra bytes to keep wMaxPacketSize. |
; 3a. Save registers. |
push edx |
; 3b. Call the allocator. |
movi eax, 44 |
call malloc |
; 3c. Restore registers. |
pop ecx |
; 3d. If failed, say something to the debug board and return error. |
test eax, eax |
jz .nomemory |
; 3e. Store the pointer in esi. xchg eax,r32 is one byte shorter than mov. |
xchg esi, eax |
; 4. Open a pipe for the status endpoint with descriptor found in step 1. |
movzx eax, [ecx+usb_endpoint_descr.bEndpointAddress] |
movzx edx, [ecx+usb_endpoint_descr.bInterval] |
movzx ecx, [ecx+usb_endpoint_descr.wMaxPacketSize] |
test ecx, (1 shl 11) - 1 |
jz .free |
push ecx |
stdcall usb_open_pipe, ebx, eax, ecx, INTERRUPT_PIPE, edx |
pop ecx |
; If failed, free the memory allocated in step 3, |
; say something to the debug board and return error. |
test eax, eax |
jz .free |
; 5. Send control query for the hub descriptor, |
; pass status pipe as a callback parameter, |
; allow short packets. |
and ecx, (1 shl 11) - 1 |
mov [esi+40], ecx |
mov dword [esi], 0xA0 + \ ; class-specific request |
(USB_GET_DESCRIPTOR shl 8) + \ |
(0 shl 16) + \ ; descriptor index 0 |
(USB_HUB_DESCRIPTOR shl 24) |
mov dword [esi+4], 40 shl 16 |
stdcall usb_control_async, ebx, esi, esi, 40, usb_hub_got_config, eax, 1 |
; 6. If failed, free the memory allocated in step 3, |
; say something to the debug board and return error. |
test eax, eax |
jz .free |
; Otherwise, return 1. usb_hub_got_config will overwrite it later. |
xor eax, eax |
inc eax |
jmp .nothing |
.free: |
xchg eax, esi |
call free |
jmp .return0 |
.errorep: |
dbgstr 'Invalid config descriptor for a hub' |
jmp .return0 |
.nomemory: |
dbgstr 'No memory for USB hub data' |
.return0: |
xor eax, eax |
.nothing: |
pop esi ebx ; restore used registers to be stdcall |
retn 12 |
endp |
|
; This procedure is called when the request for the hub descriptor initiated |
; by usb_hub_init is finished, either successfully or unsuccessfully. |
proc usb_hub_got_config stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push ebx ; save used registers to be stdcall |
; 1. If failed, say something to the debug board, free the buffer |
; and stop the initialization. |
cmp [status], 0 |
jnz .invalid |
; 2. The length must be at least sizeof.usb_hub_descr. |
; Note that [length] includes 8 bytes of setup packet. |
cmp [length], 8 + sizeof.usb_hub_descr |
jb .invalid |
; 3. Sanity checks for the hub descriptor. |
mov eax, [buffer] |
if USB_DUMP_DESCRIPTORS |
mov ecx, [length] |
sub ecx, 8 |
DEBUGF 1,'K : hub config:' |
push eax |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz @b |
DEBUGF 1,'\n' |
pop eax |
end if |
cmp [eax+usb_hub_descr.bLength], sizeof.usb_hub_descr |
jb .invalid |
cmp [eax+usb_hub_descr.bDescriptorType], USB_HUB_DESCRIPTOR |
jnz .invalid |
movzx ecx, [eax+usb_hub_descr.bNbrPorts] |
test ecx, ecx |
jz .invalid |
; 4. We use sizeof.usb_hub_descr bytes plus DeviceRemovable info; |
; size of DeviceRemovable is (NumPorts+1) bits, this gives |
; floor(NumPorts/8)+1 bytes. Check that all data are present in the |
; descriptor and were successfully read. |
mov edx, ecx |
shr edx, 3 |
add edx, sizeof.usb_hub_descr + 1 |
cmp [eax+usb_hub_descr.bLength], dl |
jb .invalid |
sub [length], 8 |
cmp [length], edx |
jb .invalid |
; 5. Allocate the memory for usb_hub structure. |
; Total size of variable-length data is ALIGN_UP(floor(NumPorts/8)+1+MaxPacketSize,4)+8*NumPorts. |
add edx, [eax+40] |
add edx, sizeof.usb_hub - sizeof.usb_hub_descr + 3 |
and edx, not 3 |
lea eax, [edx+ecx*8] |
push ecx edx |
call malloc |
pop edx ecx |
test eax, eax |
jz .nomemory |
xchg eax, ebx |
; 6. Fill usb_hub structure. |
mov [ebx+usb_hub.NumPorts], ecx |
add edx, ebx |
mov [ebx+usb_hub.ConnectedDevicesPtr], edx |
mov eax, [pipe] |
mov [ebx+usb_hub.ConfigPipe], eax |
mov edx, [eax+usb_pipe.Controller] |
mov [ebx+usb_hub.Controller], edx |
mov eax, [calldata] |
mov [ebx+usb_hub.StatusPipe], eax |
push esi edi |
mov esi, [buffer] |
mov eax, [esi+40] |
mov [ebx+usb_hub.MaxPacketSize], eax |
; The following commands load bNbrPorts, wHubCharacteristics, bPwrOn2PwrGood. |
mov edx, dword [esi+usb_hub_descr.bNbrPorts] |
mov dl, 0 |
; The following command zeroes AccStatusChange and stores |
; HubCharacteristics and PowerOnInterval. |
mov dword [ebx+usb_hub.AccStatusChange], edx |
xor eax, eax |
mov [ebx+usb_hub.Actions], eax |
; Copy DeviceRemovable data. |
lea edi, [ebx+sizeof.usb_hub] |
add esi, sizeof.usb_hub_descr |
mov edx, ecx |
shr ecx, 3 |
inc ecx |
rep movsb |
mov [ebx+usb_hub.StatusChangePtr], edi |
; Zero ConnectedDevices. |
mov edi, [ebx+usb_hub.ConnectedDevicesPtr] |
mov ecx, edx |
rep stosd |
mov [ebx+usb_hub.ConnectedTimePtr], edi |
; Set ConnectedTime to -1. |
dec eax |
mov ecx, edx |
rep stosd |
pop edi esi |
; 7. Replace value of 1 returned from usb_hub_init to the real value. |
; Note: hubs are part of the core USB code, so this code can work with |
; internals of other parts. Another way, the only possible one for external |
; drivers, is to use two memory allocations: one (returned from AddDevice and |
; fixed after that) for pointer, another for real data. That would work also, |
; but wastes one allocation. |
mov eax, [pipe] |
mov eax, [eax+usb_pipe.DeviceData] |
add eax, [eax+usb_device_data.Interfaces] |
.scan: |
cmp [eax+usb_interface_data.DriverData], 1 |
jnz @f |
cmp [eax+usb_interface_data.DriverFunc], usb_hub_pseudosrv - USBSRV.usb_func |
jz .scan_found |
@@: |
add eax, sizeof.usb_interface_data |
jmp .scan |
.scan_found: |
mov [eax+usb_interface_data.DriverData], ebx |
; 8. Insert the hub structure to the tail of the overall list of all hubs. |
mov ecx, usb_hubs_list |
mov edx, [ecx+usb_hub.Prev] |
mov [ecx+usb_hub.Prev], ebx |
mov [edx+usb_hub.Next], ebx |
mov [ebx+usb_hub.Prev], edx |
mov [ebx+usb_hub.Next], ecx |
; 9. Start powering up all ports. |
DEBUGF 1,'K : found hub with %d ports\n',[ebx+usb_hub.NumPorts] |
lea eax, [ebx+usb_hub.ConfigBuffer] |
xor ecx, ecx |
mov dword [eax], 23h + \ ; class-specific request to hub port |
(USB_SET_FEATURE shl 8) + \ |
(PORT_POWER shl 16) |
mov edx, [ebx+usb_hub.NumPorts] |
mov dword [eax+4], edx |
stdcall usb_control_async, [ebx+usb_hub.ConfigPipe], eax, ecx, ecx, usb_hub_port_powered, ebx, ecx |
.freebuf: |
; 10. Free the buffer for hub descriptor and return. |
mov eax, [buffer] |
call free |
pop ebx ; restore used registers to be stdcall |
ret |
.nomemory: |
dbgstr 'No memory for USB hub data' |
jmp .freebuf |
.invalid: |
dbgstr 'Invalid hub descriptor' |
jmp .freebuf |
endp |
|
; This procedure is called when the request to power up some port is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_powered stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and ssstop the initialization. |
cmp [status], 0 |
jnz .invalid |
; 2. Check whether all ports were powered. |
; If so, go to 4. Otherwise, proceed to 3. |
mov eax, [calldata] |
dec dword [eax+usb_hub.ConfigBuffer+4] |
jz .done |
; 3. Power up the next port and return. |
lea edx, [eax+usb_hub.ConfigBuffer] |
xor ecx, ecx |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, ecx, usb_hub_port_powered, eax, ecx |
.nothing: |
ret |
.done: |
; 4. All ports were powered. |
; The hub requires some delay until power will be stable, the delay value |
; is provided in the hub descriptor; we have copied that value to |
; usb_hub.PowerOnInterval. Note the time and set the corresponding flag |
; for usb_hub_process_deferred. |
mov ecx, [timer_ticks] |
mov [eax+usb_hub.PoweredOnTime], ecx |
or [eax+usb_hub.Actions], HUB_WAIT_POWERED |
jmp .nothing |
.invalid: |
dbgstr 'Error while powering hub ports' |
jmp .nothing |
endp |
|
; Requests notification about any changes in hub/ports configuration. |
; Called when initial configuration is done and when a previous notification |
; has been processed. |
proc usb_hub_wait_change |
stdcall usb_normal_transfer_async, [eax+usb_hub.StatusPipe], \ |
[eax+usb_hub.StatusChangePtr], [eax+usb_hub.MaxPacketSize], usb_hub_changed, eax, 1 |
ret |
endp |
|
; This procedure is called when something has changed on the hub. |
proc usb_hub_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; DEBUGF 1,'K : [%d] int pipe for hub %x\n',[timer_ticks],[calldata] |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
xor ecx, ecx |
cmp [status], ecx |
jnz .failed |
; 2. If no data were retrieved, restart waiting. |
mov eax, [calldata] |
cmp [length], ecx |
jz .continue |
; 3. If size of data retrieved is less than maximal, pad with zeroes; |
; this corresponds to 'state of other ports was not changed' |
mov ecx, [eax+usb_hub.NumPorts] |
shr ecx, 3 |
inc ecx |
sub ecx, [length] |
jbe .restart |
push eax edi |
mov edi, [buffer] |
add edi, [length] |
xor eax, eax |
rep stosb |
pop edi eax |
.restart: |
; State of some elements of the hub was changed. |
; Find the first element that was changed, |
; ask the hub about nature of the change, |
; clear the corresponding change, |
; reask the hub about status+change (it is possible that another change |
; occurs between the first ask and clearing the change; we won't see that |
; change, so we need to query the status after clearing the change), |
; continue two previous steps until nothing changes, |
; process all changes which were registered. |
; When all changes for one element will be processed, return to here and look |
; for other changed elements. |
mov edx, [eax+usb_hub.StatusChangePtr] |
; We keep all observed changes in the special var usb_hub.AccStatusChange; |
; it will be logical OR of all observed StatusChange's. |
; 4. No observed changes yet, zero usb_hub.AccStatusChange. |
xor ecx, ecx |
mov [eax+usb_hub.AccStatusChange], cl |
; 5. Test whether there was a change in the hub itself. |
; If so, query hub state. |
btr dword [edx], ecx |
jnc .no_hub_change |
.next_hub_change: |
; DEBUGF 1,'K : [%d] querying status of hub %x\n',[timer_ticks],eax |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
lea ecx, [eax+usb_hub.StatusData] |
mov dword [edx], 0A0h + \ ; class-specific request from hub itself |
(USB_GET_STATUS shl 8) |
mov dword [edx+4], 4 shl 16 ; get 4 bytes |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_status, eax, 0 |
jmp .nothing |
.no_hub_change: |
; 6. Find the first port with changed state and clear the corresponding bit |
; (so next scan after .restart will not consider this port again). |
; If found, go to 8. Otherwise, advance to 7. |
inc ecx |
.test_port_change: |
btr [edx], ecx |
jc .found_port_change |
inc ecx |
cmp ecx, [eax+usb_hub.NumPorts] |
jbe .test_port_change |
.continue: |
; 7. All changes have been processed. Wait for next notification. |
call usb_hub_wait_change |
.nothing: |
ret |
.found_port_change: |
mov dword [eax+usb_hub.ChangeConfigBuffer+4], ecx |
.next_port_change: |
; 8. Query port state. Continue work in usb_hub_port_status callback. |
; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] querying status of hub %x port %d\n',[timer_ticks],eax,ecx |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
mov dword [edx], 0A3h + \ ; class-specific request from hub port |
(USB_GET_STATUS shl 8) |
mov byte [edx+6], 4 ; data length = 4 bytes |
lea ecx, [eax+usb_hub.StatusData] |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_port_status, eax, 0 |
jmp .nothing |
.failed: |
cmp [status], USB_STATUS_CLOSED |
jz .nothing |
dbgstr 'Querying hub notification failed' |
jmp .nothing |
endp |
|
; This procedure is called when the request of hub status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. Accumulate observed changes. |
mov eax, [calldata] |
mov dl, byte [eax+usb_hub.StatusChange] |
or [eax+usb_hub.AccStatusChange], dl |
.next_change: |
; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. |
mov cl, C_HUB_OVER_CURRENT |
btr dword [eax+usb_hub.StatusChange], 1 |
jc .clear_hub_change |
mov cl, C_HUB_LOCAL_POWER |
btr dword [eax+usb_hub.StatusChange], 0 |
jnc .final |
.clear_hub_change: |
; 4. Clear the change and continue in usb_hub_change_cleared callback. |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
mov dword [edx], 20h + \ ; class-specific request to hub itself |
(USB_CLEAR_FEATURE shl 8) |
mov [edx+2], cl ; feature selector |
and dword [edx+4], 0 |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_change_cleared, eax, 0 |
.nothing: |
ret |
.final: |
; 5. All changes cleared and accumulated, now process them. |
; Note: that needs work. |
DEBUGF 1,'K : hub status %x\n',[eax+usb_hub.AccStatusChange]:2 |
test [eax+usb_hub.AccStatusChange], 1 |
jz .no_local_power |
test [eax+usb_hub.StatusData], 1 |
jz .local_power_lost |
dbgstr 'Hub local power is now good' |
jmp .no_local_power |
.local_power_lost: |
dbgstr 'Hub local power is now lost' |
.no_local_power: |
test [eax+usb_hub.AccStatusChange], 2 |
jz .no_overcurrent |
test [eax+usb_hub.StatusData], 2 |
jz .no_overcurrent |
dbgstr 'Hub global overcurrent' |
.no_overcurrent: |
; 6. Process possible changes for other ports. |
jmp usb_hub_changed.restart |
.failed: |
dbgstr 'Querying hub status failed' |
jmp .nothing |
endp |
|
; This procedure is called when the request to clear hub change is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_change_cleared stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. If there is a change which was observed, but not yet cleared, |
; go to the code which clears it. |
mov eax, [calldata] |
cmp [eax+usb_hub.StatusChange], 0 |
jnz usb_hub_status.next_change |
; 3. Otherwise, go to the code which queries the status. |
jmp usb_hub_changed.next_hub_change |
.failed: |
dbgstr 'Clearing hub change failed' |
ret |
endp |
|
; This procedure is called when the request of port status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. Accumulate observed changes. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.StatusChange]:4 |
mov dl, byte [eax+usb_hub.StatusChange] |
or [eax+usb_hub.AccStatusChange], dl |
.next_change: |
; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. |
; Ignore change in reset status; it is cleared by synchronous code |
; (usb_hub_process_deferred), so avoid unnecessary interference. |
; mov cl, C_PORT_RESET |
btr dword [eax+usb_hub.StatusChange], PORT_RESET |
; jc .clear_port_change |
mov cl, C_PORT_OVER_CURRENT |
btr dword [eax+usb_hub.StatusChange], PORT_OVER_CURRENT |
jc .clear_port_change |
mov cl, C_PORT_SUSPEND |
btr dword [eax+usb_hub.StatusChange], PORT_SUSPEND |
jc .clear_port_change |
mov cl, C_PORT_ENABLE |
btr dword [eax+usb_hub.StatusChange], PORT_ENABLE |
jc .clear_port_change |
mov cl, C_PORT_CONNECTION |
btr dword [eax+usb_hub.StatusChange], PORT_CONNECTION |
jnc .final |
.clear_port_change: |
; 4. Clear the change and continue in usb_hub_port_changed callback. |
call usb_hub_clear_port_change |
jmp .nothing |
.final: |
; All changes cleared and accumulated, now process them. |
movzx ecx, byte [eax+usb_hub.ChangeConfigBuffer+4] |
dec ecx |
DEBUGF 1,'K : final: hub %x port %d status %x change %x\n',eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.AccStatusChange]:2 |
; 5. Process connect/disconnect events. |
; 5a. Test whether there is such event. |
test byte [eax+usb_hub.AccStatusChange], 1 shl PORT_CONNECTION |
jz .nodisconnect |
; 5b. If there was a connected device, notify the main code about disconnect. |
push ebx |
mov edx, [eax+usb_hub.ConnectedDevicesPtr] |
xor ebx, ebx |
xchg ebx, [edx+ecx*4] |
test ebx, ebx |
jz @f |
push eax ecx |
call usb_device_disconnected |
pop ecx eax |
@@: |
pop ebx |
; 5c. If the disconnect event corresponds to the port which is currently |
; resetting, then another request from synchronous code could be in the fly, |
; so aborting reset immediately would lead to problems with those requests. |
; Thus, just set the corresponding status and let the synchronous code process. |
test byte [eax+usb_hub.Actions], (HUB_RESET_SIGNAL or HUB_RESET_RECOVERY) |
jz @f |
mov edx, [eax+usb_hub.Controller] |
cmp [edx+usb_controller.ResettingPort], cl |
jnz @f |
mov [edx+usb_controller.ResettingStatus], -1 |
@@: |
; 5d. If the current status is 'connected', store the current time as connect |
; time and set the corresponding bit for usb_hub_process_deferred. |
; Otherwise, set connect time to -1. |
; If current time is -1, pretend that the event occured one tick later and |
; store zero. |
mov edx, [eax+usb_hub.ConnectedTimePtr] |
test byte [eax+usb_hub.StatusData], 1 shl PORT_CONNECTION |
jz .disconnected |
or [eax+usb_hub.Actions], HUB_WAIT_CONNECT |
push eax |
call usb_hub_store_connected_time |
pop eax |
jmp @f |
.disconnected: |
or dword [edx+ecx*4], -1 |
@@: |
.nodisconnect: |
; 6. Process port disabling. |
test [eax+usb_hub.AccStatusChange], 1 shl PORT_ENABLE |
jz .nodisable |
test byte [eax+usb_hub.StatusData], 1 shl PORT_ENABLE |
jnz .nodisable |
; Note: that needs work. |
dbgstr 'Port disabled' |
.nodisable: |
; 7. Process port overcurrent. |
test [eax+usb_hub.AccStatusChange], 1 shl PORT_OVER_CURRENT |
jz .noovercurrent |
test byte [eax+usb_hub.StatusData], 1 shl PORT_OVER_CURRENT |
jz .noovercurrent |
; Note: that needs work. |
dbgstr 'Port over-current' |
.noovercurrent: |
; 8. Process possible changes for other ports. |
jmp usb_hub_changed.restart |
.failed: |
dbgstr 'Querying port status failed' |
.nothing: |
ret |
endp |
|
; Helper procedure to store current time in ConnectedTime, |
; advancing -1 to zero if needed. |
proc usb_hub_store_connected_time |
mov eax, [timer_ticks] |
; transform -1 to 0, leave other values as is |
cmp eax, -1 |
sbb eax, -1 |
mov [edx+ecx*4], eax |
ret |
endp |
|
; Helper procedure for several parts of hub code. |
; Sends a request to clear the given feature of the port. |
; eax -> usb_hub, cl = feature; |
; as is should be called from async code, sync code should set |
; edx to ConfigBuffer and call usb_hub_clear_port_change.buffer; |
; port number (1-based) should be filled in [edx+4] by previous requests. |
proc usb_hub_clear_port_change |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
.buffer: |
; push edx |
; movzx edx, byte [edx+4] |
; dec edx |
; DEBUGF 1,'K : [%d] hub %x port %d clear feature %d\n',[timer_ticks],eax,edx,cl |
; pop edx |
mov dword [edx], 23h + \ ; class-specific request to hub port |
(USB_CLEAR_FEATURE shl 8) |
mov byte [edx+2], cl |
and dword [edx+4], 0xFF |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, edx, 0, usb_hub_port_changed, eax, 0 |
ret |
endp |
|
; This procedure is called when the request to clear port change is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. If the request was originated by synchronous code, no further processing |
; is required. |
mov eax, [calldata] |
lea edx, [eax+usb_hub.ConfigBuffer] |
cmp [buffer], edx |
jz .nothing |
; 3. If there is a change which was observed, but not yet cleared, |
; go to the code which clears it. |
cmp [eax+usb_hub.StatusChange], 0 |
jnz usb_hub_port_status.next_change |
; 4. Otherwise, go to the code which queries the status. |
jmp usb_hub_changed.next_port_change |
.failed: |
dbgstr 'Clearing port change failed' |
.nothing: |
ret |
endp |
|
; This procedure is called in the USB thread from usb_thread_proc, |
; contains synchronous code which should be activated at certain time |
; (e.g. reset a recently connected device after debounce interval 100ms). |
; Returns the number of ticks when it should be called next time. |
proc usb_hub_process_deferred |
; 1. Top-of-stack will contain return value; initialize to infinite timeout. |
push -1 |
; 2. If wait for stable power is active, then |
; either reschedule wakeup (if time is not over) |
; or start processing notifications. |
test byte [esi+usb_hub.Actions], HUB_WAIT_POWERED |
jz .no_powered |
movzx eax, [esi+usb_hub.PowerOnInterval] |
; three following instructions are equivalent to edx = ceil(eax / 5) + 1 |
; 1 extra tick is added to make sure that the interval is at least as needed |
; (it is possible that PoweredOnTime was set just before timer interrupt, and |
; this test goes on just after timer interrupt) |
add eax, 9 |
; two following instructions are equivalent to edx = floor(eax / 5) |
; for any 0 <= eax < 40000000h |
mov ecx, 33333334h |
mul ecx |
mov eax, [timer_ticks] |
sub eax, [esi+usb_hub.PoweredOnTime] |
sub eax, edx |
jge .powered_on |
neg eax |
pop ecx |
push eax |
jmp .no_powered |
.powered_on: |
and [esi+usb_hub.Actions], not HUB_WAIT_POWERED |
mov eax, esi |
call usb_hub_wait_change |
.no_powered: |
; 3. If reset is pending, check whether we can start it and start it, if so. |
test byte [esi+usb_hub.Actions], HUB_RESET_WAITING |
jz .no_wait_reset |
mov eax, [esi+usb_hub.Controller] |
cmp [eax+usb_controller.ResettingPort], -1 |
jnz .no_wait_reset |
call usb_hub_initiate_reset |
.no_wait_reset: |
; 4. If reset signalling is active, wait for end of reset signalling |
; and schedule wakeup in 1 tick. |
test byte [esi+usb_hub.Actions], HUB_RESET_SIGNAL |
jz .no_resetting_port |
; It has no sense to query status several times per tick. |
mov eax, [timer_ticks] |
cmp eax, [esi+usb_hub.ResetTime] |
jz @f |
mov [esi+usb_hub.ResetTime], eax |
movzx ecx, byte [esi+usb_hub.ConfigBuffer+4] |
mov eax, usb_hub_resetting_port_status |
call usb_hub_query_port_status |
@@: |
pop eax |
push 1 |
.no_resetting_port: |
; 5. If reset recovery is active and time is not over, reschedule wakeup. |
test byte [esi+usb_hub.Actions], HUB_RESET_RECOVERY |
jz .no_reset_recovery |
mov eax, [timer_ticks] |
sub eax, [esi+usb_hub.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_done |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
jmp .no_reset_recovery |
.reset_done: |
; 6. If reset recovery is active and time is over, clear 'reset recovery' flag, |
; notify other code about a new device and let it do further steps. |
; If that fails, stop reset process for this port and disable that port. |
and [esi+usb_hub.Actions], not HUB_RESET_RECOVERY |
; Bits 9-10 of port status encode port speed. |
; If PORT_LOW_SPEED is set, the device is low-speed. Otherwise, |
; PORT_HIGH_SPEED bit distinguishes full-speed and high-speed devices. |
; This corresponds to values of USB_SPEED_FS=0, USB_SPEED_LS=1, USB_SPEED_HS=2. |
mov eax, dword [esi+usb_hub.ResetStatusData] |
shr eax, PORT_LOW_SPEED |
and eax, 3 |
test al, 1 |
jz @f |
mov al, 1 |
@@: |
; movzx ecx, [esi+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d speed %d\n',[timer_ticks],esi,ecx,eax |
push esi |
mov esi, [esi+usb_hub.Controller] |
cmp [esi+usb_controller.ResettingStatus], -1 |
jz .disconnected_while_reset |
mov edx, [esi+usb_controller.HardwareFunc] |
call [edx+usb_hardware_func.NewDevice] |
pop esi |
test eax, eax |
jnz .no_reset_recovery |
mov eax, esi |
call usb_hub_disable_resetting_port |
jmp .no_reset_recovery |
.disconnected_while_reset: |
pop esi |
mov eax, esi |
call usb_hub_reset_aborted |
.no_reset_recovery: |
; 7. Handle recent connection events. |
; Note: that should be done after step 6, because step 6 can clear |
; HUB_RESET_IN_PROGRESS flag. |
; 7a. Test whether there is such an event pending. If no, skip this step. |
test byte [esi+usb_hub.Actions], HUB_WAIT_CONNECT |
jz .no_wait_connect |
; 7b. If we have started reset process for another port in the same hub, |
; skip this step: the buffer for config requests can be used for that port. |
test byte [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS |
jnz .no_wait_connect |
; 7c. Clear flag 'there are connection events which should be processed'. |
; If there are another connection events, this flag will be set again. |
and [esi+usb_hub.Actions], not HUB_WAIT_CONNECT |
; 7d. Prepare for loop over all ports. |
xor ecx, ecx |
.test_wait_connect: |
; 7e. For every port test for recent connection event. |
; If none, continue the loop for the next port. |
mov edx, [esi+usb_hub.ConnectedTimePtr] |
mov eax, [edx+ecx*4] |
cmp eax, -1 |
jz .next_wait_connect |
or [esi+usb_hub.Actions], HUB_WAIT_CONNECT |
; 7f. Test whether initial delay is over. |
sub eax, [timer_ticks] |
neg eax |
sub eax, USB_CONNECT_DELAY |
jge .connect_delay_over |
; 7g. The initial delay is not over; |
; set the corresponding flag again, reschedule wakeup and continue the loop. |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
jmp .next_wait_connect |
.connect_delay_over: |
; The initial delay is over. |
; It is possible that there was disconnect event during that delay, probably |
; with connect event after that. If so, we should restart the waiting. However, |
; on the hardware level connect/disconnect events from hubs are implemented |
; using polling with interval selected by the hub, so it is possible that |
; we have not yet observed that disconnect event. |
; Thus, we query port status+change data before all further processing. |
; 7h. Send the request for status+change data. |
push ecx |
; Hub requests expect 1-based port number, not zero-based we operate with. |
inc ecx |
mov eax, usb_hub_connect_port_status |
call usb_hub_query_port_status |
pop ecx |
; 3i. If request has been submitted successfully, set the flag |
; 'reset in progress, config buffer is owned by reset process' and break |
; from the loop. |
test eax, eax |
jz .next_wait_connect |
or [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS |
jmp .no_wait_connect |
.next_wait_connect: |
; 7j. Continue the loop for next port. |
inc ecx |
cmp ecx, [esi+usb_hub.NumPorts] |
jb .test_wait_connect |
.no_wait_connect: |
; 8. Pop return value from top-of-stack and return. |
pop eax |
ret |
endp |
|
; Helper procedure for other code. Called when reset process is aborted. |
proc usb_hub_reset_aborted |
; Clear 'reset in progress' flag and test for other devices which could be |
; waiting for reset. |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
push esi |
mov esi, [eax+usb_hub.Controller] |
call usb_test_pending_port |
pop esi |
ret |
endp |
|
; Helper procedure for usb_hub_process_deferred. |
; Sends a request to query port status. |
; esi -> usb_hub, eax = callback, ecx = 1-based port. |
proc usb_hub_query_port_status |
; dec ecx |
; DEBUGF 1,'K : [%d] [main] hub %x port %d query status\n',[timer_ticks],esi,ecx |
; inc ecx |
add ecx, 4 shl 16 ; data length = 4 |
lea edx, [esi+usb_hub.ConfigBuffer] |
mov dword [edx], 0A3h + \ ; class-specific request from hub port |
(USB_GET_STATUS shl 8) |
mov dword [edx+4], ecx |
lea ecx, [esi+usb_hub.ResetStatusData] |
stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, ecx, 4, eax, esi, 0 |
ret |
endp |
|
; This procedure is called when the request to query port status |
; initiated by usb_hub_process_deferred for testing connection is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_connect_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push esi ; save used register to be stdcall |
mov eax, [calldata] |
mov esi, [pipe] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] [connect test] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 |
; 1. In any case, clear 'reset in progress' flag. |
; If everything is ok, it would be set again. |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
; 2. If the request has failed, stop reset process. |
cmp [status], 0 |
jnz .nothing |
mov edx, [eax+usb_hub.ConnectedTimePtr] |
movzx ecx, byte [eax+usb_hub.ConfigBuffer+4] |
dec ecx |
; 3. Test whether there was a disconnect event. |
test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION |
jz .reset |
; 4. There was a disconnect event. |
; There is another handler of connect/disconnect events, usb_hub_port_status. |
; However, we do not know whether it has already processed this event |
; or it will process it sometime later. |
; If ConnectedTime is -1, then another handler has already run, |
; there was no connection event, so just leave the value as -1. |
; Otherwise, there are two possibilities: either another handler has not yet |
; run (which is quite likely), or there was a connection event and the other |
; handler has run exactly while our request was processed (otherwise our |
; request would not been submitted; this is quite unlikely due to timing |
; requirements, but not impossible). In this case, set ConnectedTime to the |
; current time: in the likely case it prevents usb_hub_process_deferred from immediate |
; issuing of another requests (which would be just waste of time); |
; in the unlikely case it is still correct (although slightly increases |
; the debounce interval). |
cmp dword [edx+ecx*4], -1 |
jz .nothing |
call usb_hub_store_connected_time |
jmp .nothing |
.reset: |
; 5. The device remained connected for the entire debounce interval; |
; we can proceed with initialization. |
; Clear connected time for this port and notify usb_hub_process_deferred that |
; the new port is waiting for reset. |
or dword [edx+ecx*4], -1 |
or [eax+usb_hub.Actions], HUB_RESET_IN_PROGRESS + HUB_RESET_WAITING |
.nothing: |
pop esi ; restore used register to be stdcall |
ret |
endp |
|
; This procedure is called when the request to query port status |
; initiated by usb_hub_process_deferred for testing reset status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_resetting_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. If the request has failed, do nothing. |
cmp [status], 0 |
jnz .nothing |
; 2. If reset signalling is still active, do nothing. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : hub %x port %d ResetStatusData = %x change = %x\n',eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 |
test byte [eax+usb_hub.ResetStatusData], 1 shl PORT_RESET |
jnz .nothing |
; 3. Store the current time to start reset recovery interval |
; and clear 'reset signalling active' flag. |
mov edx, [timer_ticks] |
mov [eax+usb_hub.ResetTime], edx |
and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL |
; 4. If the device has not been disconnected, set 'reset recovery active' bit. |
; Otherwise, terminate reset process. |
test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION |
jnz .disconnected |
or [eax+usb_hub.Actions], HUB_RESET_RECOVERY |
.common: |
; In any case, clear change of resetting status. |
lea edx, [eax+usb_hub.ConfigBuffer] |
mov cl, C_PORT_RESET |
call usb_hub_clear_port_change.buffer |
.nothing: |
ret |
.disconnected: |
call usb_hub_reset_aborted |
jmp .common |
endp |
|
; Helper procedure for usb_hub_process_deferred. Initiates reset signalling |
; on the current port (given by 1-based value [ConfigBuffer+4]). |
; esi -> usb_hub, eax -> usb_controller |
proc usb_hub_initiate_reset |
; 1. Store hub+port data in the controller structure. |
movzx ecx, [esi+usb_hub.ConfigBuffer+4] |
dec ecx |
mov [eax+usb_controller.ResettingPort], cl |
mov [eax+usb_controller.ResettingHub], esi |
; 2. Store the current time and set 'reset signalling active' flag. |
mov eax, [timer_ticks] |
mov [esi+usb_hub.ResetTime], eax |
and [esi+usb_hub.Actions], not HUB_RESET_WAITING |
or [esi+usb_hub.Actions], HUB_RESET_SIGNAL |
; 3. Send request to the hub to initiate request signalling. |
lea edx, [esi+usb_hub.ConfigBuffer] |
; DEBUGF 1,'K : [%d] hub %x port %d initiate reset\n',[timer_ticks],esi,ecx |
mov dword [edx], 23h + \ |
(USB_SET_FEATURE shl 8) + \ |
(PORT_RESET shl 16) |
and dword [edx+4], 0xFF |
stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_reset_started, esi, 0 |
test eax, eax |
jnz @f |
mov eax, esi |
call usb_hub_reset_aborted |
@@: |
ret |
endp |
|
; This procedure is called when the request to start reset signalling initiated |
; by usb_hub_initiate_reset is completed, either successfully or unsuccessfully. |
proc usb_hub_reset_started stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; If the request is successful, do nothing. |
; Otherwise, clear 'reset signalling' flag and abort reset process. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d reset started\n',[timer_ticks],eax,ecx |
cmp [status], 0 |
jz .nothing |
and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL |
dbgstr 'Failed to reset hub port' |
call usb_hub_reset_aborted |
.nothing: |
ret |
endp |
|
; This procedure is called by the protocol layer if something has failed during |
; initial stages of the configuration process, so the device should be disabled |
; at hub level. |
proc usb_hub_disable_resetting_port |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d disable\n',[timer_ticks],eax,ecx |
lea edx, [eax+usb_hub.ConfigBuffer] |
mov cl, PORT_ENABLE |
jmp usb_hub_clear_port_change.buffer |
endp |
|
; This procedure is called when the hub is disconnected. |
proc usb_hub_disconnect |
virtual at esp |
dd ? ; return address |
.hubdata dd ? |
end virtual |
; 1. If the hub is disconnected during initial configuration, |
; 1 is stored as hub data and there is nothing to do. |
mov eax, [.hubdata] |
cmp eax, 1 |
jz .nothing |
; 2. Remove the hub from the overall list. |
mov ecx, [eax+usb_hub.Next] |
mov edx, [eax+usb_hub.Prev] |
mov [ecx+usb_hub.Prev], edx |
mov [edx+usb_hub.Next], ecx |
; 3. If some child is in reset process, abort reset. |
push esi |
mov esi, [eax+usb_hub.Controller] |
cmp [esi+usb_controller.ResettingHub], eax |
jnz @f |
cmp [esi+usb_controller.ResettingPort], -1 |
jz @f |
push eax |
call usb_test_pending_port |
pop eax |
@@: |
pop esi |
; 4. Loop over all children and notify other code that they were disconnected. |
push ebx |
xor ecx, ecx |
.disconnect_children: |
mov ebx, [eax+usb_hub.ConnectedDevicesPtr] |
mov ebx, [ebx+ecx*4] |
test ebx, ebx |
jz @f |
push eax ecx |
call usb_device_disconnected |
pop ecx eax |
@@: |
inc ecx |
cmp ecx, [eax+usb_hub.NumPorts] |
jb .disconnect_children |
; 4. Free memory allocated for the hub data. |
call free |
pop ebx |
.nothing: |
retn 4 |
endp |
|
; Helper function for USB2 scheduler. |
; in: eax -> usb_hub |
; out: ecx = TT think time for the hub in FS-bytes |
proc usb_get_tt_think_time |
movzx ecx, [eax+usb_hub.HubCharacteristics] |
shr ecx, 5 |
and ecx, 3 |
inc ecx |
ret |
endp |