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