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