Subversion Repositories Kolibri OS

Rev

Rev 4227 | Rev 4305 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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