Subversion Repositories Kolibri OS

Rev

Rev 3598 | Rev 4305 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. ; Support for USB (non-root) hubs:
  2. ; powering up/resetting/disabling ports,
  3. ; watching for adding/removing devices.
  4.  
  5. ; =============================================================================
  6. ; ================================= Constants =================================
  7. ; =============================================================================
  8. ; Hub constants
  9. ; USB hub descriptor type
  10. USB_HUB_DESCRIPTOR = 29h
  11.  
  12. ; Features for CLEAR_FEATURE commands to the hub.
  13. C_HUB_LOCAL_POWER   = 0
  14. C_HUB_OVER_CURRENT  = 1
  15.  
  16. ; Bits in result of GET_STATUS command for a port.
  17. ; Also suitable for CLEAR_FEATURE/SET_FEATURE commands, where applicable,
  18. ; except TEST/INDICATOR.
  19. PORT_CONNECTION     = 0
  20. PORT_ENABLE         = 1
  21. PORT_SUSPEND        = 2
  22. PORT_OVER_CURRENT   = 3
  23. PORT_RESET          = 4
  24. PORT_POWER          = 8
  25. PORT_LOW_SPEED      = 9
  26. PORT_HIGH_SPEED     = 10
  27. PORT_TEST_BIT       = 11
  28. PORT_INDICATOR_BIT  = 12
  29. C_PORT_CONNECTION   = 16
  30. C_PORT_ENABLE       = 17
  31. C_PORT_SUSPEND      = 18
  32. C_PORT_OVER_CURRENT = 19
  33. C_PORT_RESET        = 20
  34. PORT_TEST_FEATURE   = 21
  35. PORT_INDICATOR_FEATURE = 22
  36.  
  37. ; Internal constants
  38. ; Bits in usb_hub.Actions
  39. HUB_WAIT_POWERED      = 1
  40. ; ports were powered, wait until power is stable
  41. HUB_WAIT_CONNECT      = 2
  42. ; some device was connected, wait initial debounce interval
  43. HUB_RESET_IN_PROGRESS = 4
  44. ; reset in progress, so buffer for config requests is owned
  45. ; by reset process; this includes all stages from initial disconnect test
  46. ; to end of setting address (fail on any stage should lead to disabling port,
  47. ; which requires a config request)
  48. HUB_RESET_WAITING     = 8
  49. ; the port is ready for reset, but another device somewhere on the bus
  50. ; is resetting. Implies HUB_RESET_IN_PROGRESS
  51. HUB_RESET_SIGNAL      = 10h
  52. ; reset signalling is active for some port in the hub
  53. ; Implies HUB_RESET_IN_PROGRESS
  54. HUB_RESET_RECOVERY    = 20h
  55. ; reset recovery is active for some port in the hub
  56. ; Implies HUB_RESET_IN_PROGRESS
  57.  
  58. ; Well, I think that those 5 flags WAIT_CONNECT and RESET_* require additional
  59. ; comments. So that is the overview of what happens with a new device assuming
  60. ; no errors.
  61. ; * device is connected;
  62. ; * hub notifies us about connect event; after some processing
  63. ;   usb_hub_port_change finally processes that event, setting the flag
  64. ;   HUB_WAIT_CONNECT and storing time when the device was connected;
  65. ; * 100 ms delay;
  66. ; * usb_hub_process_deferred clears HUB_WAIT_CONNECT,
  67. ;   sets HUB_RESET_IN_PROGRESS, stores the port index in ConfigBuffer and asks
  68. ;   the hub whether there was a disconnect event for that port during those
  69. ;   100 ms (on the hardware level notifications are obtained using polling
  70. ;   with some intervals, so it is possible that the corresponding notification
  71. ;   has not arrived yet);
  72. ; * usb_hub_connect_port_status checks that there was no disconnect event
  73. ;   and sets HUB_RESET_WAITING flag (HUB_RESET_IN_PROGRESS is still set,
  74. ;   ConfigBuffer still contains the port index);
  75. ; * usb_hub_process_deferred checks whether there is another device currently
  76. ;   resetting. If so, it waits until reset is done
  77. ;   (with HUB_RESET_WAITING and HUB_RESET_IN_PROGRESS bits set);
  78. ; * usb_hub_process_deferred clears HUB_RESET_WAITING, sets HUB_RESET_SIGNAL
  79. ;   and initiates reset signalling on the port;
  80. ; * usb_hub_process_deferred checks the status every tick;
  81. ;   when reset signalling is stopped by the hub, usb_hub_resetting_port_status
  82. ;   callback clears HUB_RESET_SIGNAL and sets HUB_RESET_RECOVERY;
  83. ; * 10 ms (at least) delay;
  84. ; * usb_hub_process_deferred clears HUB_RESET_RECOVERY and notifies other code
  85. ;   that the new device is ready to be configured;
  86. ; * when it is possible to reset another device, the protocol layer
  87. ;   clears HUB_RESET_IN_PROGRESS bit.
  88.  
  89. ; =============================================================================
  90. ; ================================ Structures =================================
  91. ; =============================================================================
  92. ; This structure contains all used data for one hub.
  93. struct usb_hub
  94. ; All configured hubs are organized in the global usb_hub_list.
  95. ; Two following fields give next/prev items in that list.
  96. ; While the hub is unconfigured, they point to usb_hub itself.
  97. Next                    dd      ?
  98. Prev                    dd      ?
  99. Controller              dd      ?
  100. ; Pointer to usb_controller for the bus.
  101. ;
  102. ; Handles of two pipes: configuration control pipe for zero endpoint opened by
  103. ; the common code and status interrupt pipe opened by us.
  104. ConfigPipe              dd      ?
  105. StatusPipe              dd      ?
  106. NumPorts                dd      ?
  107. ; Number of downstream ports; from 1 to 255.
  108. MaxPacketSize           dd      ?
  109. ; Maximum packet size for interrupt endpoint.
  110. ; Usually equals ceil((1+NumPorts)/8), but some hubs give additional bytes.
  111. Actions                 dd      ?
  112. ; Bitfield with HUB_* constants.
  113. PoweredOnTime           dd      ?
  114. ; Time (in ticks) when all downstream ports were powered up.
  115. ResetTime               dd      ?
  116. ; Time (in ticks) when the current port was reset;
  117. ; when a port is resetting, contains the last tick of status check;
  118. ; when reset recovery for a port is active, contains the time when
  119. ; reset was completed.
  120. ;
  121. ; There are two possible reasons for configuration requests:
  122. ; synchronous, when certain time is passed after something,
  123. ; and asynchronous, when the hub is notifying about some change and
  124. ; config request needs to be issued in order to query details.
  125. ; Use two different buffers to avoid unnecessary dependencies.
  126. ConfigBuffer            rb      8
  127. ; Buffer for configuration requests for synchronous events.
  128. ChangeConfigBuffer      rb      8
  129. ; Buffer for configuration requests for status changes.
  130. AccStatusChange         db      ?
  131. ; Accumulated status change. See 11.12.3 of USB2 spec or comments in code.
  132. HubCharacteristics      dw      ?
  133. ; Copy of usb_hub_descr.wHubCharacteristics.
  134. PowerOnInterval         db      ?
  135. ; Copy of usb_hub_descr.bPwrOn2PwrGood.
  136. ;
  137. ; Two following fields are written at once by GET_STATUS request
  138. ; and must remain in this order.
  139. StatusData              dw      ?
  140. ; Bitfield with 1 shl PORT_* indicating status of the current port.
  141. StatusChange            dw      ?
  142. ; Bitfield with 1 shl PORT_* indicating change in status of the current port.
  143. ; Two following fields are written at once by GET_STATUS request
  144. ; and must remain in this order.
  145. ; The meaning is the same as of StatusData/StatusChange; two following fields
  146. ; are used by the synchronous requests to avoid unnecessary interactions with
  147. ; the asynchronous handler.
  148. ResetStatusData         dw      ?
  149. ResetStatusChange       dw      ?
  150. StatusChangePtr         dd      ?
  151. ; Pointer to StatusChangeBuf.
  152. ConnectedDevicesPtr     dd      ?
  153. ; Pointer to ConnectedDevices.
  154. ConnectedTimePtr        dd      ?
  155. ; Pointer to ConnectedTime.
  156. ;
  157. ; Variable-length parts:
  158. ; DeviceRemovable rb (NumPorts+8)/8
  159. ;  Bit i+1 = device at port i (zero-based) is non-removable.
  160. ; StatusChangeBuf rb (NumPorts+8)/8
  161. ;  Buffer for status interrupt pipe. Bit 0 = hub status change,
  162. ;  other bits = status change of the corresponding ports.
  163. ; ConnectedDevices rd NumPorts
  164. ;  Pointers to config pipes for connected devices or zero if no device connected.
  165. ; ConnectedTime rd NumPorts
  166. ;  For initial debounce interval:
  167. ;   time (in ticks) when a device was connected at that port.
  168. ;  Normally: -1
  169. ends
  170.  
  171. ; Hub descriptor.
  172. struct usb_hub_descr usb_descr
  173. bNbrPorts               db      ?
  174. ; Number of downstream ports.
  175. wHubCharacteristics     dw      ?
  176. ; Bit 0: 0 = all ports are powered at once, 1 = individual port power switching
  177. ; Bit 1: reserved, must be zero
  178. ; Bit 2: 1 = the hub is part of a compound device
  179. ; Bits 3-4: 00 = global overcurrent protection,
  180. ;           01 = individual port overcurrent protection,
  181. ;           1x = no overcurrent protection
  182. ; Bits 5-6: Transaction Translator Think Time, 8*(value+1) full-speed bit times
  183. ; Bit 7: 1 = port indicators supported
  184. ; Other bits are reserved.
  185. bPwrOn2PwrGood          db      ?
  186. ; Time in 2ms intervals between powering up a port and a port becoming ready.
  187. bHubContrCurrent        db      ?
  188. ; Maximum current requirements of the Hub Controller electronics in mA.
  189. ; DeviceRemovable - variable length
  190. ;  Bit 0 is reserved, bit i+1 = device at port i is non-removable.
  191. ; PortPwrCtrlMask - variable length
  192. ;  Obsolete, exists for compatibility. We ignore it.
  193. ends
  194.  
  195. iglobal
  196. align 4
  197. ; Implementation of struct USBFUNC for hubs.
  198. usb_hub_callbacks:
  199.         dd      usb_hub_callbacks_end - usb_hub_callbacks
  200.         dd      usb_hub_init
  201.         dd      usb_hub_disconnect
  202. usb_hub_callbacks_end:
  203. usb_hub_pseudosrv       dd      usb_hub_callbacks
  204. endg
  205.  
  206. ; This procedure is called when new hub is detected.
  207. ; It initializes the device.
  208. ; Technically, initialization implies sending several USB queries,
  209. ; so it is split in several procedures. The first is usb_hub_init,
  210. ; other are callbacks which will be called at some time in the future,
  211. ; when the device will respond.
  212. ; edx = usb_interface_descr, ecx = length rest
  213. proc usb_hub_init
  214.         push    ebx esi         ; save used registers to be stdcall
  215. virtual at esp
  216.                 rd      2       ; saved registers
  217.                 dd      ?       ; return address
  218. .pipe           dd      ?       ; handle of the config pipe
  219. .config         dd      ?       ; pointer to usb_config_descr
  220. .interface      dd      ?       ; pointer to usb_interface_descr
  221. end virtual
  222. ; Hubs use one IN interrupt endpoint for polling the device
  223. ; 1. Locate the descriptor of the interrupt endpoint.
  224. ; Loop over all descriptors owned by this interface.
  225. .lookep:
  226. ; 1a. Skip the current descriptor.
  227.         movzx   eax, [edx+usb_descr.bLength]
  228.         add     edx, eax
  229.         sub     ecx, eax
  230.         jb      .errorep
  231. ; 1b. Length of data left must be at least sizeof.usb_endpoint_descr.
  232.         cmp     ecx, sizeof.usb_endpoint_descr
  233.         jb      .errorep
  234. ; 1c. If we have found another interface descriptor but not found our endpoint,
  235. ; this is an error: all subsequent descriptors belong to that interface
  236. ; (or further interfaces).
  237.         cmp     [edx+usb_endpoint_descr.bDescriptorType], USB_INTERFACE_DESCR
  238.         jz      .errorep
  239. ; 1d. Ignore all interface-related descriptors except endpoint descriptor.
  240.         cmp     [edx+usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR
  241.         jnz     .lookep
  242. ; 1e. Length of endpoint descriptor must be at least sizeof.usb_endpoint_descr.
  243.         cmp     [edx+usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr
  244.         jb      .errorep
  245. ; 1f. Ignore all endpoints except for INTERRUPT IN.
  246.         cmp     [edx+usb_endpoint_descr.bEndpointAddress], 0
  247.         jge     .lookep
  248.         mov     al, [edx+usb_endpoint_descr.bmAttributes]
  249.         and     al, 3
  250.         cmp     al, INTERRUPT_PIPE
  251.         jnz     .lookep
  252. ; We have located the descriptor for INTERRUPT IN endpoint,
  253. ; the pointer is in edx.
  254. ; 2. Allocate memory for the hub descriptor.
  255. ; Maximum length (assuming 255 downstream ports) is 40 bytes.
  256. ; Allocate 4 extra bytes to keep wMaxPacketSize.
  257. ; 2a. Save registers.
  258.         push    edx
  259. ; 2b. Call the allocator.
  260.         movi    eax, 44
  261.         call    malloc
  262. ; 2c. Restore registers.
  263.         pop     ecx
  264. ; 2d. If failed, say something to the debug board and return error.
  265.         test    eax, eax
  266.         jz      .nomemory
  267. ; 2e. Store the pointer in esi. xchg eax,r32 is one byte shorter than mov.
  268.         xchg    esi, eax
  269. ; 3. Open a pipe for the status endpoint with descriptor found in step 1.
  270.         mov     ebx, [.pipe]
  271.         movzx   eax, [ecx+usb_endpoint_descr.bEndpointAddress]
  272.         movzx   edx, [ecx+usb_endpoint_descr.bInterval]
  273.         movzx   ecx, [ecx+usb_endpoint_descr.wMaxPacketSize]
  274.         test    ecx, (1 shl 11) - 1
  275.         jz      .free
  276.         push    ecx
  277.         stdcall usb_open_pipe, ebx, eax, ecx, INTERRUPT_PIPE, edx
  278.         pop     ecx
  279. ; If failed, free the memory allocated in step 2,
  280. ; say something to the debug board and return error.
  281.         test    eax, eax
  282.         jz      .free
  283. ; 4. Send control query for the hub descriptor,
  284. ; pass status pipe as a callback parameter,
  285. ; allow short packets.
  286.         and     ecx, (1 shl 11) - 1
  287.         mov     [esi+40], ecx
  288.         mov     dword [esi], 0xA0 + \   ; class-specific request
  289.                 (USB_GET_DESCRIPTOR shl 8) + \
  290.                 (0 shl 16) + \          ; descriptor index 0
  291.                 (USB_HUB_DESCRIPTOR shl 24)
  292.         mov     dword [esi+4], 40 shl 16
  293.         stdcall usb_control_async, ebx, esi, esi, 40, usb_hub_got_config, eax, 1
  294. ; 5. If failed, free the memory allocated in step 2,
  295. ; say something to the debug board and return error.
  296.         test    eax, eax
  297.         jz      .free
  298. ; Otherwise, return 1. usb_hub_got_config will overwrite it later.
  299.         xor     eax, eax
  300.         inc     eax
  301.         jmp     .nothing
  302. .free:
  303.         xchg    eax, esi
  304.         call    free
  305.         jmp     .return0
  306. .errorep:
  307.         dbgstr 'Invalid config descriptor for a hub'
  308.         jmp     .return0
  309. .nomemory:
  310.         dbgstr 'No memory for USB hub data'
  311. .return0:
  312.         xor     eax, eax
  313. .nothing:
  314.         pop     esi ebx         ; restore used registers to be stdcall
  315.         retn    12
  316. endp
  317.  
  318. ; This procedure is called when the request for the hub descriptor initiated
  319. ; by usb_hub_init is finished, either successfully or unsuccessfully.
  320. proc usb_hub_got_config stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  321.         push    ebx             ; save used registers to be stdcall
  322. ; 1. If failed, say something to the debug board, free the buffer
  323. ; and stop the initialization.
  324.         cmp     [status], 0
  325.         jnz     .invalid
  326. ; 2. The length must be at least sizeof.usb_hub_descr.
  327. ; Note that [length] includes 8 bytes of setup packet.
  328.         cmp     [length], 8 + sizeof.usb_hub_descr
  329.         jb      .invalid
  330. ; 3. Sanity checks for the hub descriptor.
  331.         mov     eax, [buffer]
  332. if USB_DUMP_DESCRIPTORS
  333.         mov     ecx, [length]
  334.         sub     ecx, 8
  335.         DEBUGF 1,'K : hub config:'
  336.         push    eax
  337. @@:
  338.         DEBUGF 1,' %x',[eax]:2
  339.         inc     eax
  340.         dec     ecx
  341.         jnz     @b
  342.         DEBUGF 1,'\n'
  343.         pop     eax
  344. end if
  345.         cmp     [eax+usb_hub_descr.bLength], sizeof.usb_hub_descr
  346.         jb      .invalid
  347.         cmp     [eax+usb_hub_descr.bDescriptorType], USB_HUB_DESCRIPTOR
  348.         jnz     .invalid
  349.         movzx   ecx, [eax+usb_hub_descr.bNbrPorts]
  350.         test    ecx, ecx
  351.         jz      .invalid
  352. ; 4. We use sizeof.usb_hub_descr bytes plus DeviceRemovable info;
  353. ; size of DeviceRemovable is (NumPorts+1) bits, this gives
  354. ; floor(NumPorts/8)+1 bytes. Check that all data are present in the
  355. ; descriptor and were successfully read.
  356.         mov     edx, ecx
  357.         shr     edx, 3
  358.         add     edx, sizeof.usb_hub_descr + 1
  359.         cmp     [eax+usb_hub_descr.bLength], dl
  360.         jb      .invalid
  361.         sub     [length], 8
  362.         cmp     [length], edx
  363.         jb      .invalid
  364. ; 5. Allocate the memory for usb_hub structure.
  365. ; Total size of variable-length data is ALIGN_UP(floor(NumPorts/8)+1+MaxPacketSize,4)+8*NumPorts.
  366.         add     edx, [eax+40]
  367.         add     edx, sizeof.usb_hub - sizeof.usb_hub_descr + 3
  368.         and     edx, not 3
  369.         lea     eax, [edx+ecx*8]
  370.         push    ecx edx
  371.         call    malloc
  372.         pop     edx ecx
  373.         test    eax, eax
  374.         jz      .nomemory
  375.         xchg    eax, ebx
  376. ; 6. Fill usb_hub structure.
  377.         mov     [ebx+usb_hub.NumPorts], ecx
  378.         add     edx, ebx
  379.         mov     [ebx+usb_hub.ConnectedDevicesPtr], edx
  380.         mov     eax, [pipe]
  381.         mov     [ebx+usb_hub.ConfigPipe], eax
  382.         mov     edx, [eax+usb_pipe.Controller]
  383.         mov     [ebx+usb_hub.Controller], edx
  384.         mov     eax, [calldata]
  385.         mov     [ebx+usb_hub.StatusPipe], eax
  386.         push    esi edi
  387.         mov     esi, [buffer]
  388.         mov     eax, [esi+40]
  389.         mov     [ebx+usb_hub.MaxPacketSize], eax
  390. ; The following commands load bNbrPorts, wHubCharacteristics, bPwrOn2PwrGood.
  391.         mov     edx, dword [esi+usb_hub_descr.bNbrPorts]
  392.         mov     dl, 0
  393. ; The following command zeroes AccStatusChange and stores
  394. ; HubCharacteristics and PowerOnInterval.
  395.         mov     dword [ebx+usb_hub.AccStatusChange], edx
  396.         xor     eax, eax
  397.         mov     [ebx+usb_hub.Actions], eax
  398. ; Copy DeviceRemovable data.
  399.         lea     edi, [ebx+sizeof.usb_hub]
  400.         add     esi, sizeof.usb_hub_descr
  401.         mov     edx, ecx
  402.         shr     ecx, 3
  403.         inc     ecx
  404.         rep movsb
  405.         mov     [ebx+usb_hub.StatusChangePtr], edi
  406. ; Zero ConnectedDevices.
  407.         mov     edi, [ebx+usb_hub.ConnectedDevicesPtr]
  408.         mov     ecx, edx
  409.         rep stosd
  410.         mov     [ebx+usb_hub.ConnectedTimePtr], edi
  411. ; Set ConnectedTime to -1.
  412.         dec     eax
  413.         mov     ecx, edx
  414.         rep stosd
  415.         pop     edi esi
  416. ; 7. Replace value of 1 returned from usb_hub_init to the real value.
  417. ; Note: hubs are part of the core USB code, so this code can work with
  418. ; internals of other parts. Another way, the only possible one for external
  419. ; drivers, is to use two memory allocations: one (returned from AddDevice and
  420. ; fixed after that) for pointer, another for real data. That would work also,
  421. ; but wastes one allocation.
  422.         mov     eax, [pipe]
  423.         mov     eax, [eax+usb_pipe.DeviceData]
  424.         add     eax, [eax+usb_device_data.Interfaces]
  425. .scan:
  426.         cmp     [eax+usb_interface_data.DriverData], 1
  427.         jnz     @f
  428.         cmp     [eax+usb_interface_data.DriverFunc], usb_hub_pseudosrv - USBSRV.usb_func
  429.         jz      .scan_found
  430. @@:
  431.         add     eax, sizeof.usb_interface_data
  432.         jmp     .scan
  433. .scan_found:
  434.         mov     [eax+usb_interface_data.DriverData], ebx
  435. ; 8. Insert the hub structure to the tail of the overall list of all hubs.
  436.         mov     ecx, usb_hubs_list
  437.         mov     edx, [ecx+usb_hub.Prev]
  438.         mov     [ecx+usb_hub.Prev], ebx
  439.         mov     [edx+usb_hub.Next], ebx
  440.         mov     [ebx+usb_hub.Prev], edx
  441.         mov     [ebx+usb_hub.Next], ecx
  442. ; 9. Start powering up all ports.
  443.         DEBUGF 1,'K : found hub with %d ports\n',[ebx+usb_hub.NumPorts]
  444.         lea     eax, [ebx+usb_hub.ConfigBuffer]
  445.         xor     ecx, ecx
  446.         mov     dword [eax], 23h + \    ; class-specific request to hub port
  447.                 (USB_SET_FEATURE shl 8) + \
  448.                 (PORT_POWER shl 16)
  449.         mov     edx, [ebx+usb_hub.NumPorts]
  450.         mov     dword [eax+4], edx
  451.         stdcall usb_control_async, [ebx+usb_hub.ConfigPipe], eax, ecx, ecx, usb_hub_port_powered, ebx, ecx
  452. .freebuf:
  453. ; 10. Free the buffer for hub descriptor and return.
  454.         mov     eax, [buffer]
  455.         call    free
  456.         pop     ebx             ; restore used registers to be stdcall
  457.         ret
  458. .nomemory:
  459.         dbgstr 'No memory for USB hub data'
  460.         jmp     .freebuf
  461. .invalid:
  462.         dbgstr 'Invalid hub descriptor'
  463.         jmp     .freebuf
  464. endp
  465.  
  466. ; This procedure is called when the request to power up some port is completed,
  467. ; either successfully or unsuccessfully.
  468. proc usb_hub_port_powered stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  469. ; 1. Check whether the operation was successful.
  470. ; If not, say something to the debug board and ssstop the initialization.
  471.         cmp     [status], 0
  472.         jnz     .invalid
  473. ; 2. Check whether all ports were powered.
  474. ; If so, go to 4. Otherwise, proceed to 3.
  475.         mov     eax, [calldata]
  476.         dec     dword [eax+usb_hub.ConfigBuffer+4]
  477.         jz      .done
  478. ; 3. Power up the next port and return.
  479.         lea     edx, [eax+usb_hub.ConfigBuffer]
  480.         xor     ecx, ecx
  481.         stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, ecx, usb_hub_port_powered, eax, ecx
  482. .nothing:
  483.         ret
  484. .done:
  485. ; 4. All ports were powered.
  486. ; The hub requires some delay until power will be stable, the delay value
  487. ; is provided in the hub descriptor; we have copied that value to
  488. ; usb_hub.PowerOnInterval. Note the time and set the corresponding flag
  489. ; for usb_hub_process_deferred.
  490.         mov     ecx, [timer_ticks]
  491.         mov     [eax+usb_hub.PoweredOnTime], ecx
  492.         or      [eax+usb_hub.Actions], HUB_WAIT_POWERED
  493.         jmp     .nothing
  494. .invalid:
  495.         dbgstr 'Error while powering hub ports'
  496.         jmp     .nothing
  497. endp
  498.  
  499. ; Requests notification about any changes in hub/ports configuration.
  500. ; Called when initial configuration is done and when a previous notification
  501. ; has been processed.
  502. proc usb_hub_wait_change
  503.         stdcall usb_normal_transfer_async, [eax+usb_hub.StatusPipe], \
  504.                 [eax+usb_hub.StatusChangePtr], [eax+usb_hub.MaxPacketSize], usb_hub_changed, eax, 1
  505.         ret
  506. endp
  507.  
  508. ; This procedure is called when something has changed on the hub.
  509. proc usb_hub_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  510. ;       DEBUGF 1,'K : [%d] int pipe for hub %x\n',[timer_ticks],[calldata]
  511. ; 1. Check whether our request has failed.
  512. ; If so, say something to the debug board and stop processing notifications.
  513.         xor     ecx, ecx
  514.         cmp     [status], ecx
  515.         jnz     .failed
  516. ; 2. If no data were retrieved, restart waiting.
  517.         mov     eax, [calldata]
  518.         cmp     [length], ecx
  519.         jz      .continue
  520. ; 3. If size of data retrieved is less than maximal, pad with zeroes;
  521. ; this corresponds to 'state of other ports was not changed'
  522.         mov     ecx, [eax+usb_hub.NumPorts]
  523.         shr     ecx, 3
  524.         inc     ecx
  525.         sub     ecx, [length]
  526.         jbe     .restart
  527.         push    eax edi
  528.         mov     edi, [buffer]
  529.         add     edi, [length]
  530.         xor     eax, eax
  531.         rep stosb
  532.         pop     edi eax
  533. .restart:
  534. ; State of some elements of the hub was changed.
  535. ; Find the first element that was changed,
  536. ; ask the hub about nature of the change,
  537. ; clear the corresponding change,
  538. ; reask the hub about status+change (it is possible that another change
  539. ; occurs between the first ask and clearing the change; we won't see that
  540. ; change, so we need to query the status after clearing the change),
  541. ; continue two previous steps until nothing changes,
  542. ; process all changes which were registered.
  543. ; When all changes for one element will be processed, return to here and look
  544. ; for other changed elements.
  545.         mov     edx, [eax+usb_hub.StatusChangePtr]
  546. ; We keep all observed changes in the special var usb_hub.AccStatusChange;
  547. ; it will be logical OR of all observed StatusChange's.
  548. ; 4. No observed changes yet, zero usb_hub.AccStatusChange.
  549.         xor     ecx, ecx
  550.         mov     [eax+usb_hub.AccStatusChange], cl
  551. ; 5. Test whether there was a change in the hub itself.
  552. ; If so, query hub state.
  553.         btr     dword [edx], ecx
  554.         jnc     .no_hub_change
  555. .next_hub_change:
  556. ;       DEBUGF 1,'K : [%d] querying status of hub %x\n',[timer_ticks],eax
  557.         lea     edx, [eax+usb_hub.ChangeConfigBuffer]
  558.         lea     ecx, [eax+usb_hub.StatusData]
  559.         mov     dword [edx], 0A0h + \   ; class-specific request from hub itself
  560.                 (USB_GET_STATUS shl 8)
  561.         mov     dword [edx+4], 4 shl 16 ; get 4 bytes
  562.         stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_status, eax, 0
  563.         jmp     .nothing
  564. .no_hub_change:
  565. ; 6. Find the first port with changed state and clear the corresponding bit
  566. ; (so next scan after .restart will not consider this port again).
  567. ; If found, go to 8. Otherwise, advance to 7.
  568.         inc     ecx
  569. .test_port_change:
  570.         btr     [edx], ecx
  571.         jc      .found_port_change
  572.         inc     ecx
  573.         cmp     ecx, [eax+usb_hub.NumPorts]
  574.         jbe     .test_port_change
  575. .continue:
  576. ; 7. All changes have been processed. Wait for next notification.
  577.         call    usb_hub_wait_change
  578. .nothing:
  579.         ret
  580. .found_port_change:
  581.         mov     dword [eax+usb_hub.ChangeConfigBuffer+4], ecx
  582. .next_port_change:
  583. ; 8. Query port state. Continue work in usb_hub_port_status callback.
  584. ;       movzx   ecx, [eax+usb_hub.ChangeConfigBuffer+4]
  585. ;       dec     ecx
  586. ;       DEBUGF 1,'K : [%d] querying status of hub %x port %d\n',[timer_ticks],eax,ecx
  587.         lea     edx, [eax+usb_hub.ChangeConfigBuffer]
  588.         mov     dword [edx], 0A3h + \   ; class-specific request from hub port
  589.                 (USB_GET_STATUS shl 8)
  590.         mov     byte [edx+6], 4         ; data length = 4 bytes
  591.         lea     ecx, [eax+usb_hub.StatusData]
  592.         stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_port_status, eax, 0
  593.         jmp     .nothing
  594. .failed:
  595.         cmp     [status], USB_STATUS_CLOSED
  596.         jz      .nothing
  597.         dbgstr 'Querying hub notification failed'
  598.         jmp     .nothing
  599. endp
  600.  
  601. ; This procedure is called when the request of hub status is completed,
  602. ; either successfully or unsuccessfully.
  603. proc usb_hub_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  604. ; 1. Check whether our request has failed.
  605. ; If so, say something to the debug board and stop processing notifications.
  606.         cmp     [status], 0
  607.         jnz     .failed
  608. ; 2. Accumulate observed changes.
  609.         mov     eax, [calldata]
  610.         mov     dl, byte [eax+usb_hub.StatusChange]
  611.         or      [eax+usb_hub.AccStatusChange], dl
  612. .next_change:
  613. ; 3. Find the first change. If found, advance to 4. Otherwise, go to 5.
  614.         mov     cl, C_HUB_OVER_CURRENT
  615.         btr     dword [eax+usb_hub.StatusChange], 1
  616.         jc      .clear_hub_change
  617.         mov     cl, C_HUB_LOCAL_POWER
  618.         btr     dword [eax+usb_hub.StatusChange], 0
  619.         jnc     .final
  620. .clear_hub_change:
  621. ; 4. Clear the change and continue in usb_hub_change_cleared callback.
  622.         lea     edx, [eax+usb_hub.ChangeConfigBuffer]
  623.         mov     dword [edx], 20h + \    ; class-specific request to hub itself
  624.                 (USB_CLEAR_FEATURE shl 8)
  625.         mov     [edx+2], cl     ; feature selector
  626.         and     dword [edx+4], 0
  627.         stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_change_cleared, eax, 0
  628. .nothing:
  629.         ret
  630. .final:
  631. ; 5. All changes cleared and accumulated, now process them.
  632. ; Note: that needs work.
  633.         DEBUGF 1,'K : hub status %x\n',[eax+usb_hub.AccStatusChange]:2
  634.         test    [eax+usb_hub.AccStatusChange], 1
  635.         jz      .no_local_power
  636.         test    [eax+usb_hub.StatusData], 1
  637.         jz      .local_power_lost
  638.         dbgstr 'Hub local power is now good'
  639.         jmp     .no_local_power
  640. .local_power_lost:
  641.         dbgstr 'Hub local power is now lost'
  642. .no_local_power:
  643.         test    [eax+usb_hub.AccStatusChange], 2
  644.         jz      .no_overcurrent
  645.         test    [eax+usb_hub.StatusData], 2
  646.         jz      .no_overcurrent
  647.         dbgstr 'Hub global overcurrent'
  648. .no_overcurrent:
  649. ; 6. Process possible changes for other ports.
  650.         jmp     usb_hub_changed.restart
  651. .failed:
  652.         dbgstr 'Querying hub status failed'
  653.         jmp     .nothing
  654. endp
  655.  
  656. ; This procedure is called when the request to clear hub change is completed,
  657. ; either successfully or unsuccessfully.
  658. proc usb_hub_change_cleared stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  659. ; 1. Check whether our request has failed.
  660. ; If so, say something to the debug board and stop processing notifications.
  661.         cmp     [status], 0
  662.         jnz     .failed
  663. ; 2. If there is a change which was observed, but not yet cleared,
  664. ; go to the code which clears it.
  665.         mov     eax, [calldata]
  666.         cmp     [eax+usb_hub.StatusChange], 0
  667.         jnz     usb_hub_status.next_change
  668. ; 3. Otherwise, go to the code which queries the status.
  669.         jmp     usb_hub_changed.next_hub_change
  670. .failed:
  671.         dbgstr 'Clearing hub change failed'
  672.         ret
  673. endp
  674.  
  675. ; This procedure is called when the request of port status is completed,
  676. ; either successfully or unsuccessfully.
  677. proc usb_hub_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  678. ; 1. Check whether our request has failed.
  679. ; If so, say something to the debug board and stop processing notifications.
  680.         cmp     [status], 0
  681.         jnz     .failed
  682. ; 2. Accumulate observed changes.
  683.         mov     eax, [calldata]
  684. ;       movzx   ecx, [eax+usb_hub.ChangeConfigBuffer+4]
  685. ;       dec     ecx
  686. ;       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
  687.         mov     dl, byte [eax+usb_hub.StatusChange]
  688.         or      [eax+usb_hub.AccStatusChange], dl
  689. .next_change:
  690. ; 3. Find the first change. If found, advance to 4. Otherwise, go to 5.
  691. ; Ignore change in reset status; it is cleared by synchronous code
  692. ; (usb_hub_process_deferred), so avoid unnecessary interference.
  693. ;       mov     cl, C_PORT_RESET
  694.         btr     dword [eax+usb_hub.StatusChange], PORT_RESET
  695. ;       jc      .clear_port_change
  696.         mov     cl, C_PORT_OVER_CURRENT
  697.         btr     dword [eax+usb_hub.StatusChange], PORT_OVER_CURRENT
  698.         jc      .clear_port_change
  699.         mov     cl, C_PORT_SUSPEND
  700.         btr     dword [eax+usb_hub.StatusChange], PORT_SUSPEND
  701.         jc      .clear_port_change
  702.         mov     cl, C_PORT_ENABLE
  703.         btr     dword [eax+usb_hub.StatusChange], PORT_ENABLE
  704.         jc      .clear_port_change
  705.         mov     cl, C_PORT_CONNECTION
  706.         btr     dword [eax+usb_hub.StatusChange], PORT_CONNECTION
  707.         jnc     .final
  708. .clear_port_change:
  709. ; 4. Clear the change and continue in usb_hub_port_changed callback.
  710.         call    usb_hub_clear_port_change
  711.         jmp     .nothing
  712. .final:
  713. ; All changes cleared and accumulated, now process them.
  714.         movzx   ecx, byte [eax+usb_hub.ChangeConfigBuffer+4]
  715.         dec     ecx
  716.         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
  717. ; 5. Process connect/disconnect events.
  718. ; 5a. Test whether there is such event.
  719.         test    byte [eax+usb_hub.AccStatusChange], 1 shl PORT_CONNECTION
  720.         jz      .nodisconnect
  721. ; 5b. If there was a connected device, notify the main code about disconnect.
  722.         push    ebx
  723.         mov     edx, [eax+usb_hub.ConnectedDevicesPtr]
  724.         xor     ebx, ebx
  725.         xchg    ebx, [edx+ecx*4]
  726.         test    ebx, ebx
  727.         jz      @f
  728.         push    eax ecx
  729.         call    usb_device_disconnected
  730.         pop     ecx eax
  731. @@:
  732.         pop     ebx
  733. ; 5c. If the disconnect event corresponds to the port which is currently
  734. ; resetting, then another request from synchronous code could be in the fly,
  735. ; so aborting reset immediately would lead to problems with those requests.
  736. ; Thus, just set the corresponding status and let the synchronous code process.
  737.         test    byte [eax+usb_hub.Actions], (HUB_RESET_SIGNAL or HUB_RESET_RECOVERY)
  738.         jz      @f
  739.         mov     edx, [eax+usb_hub.Controller]
  740.         cmp     [edx+usb_controller.ResettingPort], cl
  741.         jnz     @f
  742.         mov     [edx+usb_controller.ResettingStatus], -1
  743. @@:
  744. ; 5d. If the current status is 'connected', store the current time as connect
  745. ; time and set the corresponding bit for usb_hub_process_deferred.
  746. ; Otherwise, set connect time to -1.
  747. ; If current time is -1, pretend that the event occured one tick later and
  748. ; store zero.
  749.         mov     edx, [eax+usb_hub.ConnectedTimePtr]
  750.         test    byte [eax+usb_hub.StatusData], 1 shl PORT_CONNECTION
  751.         jz      .disconnected
  752.         or      [eax+usb_hub.Actions], HUB_WAIT_CONNECT
  753.         push    eax
  754.         call    usb_hub_store_connected_time
  755.         pop     eax
  756.         jmp     @f
  757. .disconnected:
  758.         or      dword [edx+ecx*4], -1
  759. @@:
  760. .nodisconnect:
  761. ; 6. Process port disabling.
  762.         test    [eax+usb_hub.AccStatusChange], 1 shl PORT_ENABLE
  763.         jz      .nodisable
  764.         test    byte [eax+usb_hub.StatusData], 1 shl PORT_ENABLE
  765.         jnz     .nodisable
  766. ; Note: that needs work.
  767.         dbgstr 'Port disabled'
  768. .nodisable:
  769. ; 7. Process port overcurrent.
  770.         test    [eax+usb_hub.AccStatusChange], 1 shl PORT_OVER_CURRENT
  771.         jz      .noovercurrent
  772.         test    byte [eax+usb_hub.StatusData], 1 shl PORT_OVER_CURRENT
  773.         jz      .noovercurrent
  774. ; Note: that needs work.
  775.         dbgstr 'Port over-current'
  776. .noovercurrent:
  777. ; 8. Process possible changes for other ports.
  778.         jmp     usb_hub_changed.restart
  779. .failed:
  780.         dbgstr 'Querying port status failed'
  781. .nothing:
  782.         ret
  783. endp
  784.  
  785. ; Helper procedure to store current time in ConnectedTime,
  786. ; advancing -1 to zero if needed.
  787. proc usb_hub_store_connected_time
  788.         mov     eax, [timer_ticks]
  789. ; transform -1 to 0, leave other values as is
  790.         cmp     eax, -1
  791.         sbb     eax, -1
  792.         mov     [edx+ecx*4], eax
  793.         ret
  794. endp
  795.  
  796. ; Helper procedure for several parts of hub code.
  797. ; Sends a request to clear the given feature of the port.
  798. ; eax -> usb_hub, cl = feature;
  799. ; as is should be called from async code, sync code should set
  800. ; edx to ConfigBuffer and call usb_hub_clear_port_change.buffer;
  801. ; port number (1-based) should be filled in [edx+4] by previous requests.
  802. proc usb_hub_clear_port_change
  803.         lea     edx, [eax+usb_hub.ChangeConfigBuffer]
  804. .buffer:
  805. ;       push    edx
  806. ;       movzx   edx, byte [edx+4]
  807. ;       dec     edx
  808. ;       DEBUGF 1,'K : [%d] hub %x port %d clear feature %d\n',[timer_ticks],eax,edx,cl
  809. ;       pop     edx
  810.         mov     dword [edx], 23h + \    ; class-specific request to hub port
  811.                 (USB_CLEAR_FEATURE shl 8)
  812.         mov     byte [edx+2], cl
  813.         and     dword [edx+4], 0xFF
  814.         stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, edx, 0, usb_hub_port_changed, eax, 0
  815.         ret
  816. endp
  817.  
  818. ; This procedure is called when the request to clear port change is completed,
  819. ; either successfully or unsuccessfully.
  820. proc usb_hub_port_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  821. ; 1. Check whether our request has failed.
  822. ; If so, say something to the debug board and stop processing notifications.
  823.         cmp     [status], 0
  824.         jnz     .failed
  825. ; 2. If the request was originated by synchronous code, no further processing
  826. ; is required.
  827.         mov     eax, [calldata]
  828.         lea     edx, [eax+usb_hub.ConfigBuffer]
  829.         cmp     [buffer], edx
  830.         jz      .nothing
  831. ; 3. If there is a change which was observed, but not yet cleared,
  832. ; go to the code which clears it.
  833.         cmp     [eax+usb_hub.StatusChange], 0
  834.         jnz     usb_hub_port_status.next_change
  835. ; 4. Otherwise, go to the code which queries the status.
  836.         jmp     usb_hub_changed.next_port_change
  837. .failed:
  838.         dbgstr 'Clearing port change failed'
  839. .nothing:
  840.         ret
  841. endp
  842.  
  843. ; This procedure is called in the USB thread from usb_thread_proc,
  844. ; contains synchronous code which should be activated at certain time
  845. ; (e.g. reset a recently connected device after debounce interval 100ms).
  846. ; Returns the number of ticks when it should be called next time.
  847. proc usb_hub_process_deferred
  848. ; 1. Top-of-stack will contain return value; initialize to infinite timeout.
  849.         push    -1
  850. ; 2. If wait for stable power is active, then
  851. ; either reschedule wakeup (if time is not over)
  852. ; or start processing notifications.
  853.         test    byte [esi+usb_hub.Actions], HUB_WAIT_POWERED
  854.         jz      .no_powered
  855.         movzx   eax, [esi+usb_hub.PowerOnInterval]
  856. ; three following instructions are equivalent to edx = ceil(eax / 5) + 1
  857. ; 1 extra tick is added to make sure that the interval is at least as needed
  858. ; (it is possible that PoweredOnTime was set just before timer interrupt, and
  859. ; this test goes on just after timer interrupt)
  860.         add     eax, 9
  861. ; two following instructions are equivalent to edx = floor(eax / 5)
  862. ; for any 0 <= eax < 40000000h
  863.         mov     ecx, 33333334h
  864.         mul     ecx
  865.         mov     eax, [timer_ticks]
  866.         sub     eax, [esi+usb_hub.PoweredOnTime]
  867.         sub     eax, edx
  868.         jge     .powered_on
  869.         neg     eax
  870.         pop     ecx
  871.         push    eax
  872.         jmp     .no_powered
  873. .powered_on:
  874.         and     [esi+usb_hub.Actions], not HUB_WAIT_POWERED
  875.         mov     eax, esi
  876.         call    usb_hub_wait_change
  877. .no_powered:
  878. ; 3. If reset is pending, check whether we can start it and start it, if so.
  879.         test    byte [esi+usb_hub.Actions], HUB_RESET_WAITING
  880.         jz      .no_wait_reset
  881.         mov     eax, [esi+usb_hub.Controller]
  882.         cmp     [eax+usb_controller.ResettingPort], -1
  883.         jnz     .no_wait_reset
  884.         call    usb_hub_initiate_reset
  885. .no_wait_reset:
  886. ; 4. If reset signalling is active, wait for end of reset signalling
  887. ; and schedule wakeup in 1 tick.
  888.         test    byte [esi+usb_hub.Actions], HUB_RESET_SIGNAL
  889.         jz      .no_resetting_port
  890. ; It has no sense to query status several times per tick.
  891.         mov     eax, [timer_ticks]
  892.         cmp     eax, [esi+usb_hub.ResetTime]
  893.         jz      @f
  894.         mov     [esi+usb_hub.ResetTime], eax
  895.         movzx   ecx, byte [esi+usb_hub.ConfigBuffer+4]
  896.         mov     eax, usb_hub_resetting_port_status
  897.         call    usb_hub_query_port_status
  898. @@:
  899.         pop     eax
  900.         push    1
  901. .no_resetting_port:
  902. ; 5. If reset recovery is active and time is not over, reschedule wakeup.
  903.         test    byte [esi+usb_hub.Actions], HUB_RESET_RECOVERY
  904.         jz      .no_reset_recovery
  905.         mov     eax, [timer_ticks]
  906.         sub     eax, [esi+usb_hub.ResetTime]
  907.         sub     eax, USB_RESET_RECOVERY_TIME
  908.         jge     .reset_done
  909.         neg     eax
  910.         cmp     [esp], eax
  911.         jb      @f
  912.         mov     [esp], eax
  913. @@:
  914.         jmp     .no_reset_recovery
  915. .reset_done:
  916. ; 6. If reset recovery is active and time is over, clear 'reset recovery' flag,
  917. ; notify other code about a new device and let it do further steps.
  918. ; If that fails, stop reset process for this port and disable that port.
  919.         and     [esi+usb_hub.Actions], not HUB_RESET_RECOVERY
  920. ; Bits 9-10 of port status encode port speed.
  921. ; If PORT_LOW_SPEED is set, the device is low-speed. Otherwise,
  922. ; PORT_HIGH_SPEED bit distinguishes full-speed and high-speed devices.
  923. ; This corresponds to values of USB_SPEED_FS=0, USB_SPEED_LS=1, USB_SPEED_HS=2.
  924.         mov     eax, dword [esi+usb_hub.ResetStatusData]
  925.         shr     eax, PORT_LOW_SPEED
  926.         and     eax, 3
  927.         test    al, 1
  928.         jz      @f
  929.         mov     al, 1
  930. @@:
  931. ;       movzx   ecx, [esi+usb_hub.ConfigBuffer+4]
  932. ;       dec     ecx
  933. ;       DEBUGF 1,'K : [%d] hub %x port %d speed %d\n',[timer_ticks],esi,ecx,eax
  934.         push    esi
  935.         mov     esi, [esi+usb_hub.Controller]
  936.         cmp     [esi+usb_controller.ResettingStatus], -1
  937.         jz      .disconnected_while_reset
  938.         mov     edx, [esi+usb_controller.HardwareFunc]
  939.         call    [edx+usb_hardware_func.NewDevice]
  940.         pop     esi
  941.         test    eax, eax
  942.         jnz     .no_reset_recovery
  943.         mov     eax, esi
  944.         call    usb_hub_disable_resetting_port
  945.         jmp     .no_reset_recovery
  946. .disconnected_while_reset:
  947.         pop     esi
  948.         mov     eax, esi
  949.         call    usb_hub_reset_aborted
  950. .no_reset_recovery:
  951. ; 7. Handle recent connection events.
  952. ; Note: that should be done after step 6, because step 6 can clear
  953. ; HUB_RESET_IN_PROGRESS flag.
  954. ; 7a. Test whether there is such an event pending. If no, skip this step.
  955.         test    byte [esi+usb_hub.Actions], HUB_WAIT_CONNECT
  956.         jz      .no_wait_connect
  957. ; 7b. If we have started reset process for another port in the same hub,
  958. ; skip this step: the buffer for config requests can be used for that port.
  959.         test    byte [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS
  960.         jnz     .no_wait_connect
  961. ; 7c. Clear flag 'there are connection events which should be processed'.
  962. ; If there are another connection events, this flag will be set again.
  963.         and     [esi+usb_hub.Actions], not HUB_WAIT_CONNECT
  964. ; 7d. Prepare for loop over all ports.
  965.         xor     ecx, ecx
  966. .test_wait_connect:
  967. ; 7e. For every port test for recent connection event.
  968. ; If none, continue the loop for the next port.
  969.         mov     edx, [esi+usb_hub.ConnectedTimePtr]
  970.         mov     eax, [edx+ecx*4]
  971.         cmp     eax, -1
  972.         jz      .next_wait_connect
  973.         or      [esi+usb_hub.Actions], HUB_WAIT_CONNECT
  974. ; 7f. Test whether initial delay is over.
  975.         sub     eax, [timer_ticks]
  976.         neg     eax
  977.         sub     eax, USB_CONNECT_DELAY
  978.         jge     .connect_delay_over
  979. ; 7g. The initial delay is not over;
  980. ; set the corresponding flag again, reschedule wakeup and continue the loop.
  981.         neg     eax
  982.         cmp     [esp], eax
  983.         jb      @f
  984.         mov     [esp], eax
  985. @@:
  986.         jmp     .next_wait_connect
  987. .connect_delay_over:
  988. ; The initial delay is over.
  989. ; It is possible that there was disconnect event during that delay, probably
  990. ; with connect event after that. If so, we should restart the waiting. However,
  991. ; on the hardware level connect/disconnect events from hubs are implemented
  992. ; using polling with interval selected by the hub, so it is possible that
  993. ; we have not yet observed that disconnect event.
  994. ; Thus, we query port status+change data before all further processing.
  995. ; 7h. Send the request for status+change data.
  996.         push    ecx
  997. ; Hub requests expect 1-based port number, not zero-based we operate with.
  998.         inc     ecx
  999.         mov     eax, usb_hub_connect_port_status
  1000.         call    usb_hub_query_port_status
  1001.         pop     ecx
  1002. ; 3i. If request has been submitted successfully, set the flag
  1003. ; 'reset in progress, config buffer is owned by reset process' and break
  1004. ; from the loop.
  1005.         test    eax, eax
  1006.         jz      .next_wait_connect
  1007.         or      [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS
  1008.         jmp     .no_wait_connect
  1009. .next_wait_connect:
  1010. ; 7j. Continue the loop for next port.
  1011.         inc     ecx
  1012.         cmp     ecx, [esi+usb_hub.NumPorts]
  1013.         jb      .test_wait_connect
  1014. .no_wait_connect:
  1015. ; 8. Pop return value from top-of-stack and return.
  1016.         pop     eax
  1017.         ret
  1018. endp
  1019.  
  1020. ; Helper procedure for other code. Called when reset process is aborted.
  1021. proc usb_hub_reset_aborted
  1022. ; Clear 'reset in progress' flag and test for other devices which could be
  1023. ; waiting for reset.
  1024.         and     [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
  1025.         push    esi
  1026.         mov     esi, [eax+usb_hub.Controller]
  1027.         call    usb_test_pending_port
  1028.         pop     esi
  1029.         ret
  1030. endp
  1031.  
  1032. ; Helper procedure for usb_hub_process_deferred.
  1033. ; Sends a request to query port status.
  1034. ; esi -> usb_hub, eax = callback, ecx = 1-based port.
  1035. proc usb_hub_query_port_status
  1036. ;       dec     ecx
  1037. ;       DEBUGF 1,'K : [%d] [main] hub %x port %d query status\n',[timer_ticks],esi,ecx
  1038. ;       inc     ecx
  1039.         add     ecx, 4 shl 16           ; data length = 4
  1040.         lea     edx, [esi+usb_hub.ConfigBuffer]
  1041.         mov     dword [edx], 0A3h + \   ; class-specific request from hub port
  1042.                 (USB_GET_STATUS shl 8)
  1043.         mov     dword [edx+4], ecx
  1044.         lea     ecx, [esi+usb_hub.ResetStatusData]
  1045.         stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, ecx, 4, eax, esi, 0
  1046.         ret
  1047. endp
  1048.  
  1049. ; This procedure is called when the request to query port status
  1050. ; initiated by usb_hub_process_deferred for testing connection is completed,
  1051. ; either successfully or unsuccessfully.
  1052. proc usb_hub_connect_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  1053.         push    esi     ; save used register to be stdcall
  1054.         mov     eax, [calldata]
  1055.         mov     esi, [pipe]
  1056. ;       movzx   ecx, [eax+usb_hub.ConfigBuffer+4]
  1057. ;       dec     ecx
  1058. ;       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
  1059. ; 1. In any case, clear 'reset in progress' flag.
  1060. ; If everything is ok, it would be set again.
  1061.         and     [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
  1062. ; 2. If the request has failed, stop reset process.
  1063.         cmp     [status], 0
  1064.         jnz     .nothing
  1065.         mov     edx, [eax+usb_hub.ConnectedTimePtr]
  1066.         movzx   ecx, byte [eax+usb_hub.ConfigBuffer+4]
  1067.         dec     ecx
  1068. ; 3. Test whether there was a disconnect event.
  1069.         test    byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION
  1070.         jz      .reset
  1071. ; 4. There was a disconnect event.
  1072. ; There is another handler of connect/disconnect events, usb_hub_port_status.
  1073. ; However, we do not know whether it has already processed this event
  1074. ; or it will process it sometime later.
  1075. ; If ConnectedTime is -1, then another handler has already run,
  1076. ; there was no connection event, so just leave the value as -1.
  1077. ; Otherwise, there are two possibilities: either another handler has not yet
  1078. ; run (which is quite likely), or there was a connection event and the other
  1079. ; handler has run exactly while our request was processed (otherwise our
  1080. ; request would not been submitted; this is quite unlikely due to timing
  1081. ; requirements, but not impossible). In this case, set ConnectedTime to the
  1082. ; current time: in the likely case it prevents usb_hub_process_deferred from immediate
  1083. ; issuing of another requests (which would be just waste of time);
  1084. ; in the unlikely case it is still correct (although slightly increases
  1085. ; the debounce interval).
  1086.         cmp     dword [edx+ecx*4], -1
  1087.         jz      .nothing
  1088.         call    usb_hub_store_connected_time
  1089.         jmp     .nothing
  1090. .reset:
  1091. ; 5. The device remained connected for the entire debounce interval;
  1092. ; we can proceed with initialization.
  1093. ; Clear connected time for this port and notify usb_hub_process_deferred that
  1094. ; the new port is waiting for reset.
  1095.         or      dword [edx+ecx*4], -1
  1096.         or      [eax+usb_hub.Actions], HUB_RESET_IN_PROGRESS + HUB_RESET_WAITING
  1097. .nothing:
  1098.         pop     esi     ; restore used register to be stdcall
  1099.         ret
  1100. endp
  1101.  
  1102. ; This procedure is called when the request to query port status
  1103. ; initiated by usb_hub_process_deferred for testing reset status is completed,
  1104. ; either successfully or unsuccessfully.
  1105. proc usb_hub_resetting_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  1106. ; 1. If the request has failed, do nothing.
  1107.         cmp     [status], 0
  1108.         jnz     .nothing
  1109. ; 2. If reset signalling is still active, do nothing.
  1110.         mov     eax, [calldata]
  1111. ;       movzx   ecx, [eax+usb_hub.ConfigBuffer+4]
  1112. ;       dec     ecx
  1113. ;       DEBUGF 1,'K : hub %x port %d ResetStatusData = %x change = %x\n',eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4
  1114.         test    byte [eax+usb_hub.ResetStatusData], 1 shl PORT_RESET
  1115.         jnz     .nothing
  1116. ; 3. Store the current time to start reset recovery interval
  1117. ; and clear 'reset signalling active' flag.
  1118.         mov     edx, [timer_ticks]
  1119.         mov     [eax+usb_hub.ResetTime], edx
  1120.         and     [eax+usb_hub.Actions], not HUB_RESET_SIGNAL
  1121. ; 4. If the device has not been disconnected, set 'reset recovery active' bit.
  1122. ; Otherwise, terminate reset process.
  1123.         test    byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION
  1124.         jnz     .disconnected
  1125.         or      [eax+usb_hub.Actions], HUB_RESET_RECOVERY
  1126. .common:
  1127. ; In any case, clear change of resetting status.
  1128.         lea     edx, [eax+usb_hub.ConfigBuffer]
  1129.         mov     cl, C_PORT_RESET
  1130.         call    usb_hub_clear_port_change.buffer
  1131. .nothing:
  1132.         ret
  1133. .disconnected:
  1134.         call    usb_hub_reset_aborted
  1135.         jmp     .common
  1136. endp
  1137.  
  1138. ; Helper procedure for usb_hub_process_deferred. Initiates reset signalling
  1139. ; on the current port (given by 1-based value [ConfigBuffer+4]).
  1140. ; esi -> usb_hub, eax -> usb_controller
  1141. proc usb_hub_initiate_reset
  1142. ; 1. Store hub+port data in the controller structure.
  1143.         movzx   ecx, [esi+usb_hub.ConfigBuffer+4]
  1144.         dec     ecx
  1145.         mov     [eax+usb_controller.ResettingPort], cl
  1146.         mov     [eax+usb_controller.ResettingHub], esi
  1147. ; 2. Store the current time and set 'reset signalling active' flag.
  1148.         mov     eax, [timer_ticks]
  1149.         mov     [esi+usb_hub.ResetTime], eax
  1150.         and     [esi+usb_hub.Actions], not HUB_RESET_WAITING
  1151.         or      [esi+usb_hub.Actions], HUB_RESET_SIGNAL
  1152. ; 3. Send request to the hub to initiate request signalling.
  1153.         lea     edx, [esi+usb_hub.ConfigBuffer]
  1154. ;       DEBUGF 1,'K : [%d] hub %x port %d initiate reset\n',[timer_ticks],esi,ecx
  1155.         mov     dword [edx], 23h + \
  1156.                 (USB_SET_FEATURE shl 8) + \
  1157.                 (PORT_RESET shl 16)
  1158.         and     dword [edx+4], 0xFF
  1159.         stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_reset_started, esi, 0
  1160.         test    eax, eax
  1161.         jnz     @f
  1162.         mov     eax, esi
  1163.         call    usb_hub_reset_aborted
  1164. @@:
  1165.         ret
  1166. endp
  1167.  
  1168. ; This procedure is called when the request to start reset signalling initiated
  1169. ; by usb_hub_initiate_reset is completed, either successfully or unsuccessfully.
  1170. proc usb_hub_reset_started stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
  1171. ; If the request is successful, do nothing.
  1172. ; Otherwise, clear 'reset signalling' flag and abort reset process.
  1173.         mov     eax, [calldata]
  1174. ;       movzx   ecx, [eax+usb_hub.ConfigBuffer+4]
  1175. ;       dec     ecx
  1176. ;       DEBUGF 1,'K : [%d] hub %x port %d reset started\n',[timer_ticks],eax,ecx
  1177.         cmp     [status], 0
  1178.         jz      .nothing
  1179.         and     [eax+usb_hub.Actions], not HUB_RESET_SIGNAL
  1180.         dbgstr 'Failed to reset hub port'
  1181.         call    usb_hub_reset_aborted
  1182. .nothing:
  1183.         ret
  1184. endp
  1185.  
  1186. ; This procedure is called by the protocol layer if something has failed during
  1187. ; initial stages of the configuration process, so the device should be disabled
  1188. ; at hub level.
  1189. proc usb_hub_disable_resetting_port
  1190.         and     [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
  1191. ;       movzx   ecx, [eax+usb_hub.ConfigBuffer+4]
  1192. ;       dec     ecx
  1193. ;       DEBUGF 1,'K : [%d] hub %x port %d disable\n',[timer_ticks],eax,ecx
  1194.         lea     edx, [eax+usb_hub.ConfigBuffer]
  1195.         mov     cl, PORT_ENABLE
  1196.         jmp     usb_hub_clear_port_change.buffer
  1197. endp
  1198.  
  1199. ; This procedure is called when the hub is disconnected.
  1200. proc usb_hub_disconnect
  1201. virtual at esp
  1202.                 dd      ?       ; return address
  1203. .hubdata        dd      ?
  1204. end virtual
  1205. ; 1. If the hub is disconnected during initial configuration,
  1206. ; 1 is stored as hub data and there is nothing to do.
  1207.         mov     eax, [.hubdata]
  1208.         cmp     eax, 1
  1209.         jz      .nothing
  1210. ; 2. Remove the hub from the overall list.
  1211.         mov     ecx, [eax+usb_hub.Next]
  1212.         mov     edx, [eax+usb_hub.Prev]
  1213.         mov     [ecx+usb_hub.Prev], edx
  1214.         mov     [edx+usb_hub.Next], ecx
  1215. ; 3. If some child is in reset process, abort reset.
  1216.         push    esi
  1217.         mov     esi, [eax+usb_hub.Controller]
  1218.         cmp     [esi+usb_controller.ResettingHub], eax
  1219.         jnz     @f
  1220.         cmp     [esi+usb_controller.ResettingPort], -1
  1221.         jz      @f
  1222.         push    eax
  1223.         call    usb_test_pending_port
  1224.         pop     eax
  1225. @@:
  1226.         pop     esi
  1227. ; 4. Loop over all children and notify other code that they were disconnected.
  1228.         push    ebx
  1229.         xor     ecx, ecx
  1230. .disconnect_children:
  1231.         mov     ebx, [eax+usb_hub.ConnectedDevicesPtr]
  1232.         mov     ebx, [ebx+ecx*4]
  1233.         test    ebx, ebx
  1234.         jz      @f
  1235.         push    eax ecx
  1236.         call    usb_device_disconnected
  1237.         pop     ecx eax
  1238. @@:
  1239.         inc     ecx
  1240.         cmp     ecx, [eax+usb_hub.NumPorts]
  1241.         jb      .disconnect_children
  1242. ; 4. Free memory allocated for the hub data.
  1243.         call    free
  1244.         pop     ebx
  1245. .nothing:
  1246.         retn    4
  1247. endp
  1248.