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 | ; Functions for USB pipe manipulation: opening/closing, sending data etc. |
12 | ; |
||
13 | USB_STDCALL_VERIFY = 1 |
||
14 | macro stdcall_verify [arg] |
||
15 | { |
||
16 | common |
||
17 | if USB_STDCALL_VERIFY |
||
18 | pushad |
||
19 | stdcall arg |
||
20 | call verify_regs |
||
21 | popad |
||
22 | else |
||
23 | stdcall arg |
||
24 | end if |
||
25 | } |
||
4547 | clevermous | 26 | if USB_STDCALL_VERIFY |
27 | STDCALL_VERIFY_EXTRA = 20h |
||
28 | else |
||
29 | STDCALL_VERIFY_EXTRA = 0 |
||
30 | end if |
||
3520 | clevermous | 31 | |
32 | ; Initialization of usb_static_ep structure, |
||
33 | ; called from controller-specific initialization; edi -> usb_static_ep |
||
34 | proc usb_init_static_endpoint |
||
35 | mov [edi+usb_static_ep.NextVirt], edi |
||
36 | mov [edi+usb_static_ep.PrevVirt], edi |
||
37 | ret |
||
38 | endp |
||
39 | |||
40 | ; Part of API for drivers, see documentation for USBOpenPipe. |
||
41 | proc usb_open_pipe stdcall uses ebx esi edi,\ |
||
42 | config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword |
||
43 | locals |
||
4418 | clevermous | 44 | tt_vars rd 24 ; should be enough for ehci_select_tt_interrupt_list |
3520 | clevermous | 45 | targetsmask dd ? ; S-Mask for USB2 |
46 | bandwidth dd ? |
||
47 | target dd ? |
||
48 | endl |
||
49 | ; 1. Verify type of pipe: it must be one of *_PIPE constants. |
||
50 | ; Isochronous pipes are not supported yet. |
||
51 | mov eax, [type] |
||
52 | cmp eax, INTERRUPT_PIPE |
||
53 | ja .badtype |
||
54 | cmp al, ISOCHRONOUS_PIPE |
||
55 | jnz .goodtype |
||
56 | .badtype: |
||
57 | dbgstr 'unsupported type of USB pipe' |
||
58 | jmp .return0 |
||
59 | .goodtype: |
||
60 | ; 2. Allocate memory for pipe and transfer queue. |
||
61 | ; Empty transfer queue consists of one inactive TD. |
||
62 | mov ebx, [config_pipe] |
||
63 | mov esi, [ebx+usb_pipe.Controller] |
||
64 | mov edx, [esi+usb_controller.HardwareFunc] |
||
65 | call [edx+usb_hardware_func.AllocPipe] |
||
66 | test eax, eax |
||
67 | jz .nothing |
||
68 | mov edi, eax |
||
69 | mov edx, [esi+usb_controller.HardwareFunc] |
||
70 | call [edx+usb_hardware_func.AllocTD] |
||
71 | test eax, eax |
||
72 | jz .free_and_return0 |
||
73 | ; 3. Initialize transfer queue: pointer to transfer descriptor, |
||
74 | ; pointers in transfer descriptor, queue lock. |
||
75 | mov [edi+usb_pipe.LastTD], eax |
||
76 | mov [eax+usb_gtd.NextVirt], eax |
||
77 | mov [eax+usb_gtd.PrevVirt], eax |
||
78 | mov [eax+usb_gtd.Pipe], edi |
||
79 | lea ecx, [edi+usb_pipe.Lock] |
||
80 | call mutex_init |
||
81 | ; 4. Initialize software part of pipe structure, except device-related fields. |
||
82 | mov al, byte [type] |
||
83 | mov [edi+usb_pipe.Type], al |
||
84 | xor eax, eax |
||
85 | mov [edi+usb_pipe.Flags], al |
||
86 | mov [edi+usb_pipe.DeviceData], eax |
||
87 | mov [edi+usb_pipe.Controller], esi |
||
88 | or [edi+usb_pipe.NextWait], -1 |
||
89 | ; 5. Initialize device-related fields: |
||
90 | ; for zero endpoint, set .NextSibling = .PrevSibling = this; |
||
91 | ; for other endpoins, copy device data, take the lock guarding pipe list |
||
92 | ; for the device and verify that disconnect processing has not yet started |
||
93 | ; for the device. (Since disconnect processing also takes that lock, |
||
94 | ; either it has completed or it will not start until we release the lock.) |
||
95 | ; Note: usb_device_disconnected should not see the new pipe until |
||
96 | ; initialization is complete, so that lock will be held during next steps |
||
97 | ; (disconnect processing should either not see it at all, or see fully |
||
98 | ; initialized pipe). |
||
99 | cmp [endpoint], eax |
||
100 | jz .zero_endpoint |
||
101 | mov ecx, [ebx+usb_pipe.DeviceData] |
||
102 | mov [edi+usb_pipe.DeviceData], ecx |
||
103 | call mutex_lock |
||
104 | test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
||
105 | jz .common |
||
106 | .fail: |
||
107 | ; If disconnect processing has completed, unlock the mutex, free memory |
||
108 | ; allocated in step 2 and return zero. |
||
109 | call mutex_unlock |
||
110 | mov edx, [esi+usb_controller.HardwareFunc] |
||
111 | stdcall [edx+usb_hardware_func.FreeTD], [edi+usb_pipe.LastTD] |
||
112 | .free_and_return0: |
||
113 | mov edx, [esi+usb_controller.HardwareFunc] |
||
114 | stdcall [edx+usb_hardware_func.FreePipe], edi |
||
115 | .return0: |
||
116 | xor eax, eax |
||
117 | jmp .nothing |
||
118 | .zero_endpoint: |
||
119 | mov [edi+usb_pipe.NextSibling], edi |
||
120 | mov [edi+usb_pipe.PrevSibling], edi |
||
121 | .common: |
||
122 | ; 6. Initialize hardware part of pipe structure. |
||
123 | ; 6a. Acquire the corresponding mutex. |
||
124 | lea ecx, [esi+usb_controller.ControlLock] |
||
125 | cmp [type], BULK_PIPE |
||
126 | jb @f ; control pipe |
||
127 | lea ecx, [esi+usb_controller.BulkLock] |
||
128 | jz @f ; bulk pipe |
||
129 | lea ecx, [esi+usb_controller.PeriodicLock] |
||
130 | @@: |
||
131 | call mutex_lock |
||
132 | ; 6b. Let the controller-specific code do its job. |
||
133 | push ecx |
||
134 | mov edx, [esi+usb_controller.HardwareFunc] |
||
135 | mov eax, [edi+usb_pipe.LastTD] |
||
136 | mov ecx, [config_pipe] |
||
137 | call [edx+usb_hardware_func.InitPipe] |
||
138 | pop ecx |
||
139 | ; 6c. Release the mutex. |
||
140 | push eax |
||
141 | call mutex_unlock |
||
142 | pop eax |
||
143 | ; 6d. If controller-specific code indicates failure, |
||
144 | ; release the lock taken in step 5, free memory allocated in step 2 |
||
145 | ; and return zero. |
||
146 | test eax, eax |
||
147 | jz .fail |
||
148 | ; 7. The pipe is initialized. If this is not the first pipe for the device, |
||
149 | ; insert it to the tail of pipe list for the device, |
||
150 | ; increment number of pipes, |
||
151 | ; release the lock taken at step 5. |
||
152 | mov ecx, [edi+usb_pipe.DeviceData] |
||
153 | test ecx, ecx |
||
154 | jz @f |
||
155 | mov eax, [ebx+usb_pipe.PrevSibling] |
||
156 | mov [edi+usb_pipe.NextSibling], ebx |
||
157 | mov [edi+usb_pipe.PrevSibling], eax |
||
158 | mov [ebx+usb_pipe.PrevSibling], edi |
||
159 | mov [eax+usb_pipe.NextSibling], edi |
||
160 | inc [ecx+usb_device_data.NumPipes] |
||
161 | call mutex_unlock |
||
162 | @@: |
||
163 | ; 8. Return pointer to usb_pipe. |
||
164 | mov eax, edi |
||
165 | .nothing: |
||
166 | ret |
||
167 | endp |
||
168 | |||
169 | ; This procedure is called several times during initial device configuration, |
||
170 | ; when usb_device_data structure is reallocated. |
||
171 | ; It (re)initializes all pointers in usb_device_data. |
||
172 | ; ebx -> usb_pipe |
||
173 | proc usb_reinit_pipe_list |
||
174 | push eax |
||
175 | ; 1. (Re)initialize the lock guarding pipe list. |
||
176 | mov ecx, [ebx+usb_pipe.DeviceData] |
||
177 | call mutex_init |
||
178 | ; 2. Initialize list of opened pipes: two entries, the head and ebx. |
||
179 | add ecx, usb_device_data.OpenedPipeList - usb_pipe.NextSibling |
||
180 | mov [ecx+usb_pipe.NextSibling], ebx |
||
181 | mov [ecx+usb_pipe.PrevSibling], ebx |
||
182 | mov [ebx+usb_pipe.NextSibling], ecx |
||
183 | mov [ebx+usb_pipe.PrevSibling], ecx |
||
184 | ; 3. Initialize list of closed pipes: empty list, only the head is present. |
||
185 | add ecx, usb_device_data.ClosedPipeList - usb_device_data.OpenedPipeList |
||
186 | mov [ecx+usb_pipe.NextSibling], ecx |
||
187 | mov [ecx+usb_pipe.PrevSibling], ecx |
||
188 | pop eax |
||
189 | ret |
||
190 | endp |
||
191 | |||
192 | ; Part of API for drivers, see documentation for USBClosePipe. |
||
193 | proc usb_close_pipe |
||
194 | push ebx esi ; save used registers to be stdcall |
||
195 | virtual at esp |
||
196 | rd 2 ; saved registers |
||
197 | dd ? ; return address |
||
198 | .pipe dd ? |
||
199 | end virtual |
||
200 | ; 1. Lock the pipe list for the device. |
||
201 | mov ebx, [.pipe] |
||
202 | mov esi, [ebx+usb_pipe.Controller] |
||
203 | mov ecx, [ebx+usb_pipe.DeviceData] |
||
204 | call mutex_lock |
||
205 | ; 2. Set the flag "the driver has abandoned this pipe, free it at any time". |
||
206 | lea ecx, [ebx+usb_pipe.Lock] |
||
207 | call mutex_lock |
||
208 | or [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE |
||
209 | call mutex_unlock |
||
210 | ; 3. Call the worker function. |
||
211 | call usb_close_pipe_nolock |
||
212 | ; 4. Unlock the pipe list for the device. |
||
213 | mov ecx, [ebx+usb_pipe.DeviceData] |
||
214 | call mutex_unlock |
||
215 | ; 5. Wakeup the USB thread so that it can proceed with releasing that pipe. |
||
216 | push edi |
||
217 | call usb_wakeup |
||
218 | pop edi |
||
219 | ; 6. Return. |
||
220 | pop esi ebx ; restore used registers to be stdcall |
||
221 | retn 4 |
||
222 | endp |
||
223 | |||
224 | ; Worker function for pipe closing. Called by usb_close_pipe API and |
||
225 | ; from disconnect processing. |
||
226 | ; The lock guarding pipe list for the device should be held by the caller. |
||
227 | ; ebx -> usb_pipe, esi -> usb_controller |
||
228 | proc usb_close_pipe_nolock |
||
229 | ; 1. Set the flag "pipe is closed, ignore new transfers". |
||
230 | ; If it was already set, do nothing. |
||
231 | lea ecx, [ebx+usb_pipe.Lock] |
||
232 | call mutex_lock |
||
233 | bts dword [ebx+usb_pipe.Flags], USB_FLAG_CLOSED_BIT |
||
234 | jc .closed |
||
235 | call mutex_unlock |
||
236 | ; 2. Remove the pipe from the list of opened pipes. |
||
237 | mov eax, [ebx+usb_pipe.NextSibling] |
||
238 | mov edx, [ebx+usb_pipe.PrevSibling] |
||
239 | mov [eax+usb_pipe.PrevSibling], edx |
||
240 | mov [edx+usb_pipe.NextSibling], eax |
||
241 | ; 3. Unlink the pipe from hardware structures. |
||
242 | ; 3a. Acquire the corresponding lock. |
||
243 | lea edx, [esi+usb_controller.WaitPipeListAsync] |
||
244 | lea ecx, [esi+usb_controller.ControlLock] |
||
245 | cmp [ebx+usb_pipe.Type], BULK_PIPE |
||
246 | jb @f ; control pipe |
||
247 | lea ecx, [esi+usb_controller.BulkLock] |
||
248 | jz @f ; bulk pipe |
||
249 | add edx, usb_controller.WaitPipeListPeriodic - usb_controller.WaitPipeListAsync |
||
250 | lea ecx, [esi+usb_controller.PeriodicLock] |
||
251 | @@: |
||
252 | push edx |
||
253 | call mutex_lock |
||
254 | push ecx |
||
255 | ; 3b. Let the controller-specific code do its job. |
||
4547 | clevermous | 256 | test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED |
257 | jnz @f |
||
3520 | clevermous | 258 | mov eax, [esi+usb_controller.HardwareFunc] |
4547 | clevermous | 259 | call [eax+usb_hardware_func.DisablePipe] |
260 | @@: |
||
261 | mov eax, [esi+usb_controller.HardwareFunc] |
||
3520 | clevermous | 262 | call [eax+usb_hardware_func.UnlinkPipe] |
4547 | clevermous | 263 | mov edx, [ebx+usb_pipe.NextVirt] |
264 | mov eax, [ebx+usb_pipe.PrevVirt] |
||
265 | mov [edx+usb_pipe.PrevVirt], eax |
||
266 | mov [eax+usb_pipe.NextVirt], edx |
||
3520 | clevermous | 267 | ; 3c. Release the corresponding lock. |
268 | pop ecx |
||
269 | call mutex_unlock |
||
270 | ; 4. Put the pipe into wait queue. |
||
271 | pop edx |
||
272 | cmp [ebx+usb_pipe.NextWait], -1 |
||
273 | jz .insert_new |
||
274 | or [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT |
||
275 | jmp .inserted |
||
276 | .insert_new: |
||
277 | mov eax, [edx] |
||
278 | mov [ebx+usb_pipe.NextWait], eax |
||
279 | mov [edx], ebx |
||
280 | .inserted: |
||
281 | ; 5. Return. |
||
282 | ret |
||
283 | .closed: |
||
284 | call mutex_unlock |
||
285 | xor eax, eax |
||
286 | ret |
||
287 | endp |
||
288 | |||
4547 | clevermous | 289 | ; This procedure is called when all transfers are aborted |
290 | ; either due to call to usb_abort_pipe or due to pipe closing. |
||
291 | ; It notifies all callbacks and frees all transfer descriptors. |
||
292 | ; ebx -> usb_pipe, esi -> usb_controller, edi -> usb_hardware_func |
||
293 | ; three stack parameters: status code for callback functions |
||
294 | ; and descriptors where to start and stop. |
||
295 | proc usb_pipe_aborted |
||
296 | virtual at esp |
||
297 | dd ? ; return address |
||
298 | .status dd ? ; USB_STATUS_CLOSED or USB_STATUS_CANCELLED |
||
299 | .first_td dd ? |
||
300 | .last_td dd ? |
||
301 | end virtual |
||
302 | ; Loop over all transfers, calling the driver with the given status |
||
303 | ; and freeing all descriptors except the last one. |
||
304 | .loop: |
||
305 | mov edx, [.first_td] |
||
306 | cmp edx, [.last_td] |
||
307 | jz .done |
||
308 | mov ecx, [edx+usb_gtd.Callback] |
||
309 | test ecx, ecx |
||
310 | jz .no_callback |
||
311 | stdcall_verify ecx, ebx, [.status+12+STDCALL_VERIFY_EXTRA], \ |
||
312 | [edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData] |
||
313 | mov edx, [.first_td] |
||
314 | .no_callback: |
||
315 | mov eax, [edx+usb_gtd.NextVirt] |
||
316 | mov [.first_td], eax |
||
317 | stdcall [edi+usb_hardware_func.FreeTD], edx |
||
318 | jmp .loop |
||
319 | .done: |
||
320 | ret 12 |
||
321 | endp |
||
322 | |||
3520 | clevermous | 323 | ; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the |
324 | ; corresponding wait list. It means that the hardware has fully forgot about it. |
||
325 | ; ebx -> usb_pipe, esi -> usb_controller |
||
326 | proc usb_pipe_closed |
||
327 | push edi |
||
328 | mov edi, [esi+usb_controller.HardwareFunc] |
||
4547 | clevermous | 329 | ; 1. Notify all registered callbacks with status USB_STATUS_CLOSED, if any, |
330 | ; and free all transfer descriptors, including the last one. |
||
331 | lea ecx, [ebx+usb_pipe.Lock] |
||
332 | call mutex_lock |
||
3520 | clevermous | 333 | mov edx, [ebx+usb_pipe.LastTD] |
334 | test edx, edx |
||
335 | jz .no_transfer |
||
4547 | clevermous | 336 | mov eax, [edx+usb_gtd.NextVirt] |
3520 | clevermous | 337 | push edx |
4547 | clevermous | 338 | push eax |
339 | call mutex_unlock |
||
340 | push USB_STATUS_CLOSED |
||
341 | call usb_pipe_aborted |
||
342 | ; It is safe to free LastTD here: |
||
343 | ; usb_*_transfer_async do not enqueue new transfers if USB_FLAG_CLOSED is set. |
||
344 | stdcall [edi+usb_hardware_func.FreeTD], [ebx+usb_pipe.LastTD] |
||
345 | jmp @f |
||
3520 | clevermous | 346 | .no_transfer: |
4547 | clevermous | 347 | call mutex_unlock |
348 | @@: |
||
3520 | clevermous | 349 | ; 2. Decrement number of pipes for the device. |
350 | ; If this pipe is the last pipe, go to 5. |
||
351 | mov ecx, [ebx+usb_pipe.DeviceData] |
||
352 | call mutex_lock |
||
353 | dec [ecx+usb_device_data.NumPipes] |
||
354 | jz .last_pipe |
||
355 | call mutex_unlock |
||
356 | ; 3. If the flag "the driver has abandoned this pipe" is set, |
||
357 | ; free memory and return. |
||
358 | test [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE |
||
359 | jz .nofree |
||
360 | stdcall [edi+usb_hardware_func.FreePipe], ebx |
||
361 | pop edi |
||
362 | ret |
||
363 | ; 4. Otherwise, add it to the list of closed pipes and return. |
||
364 | .nofree: |
||
365 | add ecx, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
||
366 | mov edx, [ecx+usb_pipe.PrevSibling] |
||
367 | mov [ebx+usb_pipe.NextSibling], ecx |
||
368 | mov [ebx+usb_pipe.PrevSibling], edx |
||
369 | mov [ecx+usb_pipe.PrevSibling], ebx |
||
370 | mov [edx+usb_pipe.NextSibling], ebx |
||
371 | pop edi |
||
372 | ret |
||
373 | .last_pipe: |
||
374 | ; That was the last pipe for the device. |
||
375 | ; 5. Notify device driver(s) about disconnect. |
||
376 | call mutex_unlock |
||
3826 | clevermous | 377 | mov eax, [ecx+usb_device_data.NumInterfaces] |
3520 | clevermous | 378 | test eax, eax |
379 | jz .notify_done |
||
380 | add ecx, [ecx+usb_device_data.Interfaces] |
||
381 | .notify_loop: |
||
382 | mov edx, [ecx+usb_interface_data.DriverFunc] |
||
383 | test edx, edx |
||
384 | jz @f |
||
385 | mov edx, [edx+USBSRV.usb_func] |
||
386 | cmp [edx+USBFUNC.strucsize], USBFUNC.device_disconnect + 4 |
||
387 | jb @f |
||
388 | mov edx, [edx+USBFUNC.device_disconnect] |
||
389 | test edx, edx |
||
390 | jz @f |
||
391 | push eax ecx |
||
392 | stdcall_verify edx, [ecx+usb_interface_data.DriverData] |
||
393 | pop ecx eax |
||
394 | @@: |
||
395 | add ecx, sizeof.usb_interface_data |
||
396 | dec eax |
||
397 | jnz .notify_loop |
||
398 | .notify_done: |
||
4547 | clevermous | 399 | ; 6. Kill the timer, if active. |
400 | ; (Usually not; possible if device is disconnected |
||
401 | ; while processing SET_ADDRESS request). |
||
402 | mov eax, [ebx+usb_pipe.DeviceData] |
||
403 | cmp [eax+usb_device_data.Timer], 0 |
||
404 | jz @f |
||
405 | stdcall cancel_timer_hs, [eax+usb_device_data.Timer] |
||
406 | mov [eax+usb_device_data.Timer], 0 |
||
407 | @@: |
||
408 | ; 7. Bus address, if assigned, can now be reused. |
||
3520 | clevermous | 409 | call [edi+usb_hardware_func.GetDeviceAddress] |
410 | test eax, eax |
||
411 | jz @f |
||
412 | bts [esi+usb_controller.ExistingAddresses], eax |
||
413 | @@: |
||
414 | dbgstr 'USB device disconnected' |
||
4547 | clevermous | 415 | ; 8. All drivers have returned from disconnect callback, |
3520 | clevermous | 416 | ; so all drivers should not use any device-related pipes. |
417 | ; Free the remaining pipes. |
||
418 | mov eax, [ebx+usb_pipe.DeviceData] |
||
419 | add eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
||
420 | push eax |
||
421 | mov eax, [eax+usb_pipe.NextSibling] |
||
422 | .free_loop: |
||
423 | cmp eax, [esp] |
||
424 | jz .free_done |
||
425 | push [eax+usb_pipe.NextSibling] |
||
426 | stdcall [edi+usb_hardware_func.FreePipe], eax |
||
427 | pop eax |
||
428 | jmp .free_loop |
||
429 | .free_done: |
||
430 | stdcall [edi+usb_hardware_func.FreePipe], ebx |
||
431 | pop eax |
||
4547 | clevermous | 432 | ; 9. Free the usb_device_data structure. |
3520 | clevermous | 433 | sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
434 | call free |
||
4547 | clevermous | 435 | ; 10. Return. |
3520 | clevermous | 436 | .nothing: |
437 | pop edi |
||
438 | ret |
||
439 | endp |
||
440 | |||
4547 | clevermous | 441 | ; This procedure is called when a pipe with USB_FLAG_DISABLED is removed from the |
442 | ; corresponding wait list. It means that the hardware has fully forgot about it. |
||
443 | ; ebx -> usb_pipe, esi -> usb_controller |
||
444 | proc usb_pipe_disabled |
||
445 | push edi |
||
446 | mov edi, [esi+usb_controller.HardwareFunc] |
||
447 | ; 1. Acquire pipe lock. |
||
448 | lea ecx, [ebx+usb_pipe.Lock] |
||
449 | call mutex_lock |
||
450 | ; 2. Clear USB_FLAG_DISABLED in pipe state. |
||
451 | and [ebx+usb_pipe.Flags], not USB_FLAG_DISABLED |
||
452 | ; 3. Sanity check: ignore uninitialized pipes. |
||
453 | cmp [ebx+usb_pipe.LastTD], 0 |
||
454 | jz .no_transfer |
||
455 | ; 4. Acquire the first and last to-be-cancelled transfer descriptor, |
||
456 | ; save them in stack for the step 6, |
||
457 | ; ask the controller driver to enable the pipe for hardware, |
||
458 | ; removing transfers between first and last to-be-cancelled descriptors. |
||
459 | lea ecx, [esi+usb_controller.ControlLock] |
||
460 | cmp [ebx+usb_pipe.Type], BULK_PIPE |
||
461 | jb @f ; control pipe |
||
462 | lea ecx, [esi+usb_controller.BulkLock] |
||
463 | jz @f ; bulk pipe |
||
464 | lea ecx, [esi+usb_controller.PeriodicLock] |
||
465 | @@: |
||
466 | call mutex_lock |
||
467 | mov eax, [ebx+usb_pipe.BaseList] |
||
468 | mov edx, [eax+usb_pipe.NextVirt] |
||
469 | mov [ebx+usb_pipe.NextVirt], edx |
||
470 | mov [ebx+usb_pipe.PrevVirt], eax |
||
471 | mov [edx+usb_pipe.PrevVirt], ebx |
||
472 | mov [eax+usb_pipe.NextVirt], ebx |
||
473 | mov eax, [ebx+usb_pipe.LastTD] |
||
474 | mov edx, [eax+usb_gtd.NextVirt] |
||
475 | mov [eax+usb_gtd.NextVirt], eax |
||
476 | mov [eax+usb_gtd.PrevVirt], eax |
||
477 | push eax |
||
478 | push edx |
||
479 | push ecx |
||
480 | call [edi+usb_hardware_func.EnablePipe] |
||
481 | pop ecx |
||
482 | call mutex_unlock |
||
483 | ; 5. Release pipe lock acquired at step 1. |
||
484 | ; Callbacks called at step 6 can insert new transfers, |
||
485 | ; so we cannot call usb_pipe_aborted while holding pipe lock. |
||
486 | lea ecx, [ebx+usb_pipe.Lock] |
||
487 | call mutex_unlock |
||
488 | ; 6. Notify all registered callbacks with status USB_STATUS_CANCELLED, if any. |
||
489 | ; Two arguments describing transfers range were pushed at step 4. |
||
490 | push USB_STATUS_CANCELLED |
||
491 | call usb_pipe_aborted |
||
492 | pop edi |
||
493 | ret |
||
494 | .no_transfer: |
||
495 | call mutex_unlock |
||
496 | pop edi |
||
497 | ret |
||
498 | endp |
||
499 | |||
3520 | clevermous | 500 | ; Part of API for drivers, see documentation for USBNormalTransferAsync. |
501 | proc usb_normal_transfer_async stdcall uses ebx edi,\ |
||
502 | pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword |
||
503 | ; 1. Sanity check: callback must be nonzero. |
||
504 | ; (It is important for other parts of code.) |
||
505 | xor eax, eax |
||
506 | cmp [callback], eax |
||
507 | jz .nothing |
||
508 | ; 2. Lock the transfer queue. |
||
509 | mov ebx, [pipe] |
||
510 | lea ecx, [ebx+usb_pipe.Lock] |
||
511 | call mutex_lock |
||
512 | ; 3. If the pipe has already been closed (presumably due to device disconnect), |
||
513 | ; release the lock taken in step 2 and return zero. |
||
514 | xor eax, eax |
||
515 | test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
||
516 | jnz .unlock |
||
517 | ; 4. Allocate and initialize TDs for the transfer. |
||
518 | mov edx, [ebx+usb_pipe.Controller] |
||
519 | mov edi, [edx+usb_controller.HardwareFunc] |
||
520 | stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], [ebx+usb_pipe.LastTD], 0 |
||
521 | ; If failed, release the lock taken in step 2 and return zero. |
||
522 | test eax, eax |
||
523 | jz .unlock |
||
524 | ; 5. Store callback and its parameters in the last descriptor for this transfer. |
||
525 | mov ecx, [eax+usb_gtd.PrevVirt] |
||
526 | mov edx, [callback] |
||
527 | mov [ecx+usb_gtd.Callback], edx |
||
528 | mov edx, [calldata] |
||
529 | mov [ecx+usb_gtd.UserData], edx |
||
530 | mov edx, [buffer] |
||
531 | mov [ecx+usb_gtd.Buffer], edx |
||
532 | ; 6. Advance LastTD pointer and activate transfer. |
||
533 | push [ebx+usb_pipe.LastTD] |
||
534 | mov [ebx+usb_pipe.LastTD], eax |
||
535 | call [edi+usb_hardware_func.InsertTransfer] |
||
536 | pop eax |
||
537 | ; 7. Release the lock taken in step 2 and |
||
538 | ; return pointer to the first descriptor for the new transfer. |
||
539 | .unlock: |
||
540 | push eax |
||
541 | lea ecx, [ebx+usb_pipe.Lock] |
||
542 | call mutex_unlock |
||
543 | pop eax |
||
544 | .nothing: |
||
545 | ret |
||
546 | endp |
||
547 | |||
548 | ; Part of API for drivers, see documentation for USBControlTransferAsync. |
||
549 | proc usb_control_async stdcall uses ebx edi,\ |
||
550 | pipe:dword, config:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword |
||
551 | locals |
||
552 | last_td dd ? |
||
553 | endl |
||
554 | ; 1. Sanity check: callback must be nonzero. |
||
555 | ; (It is important for other parts of code.) |
||
556 | cmp [callback], 0 |
||
557 | jz .return0 |
||
558 | ; 2. Lock the transfer queue. |
||
559 | mov ebx, [pipe] |
||
560 | lea ecx, [ebx+usb_pipe.Lock] |
||
561 | call mutex_lock |
||
562 | ; 3. If the pipe has already been closed (presumably due to device disconnect), |
||
563 | ; release the lock taken in step 2 and return zero. |
||
564 | test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
||
565 | jnz .unlock_return0 |
||
566 | ; A control transfer contains two or three stages: |
||
567 | ; Setup stage, optional Data stage, Status stage. |
||
568 | ; 4. Allocate and initialize TDs for the Setup stage. |
||
569 | ; Payload is 8 bytes from [config]. |
||
570 | mov edx, [ebx+usb_pipe.Controller] |
||
571 | mov edi, [edx+usb_controller.HardwareFunc] |
||
572 | stdcall [edi+usb_hardware_func.AllocTransfer], [config], 8, 0, [ebx+usb_pipe.LastTD], (2 shl 2) + 0 |
||
573 | ; short transfer is an error, direction is DATA0, token is SETUP |
||
574 | mov [last_td], eax |
||
575 | test eax, eax |
||
576 | jz .fail |
||
577 | ; 5. Allocate and initialize TDs for the Data stage, if [size] is nonzero. |
||
578 | ; Payload is [size] bytes from [buffer]. |
||
579 | mov edx, [config] |
||
580 | mov ecx, (3 shl 2) + 1 ; DATA1, token is OUT |
||
581 | cmp byte [edx], 0 |
||
582 | jns @f |
||
583 | cmp [size], 0 |
||
584 | jz @f |
||
585 | inc ecx ; token is IN |
||
586 | @@: |
||
587 | cmp [size], 0 |
||
588 | jz .nodata |
||
589 | push ecx |
||
590 | stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], eax, ecx |
||
591 | pop ecx |
||
592 | test eax, eax |
||
593 | jz .fail |
||
594 | mov [last_td], eax |
||
595 | .nodata: |
||
596 | ; 6. Allocate and initialize TDs for the Status stage. |
||
597 | ; No payload. |
||
598 | xor ecx, 3 ; IN becomes OUT, OUT becomes IN |
||
599 | stdcall [edi+usb_hardware_func.AllocTransfer], 0, 0, 0, eax, ecx |
||
600 | test eax, eax |
||
601 | jz .fail |
||
602 | ; 7. Store callback and its parameters in the last descriptor for this transfer. |
||
603 | mov ecx, [eax+usb_gtd.PrevVirt] |
||
604 | mov edx, [callback] |
||
605 | mov [ecx+usb_gtd.Callback], edx |
||
606 | mov edx, [calldata] |
||
607 | mov [ecx+usb_gtd.UserData], edx |
||
608 | mov edx, [buffer] |
||
609 | mov [ecx+usb_gtd.Buffer], edx |
||
610 | ; 8. Advance LastTD pointer and activate transfer. |
||
611 | push [ebx+usb_pipe.LastTD] |
||
612 | mov [ebx+usb_pipe.LastTD], eax |
||
613 | call [edi+usb_hardware_func.InsertTransfer] |
||
614 | ; 9. Release the lock taken in step 2 and |
||
615 | ; return pointer to the first descriptor for the new transfer. |
||
616 | lea ecx, [ebx+usb_pipe.Lock] |
||
617 | call mutex_unlock |
||
618 | pop eax |
||
619 | ret |
||
620 | .fail: |
||
621 | mov eax, [last_td] |
||
622 | test eax, eax |
||
623 | jz .unlock_return0 |
||
624 | stdcall usb_undo_tds, [ebx+usb_pipe.LastTD] |
||
625 | .unlock_return0: |
||
626 | lea ecx, [ebx+usb_pipe.Lock] |
||
627 | call mutex_unlock |
||
628 | .return0: |
||
629 | xor eax, eax |
||
630 | ret |
||
631 | endp |
||
632 | |||
4547 | clevermous | 633 | ; Part of API for drivers, see documentation for USBAbortPipe. |
634 | proc usb_abort_pipe |
||
635 | push ebx esi ; save used registers to be stdcall |
||
636 | virtual at esp |
||
637 | rd 2 ; saved registers |
||
638 | dd ? ; return address |
||
639 | .pipe dd ? |
||
640 | end virtual |
||
641 | mov ebx, [.pipe] |
||
642 | ; 1. Acquire pipe lock. |
||
643 | lea ecx, [ebx+usb_pipe.Lock] |
||
644 | call mutex_lock |
||
645 | ; 2. If the pipe is already closed or abort is in progress, |
||
646 | ; just release pipe lock and return. |
||
647 | test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + USB_FLAG_DISABLED |
||
648 | jnz .nothing |
||
649 | ; 3. Mark the pipe as aborting. |
||
650 | or [ebx+usb_pipe.Flags], USB_FLAG_DISABLED |
||
651 | ; 4. We cannot do anything except adding new transfers concurrently with hardware. |
||
652 | ; Ask the controller driver to (temporarily) remove the pipe from hardware queue. |
||
653 | mov esi, [ebx+usb_pipe.Controller] |
||
654 | ; 4a. Acquire queue lock. |
||
655 | lea ecx, [esi+usb_controller.ControlLock] |
||
656 | cmp [ebx+usb_pipe.Type], BULK_PIPE |
||
657 | jb @f ; control pipe |
||
658 | lea ecx, [esi+usb_controller.BulkLock] |
||
659 | jz @f ; bulk pipe |
||
660 | lea ecx, [esi+usb_controller.PeriodicLock] |
||
661 | @@: |
||
662 | call mutex_lock |
||
663 | push ecx |
||
664 | ; 4b. Call the driver. |
||
665 | mov eax, [esi+usb_controller.HardwareFunc] |
||
666 | call [eax+usb_hardware_func.DisablePipe] |
||
667 | ; 4c. Remove the pipe from software list. |
||
668 | mov eax, [ebx+usb_pipe.NextVirt] |
||
669 | mov edx, [ebx+usb_pipe.PrevVirt] |
||
670 | mov [eax+usb_pipe.PrevVirt], edx |
||
671 | mov [edx+usb_pipe.NextVirt], eax |
||
672 | ; 4c. Register the pipe in corresponding wait list. |
||
673 | test [ebx+usb_pipe.Type], 1 |
||
674 | jz .control_bulk |
||
675 | call usb_subscribe_periodic |
||
676 | jmp @f |
||
677 | .control_bulk: |
||
678 | call usb_subscribe_control |
||
679 | @@: |
||
680 | ; 4d. Release queue lock. |
||
681 | pop ecx |
||
682 | call mutex_unlock |
||
683 | ; 4e. Notify the USB thread about new work. |
||
684 | push ebx esi edi |
||
685 | call usb_wakeup |
||
686 | pop edi esi ebx |
||
687 | ; That's all for now. To be continued in usb_pipe_disabled. |
||
688 | ; 5. Release pipe lock acquired at step 1 and return. |
||
689 | .nothing: |
||
690 | lea ecx, [ebx+usb_pipe.Lock] |
||
691 | call mutex_unlock |
||
692 | pop esi ebx |
||
693 | ret 4 |
||
694 | endp |
||
695 | |||
3745 | clevermous | 696 | ; Part of API for drivers, see documentation for USBGetParam. |
697 | proc usb_get_param |
||
698 | virtual at esp |
||
699 | dd ? ; return address |
||
700 | .pipe dd ? |
||
701 | .param dd ? |
||
702 | end virtual |
||
703 | mov edx, [.param] |
||
704 | mov ecx, [.pipe] |
||
705 | mov eax, [ecx+usb_pipe.DeviceData] |
||
706 | test edx, edx |
||
707 | jz .get_device_descriptor |
||
708 | dec edx |
||
709 | jz .get_config_descriptor |
||
710 | dec edx |
||
711 | jz .get_speed |
||
712 | or eax, -1 |
||
713 | ret 8 |
||
714 | .get_device_descriptor: |
||
715 | add eax, usb_device_data.DeviceDescriptor |
||
716 | ret 8 |
||
717 | .get_config_descriptor: |
||
718 | movzx ecx, [eax+usb_device_data.DeviceDescrSize] |
||
719 | lea eax, [eax+ecx+usb_device_data.DeviceDescriptor] |
||
720 | ret 8 |
||
721 | .get_speed: |
||
722 | movzx eax, [eax+usb_device_data.Speed] |
||
723 | ret 8 |
||
724 | endp |
||
725 | |||
3520 | clevermous | 726 | ; Initialize software part of usb_gtd. Called from controller-specific code |
727 | ; somewhere in AllocTransfer with eax -> next (inactive) usb_gtd, |
||
728 | ; ebx -> usb_pipe, ebp frame from call to AllocTransfer with [.td] -> |
||
729 | ; current (initializing) usb_gtd. |
||
730 | ; Returns ecx = [.td]. |
||
731 | proc usb_init_transfer |
||
732 | virtual at ebp-4 |
||
733 | .Size dd ? |
||
734 | rd 2 |
||
735 | .Buffer dd ? |
||
736 | dd ? |
||
737 | .Flags dd ? |
||
738 | .td dd ? |
||
739 | end virtual |
||
740 | mov [eax+usb_gtd.Pipe], ebx |
||
741 | mov ecx, [.td] |
||
742 | mov [eax+usb_gtd.PrevVirt], ecx |
||
743 | mov edx, [ecx+usb_gtd.NextVirt] |
||
744 | mov [ecx+usb_gtd.NextVirt], eax |
||
745 | mov [eax+usb_gtd.NextVirt], edx |
||
746 | mov [edx+usb_gtd.PrevVirt], eax |
||
747 | mov edx, [.Size] |
||
748 | mov [ecx+usb_gtd.Length], edx |
||
749 | xor edx, edx |
||
750 | mov [ecx+usb_gtd.Callback], edx |
||
751 | mov [ecx+usb_gtd.UserData], edx |
||
752 | ret |
||
753 | endp |
||
754 | |||
755 | ; Free all TDs for the current transfer if something has failed |
||
756 | ; during initialization (e.g. no memory for the next TD). |
||
757 | ; Stdcall with one stack argument = first TD for the transfer |
||
758 | ; and eax = last initialized TD for the transfer. |
||
759 | proc usb_undo_tds |
||
760 | push [eax+usb_gtd.NextVirt] |
||
761 | @@: |
||
762 | cmp eax, [esp+8] |
||
763 | jz @f |
||
764 | push [eax+usb_gtd.PrevVirt] |
||
765 | stdcall [edi+usb_hardware_func.FreeTD], eax |
||
766 | pop eax |
||
767 | jmp @b |
||
768 | @@: |
||
769 | pop ecx |
||
770 | mov [eax+usb_gtd.NextVirt], ecx |
||
771 | mov [ecx+usb_gtd.PrevVirt], eax |
||
772 | ret 4 |
||
773 | endp |
||
774 | |||
775 | ; Helper procedure for handling short packets in controller-specific code. |
||
776 | ; Returns with CF cleared if this is the final packet in some stage: |
||
777 | ; for control transfers that means one of Data and Status stages, |
||
778 | ; for other transfers - the final packet in the only stage. |
||
779 | proc usb_is_final_packet |
||
780 | cmp [ebx+usb_gtd.Callback], 0 |
||
781 | jnz .nothing |
||
782 | mov eax, [ebx+usb_gtd.NextVirt] |
||
783 | cmp [eax+usb_gtd.Callback], 0 |
||
784 | jz .stc |
||
785 | mov eax, [ebx+usb_gtd.Pipe] |
||
786 | cmp [eax+usb_pipe.Type], CONTROL_PIPE |
||
787 | jz .nothing |
||
788 | .stc: |
||
789 | stc |
||
790 | .nothing: |
||
791 | ret |
||
792 | endp |
||
793 | |||
794 | ; Helper procedure for controller-specific code: |
||
795 | ; removes one TD from the transfer queue, ebx -> usb_gtd to remove. |
||
796 | proc usb_unlink_td |
||
797 | mov ecx, [ebx+usb_gtd.Pipe] |
||
798 | add ecx, usb_pipe.Lock |
||
799 | call mutex_lock |
||
800 | mov eax, [ebx+usb_gtd.PrevVirt] |
||
801 | mov edx, [ebx+usb_gtd.NextVirt] |
||
802 | mov [edx+usb_gtd.PrevVirt], eax |
||
803 | mov [eax+usb_gtd.NextVirt], edx |
||
804 | call mutex_unlock |
||
805 | ret |
||
806 | endp |
||
807 | |||
4418 | clevermous | 808 | ; One part of transfer is completed, run the associated callback |
809 | ; or update total length in the next part of transfer. |
||
810 | ; in: ebx -> usb_gtd, ecx = status, edx = length |
||
811 | proc usb_process_gtd |
||
812 | ; 1. Test whether it is the last descriptor in the transfer |
||
813 | ; <=> it has an associated callback. |
||
814 | mov eax, [ebx+usb_gtd.Callback] |
||
815 | test eax, eax |
||
816 | jz .nocallback |
||
817 | ; 2. It has an associated callback; call it with corresponding parameters. |
||
818 | stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \ |
||
819 | [ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] |
||
820 | ret |
||
821 | .nocallback: |
||
822 | ; 3. It is an intermediate descriptor. Add its length to the length |
||
823 | ; in the following descriptor. |
||
824 | mov eax, [ebx+usb_gtd.NextVirt] |
||
825 | add [eax+usb_gtd.Length], edx |
||
826 | ret |
||
827 | endp |
||
828 | |||
3520 | clevermous | 829 | if USB_STDCALL_VERIFY |
830 | proc verify_regs |
||
831 | virtual at esp |
||
832 | dd ? ; return address |
||
833 | .edi dd ? |
||
834 | .esi dd ? |
||
835 | .ebp dd ? |
||
836 | .esp dd ? |
||
837 | .ebx dd ? |
||
838 | .edx dd ? |
||
839 | .ecx dd ? |
||
840 | .eax dd ? |
||
841 | end virtual |
||
842 | cmp ebx, [.ebx] |
||
843 | jz @f |
||
844 | dbgstr 'ERROR!!! ebx changed' |
||
845 | @@: |
||
846 | cmp esi, [.esi] |
||
847 | jz @f |
||
848 | dbgstr 'ERROR!!! esi changed' |
||
849 | @@: |
||
850 | cmp edi, [.edi] |
||
851 | jz @f |
||
852 | dbgstr 'ERROR!!! edi changed' |
||
853 | @@: |
||
854 | cmp ebp, [.ebp] |
||
855 | jz @f |
||
856 | dbgstr 'ERROR!!! ebp changed' |
||
857 | @@: |
||
858 | ret |
||
859 | endp |
||
860 | end if=> |