source: mainline/uspace/drv/bus/usb/xhci/isoch.c@ 708d8fcd

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 708d8fcd was 708d8fcd, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

xhci: rewritten isochronous transfers

There was a fundamental problem with relying on hardware to send
RING_OVERRUN/UNDERRUN events, which QEMU (and possibly others) do not
send. That resulted in not knowing if the transfer is still on schedule,
and having to ring the doorbell every time. That is not feasible,
because then the transfer can be more frequent than it should be.
Furthermore, it ignored the fact that isochronous TRBs are to be
scheduled not too late, but also not too soon (see 4.11.2.5 of the xHCI
spec).

Now, scheduling the TRBs to hardware is called feeding, and can be
delayed by setting a timer. Ring overruns/underruns are detected also at
the end of handling an event.

  • Property mode set to 100644
File size: 17.4 KB
Line 
1/*
2 * Copyright (c) 2017 HelUSB3 team
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup drvusbxhci
30 * @{
31 */
32/** @file
33 * @brief The host controller endpoint management.
34 */
35
36#include <str_error.h>
37
38#include "endpoint.h"
39#include "hw_struct/trb.h"
40#include "hw_struct/regs.h"
41#include "trb_ring.h"
42#include "hc.h"
43#include "bus.h"
44
45#include "isoch.h"
46
47void isoch_init(xhci_endpoint_t *ep, const usb_endpoint_descriptors_t *desc)
48{
49 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
50 xhci_isoch_t * const isoch = ep->isoch;
51
52 fibril_mutex_initialize(&isoch->guard);
53 fibril_condvar_initialize(&isoch->avail);
54
55 isoch->max_size = desc->companion.bytes_per_interval
56 ? desc->companion.bytes_per_interval
57 : ep->base.max_transfer_size;
58 /* Technically there could be superspeed plus too. */
59}
60
61static void isoch_reset(xhci_endpoint_t *ep)
62{
63 xhci_isoch_t * const isoch = ep->isoch;
64 assert(fibril_mutex_is_locked(&isoch->guard));
65
66 isoch->dequeue = isoch->enqueue = isoch->hw_enqueue = 0;
67
68 for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
69 isoch->transfers[i].state = ISOCH_EMPTY;
70 }
71
72 fibril_timer_clear_locked(isoch->feeding_timer);
73 isoch->last_mfindex = -1U;
74 usb_log_info("[isoch] Endpoint" XHCI_EP_FMT ": Data flow reset.", XHCI_EP_ARGS(*ep));
75}
76
77void isoch_fini(xhci_endpoint_t *ep)
78{
79 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
80 xhci_isoch_t * const isoch = ep->isoch;
81
82 if (isoch->feeding_timer) {
83 fibril_timer_clear(isoch->feeding_timer);
84 fibril_timer_destroy(isoch->feeding_timer);
85 }
86
87 for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i)
88 dma_buffer_free(&isoch->transfers[i].data);
89}
90
91/**
92 * Allocate isochronous buffers. Create the feeding timer.
93 */
94int isoch_alloc_transfers(xhci_endpoint_t *ep) {
95 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
96 xhci_isoch_t * const isoch = ep->isoch;
97
98 isoch->feeding_timer = fibril_timer_create(&isoch->guard);
99 if (!isoch->feeding_timer)
100 return ENOMEM;
101
102 for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
103 xhci_isoch_transfer_t *transfer = &isoch->transfers[i];
104 if (dma_buffer_alloc(&transfer->data, isoch->max_size)) {
105 isoch_fini(ep);
106 return ENOMEM;
107 }
108 }
109
110 fibril_mutex_lock(&isoch->guard);
111 isoch_reset(ep);
112 fibril_mutex_unlock(&isoch->guard);
113
114 return EOK;
115}
116
117static int schedule_isochronous_trb(xhci_endpoint_t *ep, xhci_isoch_transfer_t *it)
118{
119 xhci_trb_t trb;
120 xhci_trb_clean(&trb);
121
122 trb.parameter = it->data.phys;
123 TRB_CTRL_SET_XFER_LEN(trb, it->size);
124 TRB_CTRL_SET_TD_SIZE(trb, 0);
125 TRB_CTRL_SET_IOC(trb, 1);
126 TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_ISOCH);
127
128 // see 4.14.1 and 4.11.2.3 for the explanation, how to calculate those
129 size_t tdpc = it->size / 1024 + ((it->size % 1024) ? 1 : 0);
130 size_t tbc = tdpc / ep->max_burst;
131 if (!tdpc % ep->max_burst) --tbc;
132 size_t bsp = tdpc % ep->max_burst;
133 size_t tlbpc = (bsp ? bsp : ep->max_burst) - 1;
134
135 TRB_ISOCH_SET_TBC(trb, tbc);
136 TRB_ISOCH_SET_TLBPC(trb, tlbpc);
137 TRB_ISOCH_SET_FRAMEID(trb, (it->mfindex / 8) % 2048);
138
139 const int err = xhci_trb_ring_enqueue(&ep->ring, &trb, &it->interrupt_trb_phys);
140 return err;
141}
142
143static inline void calc_next_mfindex(xhci_endpoint_t *ep, xhci_isoch_transfer_t *it)
144{
145 xhci_isoch_t * const isoch = ep->isoch;
146 if (isoch->last_mfindex == -1U) {
147 const xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
148 const xhci_hc_t *hc = bus->hc;
149
150 /* Choose some number, give us a little time to prepare the
151 * buffers */
152 it->mfindex = XHCI_REG_RD(hc->rt_regs, XHCI_RT_MFINDEX) + 1
153 + XHCI_ISOCH_BUFFER_COUNT * ep->interval
154 + hc->ist;
155
156 // Align to ESIT start boundary
157 it->mfindex += ep->interval - 1;
158 it->mfindex &= ~(ep->interval - 1);
159 } else {
160 it->mfindex = (isoch->last_mfindex + ep->interval) % XHCI_MFINDEX_MAX;
161 }
162}
163
164/** 825 ms in uframes */
165#define END_FRAME_DELAY (895000 / 125)
166
167typedef enum {
168 WINDOW_TOO_SOON,
169 WINDOW_INSIDE,
170 WINDOW_TOO_LATE,
171} window_position_t;
172
173typedef struct {
174 window_position_t position;
175 uint32_t offset;
176} window_decision_t;
177
178/**
179 * Decide on the position of mfindex relatively to the window specified by
180 * Start Frame ID and End Frame ID. The resulting structure contains the
181 * decision, and in case of the mfindex being outside, also the number of
182 * uframes it's off.
183 */
184static inline void window_decide(window_decision_t *res, xhci_hc_t *hc, uint32_t mfindex)
185{
186 uint32_t current_mfindex = XHCI_REG_RD(hc->rt_regs, XHCI_RT_MFINDEX) + 1;
187
188 /*
189 * In your mind, rotate the clock so the window is at its beginning.
190 * The length of the window is always the same, and by rotating the
191 * mfindex too, we can decide by the value of it easily.
192 */
193 mfindex = (mfindex - current_mfindex - hc->ist + XHCI_MFINDEX_MAX) % XHCI_MFINDEX_MAX;
194 const uint32_t end = END_FRAME_DELAY - hc->ist;
195 const uint32_t threshold = (XHCI_MFINDEX_MAX + end) / 2;
196
197 if (mfindex <= end) {
198 res->position = WINDOW_INSIDE;
199 } else if (mfindex > threshold) {
200 res->position = WINDOW_TOO_LATE;
201 res->offset = XHCI_MFINDEX_MAX - mfindex;
202 } else {
203 res->position = WINDOW_TOO_SOON;
204 res->offset = mfindex - end;
205 }
206 /*
207 * TODO: The "size" of the clock is too low. We have to scale it a bit
208 * to ensure correct scheduling of transfers, that are
209 * XHCI_ISOCH_BUFFER_COUNT * interval away from now.
210 * Maximum interval is 8 seconds, which means we need a size of
211 * 16 seconds. The size of MFIINDEX is 2 seconds only.
212 *
213 * A plan is to create a thin abstraction at HC, which would return
214 * a time from 32-bit clock, having its high bits updated by the
215 * MFINDEX Wrap Event, and low bits from the MFINDEX register. Using
216 * this 32-bit clock, one can plan 6 days ahead.
217 */
218}
219
220static void isoch_feed_out_timer(void *);
221static void isoch_feed_in_timer(void *);
222
223/**
224 * Schedule TRBs with filled buffers to HW. Takes filled isoch transfers and
225 * pushes their TRBs to the ring.
226 *
227 * According to 4.11.2.5, we can't just push all TRBs we have. We must not do
228 * it too late, but also not too soon.
229 */
230static void isoch_feed_out(xhci_endpoint_t *ep)
231{
232 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
233 xhci_isoch_t * const isoch = ep->isoch;
234 assert(fibril_mutex_is_locked(&isoch->guard));
235
236 xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
237 xhci_hc_t *hc = bus->hc;
238
239 bool fed = false;
240
241 while (isoch->hw_enqueue != isoch->enqueue) {
242 xhci_isoch_transfer_t * const it = &isoch->transfers[isoch->hw_enqueue];
243
244 assert(it->state == ISOCH_FILLED);
245
246 window_decision_t wd;
247 window_decide(&wd, hc, it->mfindex);
248
249 switch (wd.position) {
250 case WINDOW_TOO_SOON: {
251 const suseconds_t delay = wd.offset * 125;
252 usb_log_debug2("[isoch] delaying feeding buffer %lu for %ldus",
253 it - isoch->transfers, delay);
254 fibril_timer_set_locked(isoch->feeding_timer, delay,
255 isoch_feed_out_timer, ep);
256 break;
257 }
258
259 case WINDOW_INSIDE:
260 usb_log_debug2("[isoch] feeding buffer %lu at 0x%x",
261 it - isoch->transfers, it->mfindex);
262 it->error = schedule_isochronous_trb(ep, it);
263 if (it->error) {
264 it->state = ISOCH_COMPLETE;
265 } else {
266 it->state = ISOCH_FED;
267 fed = true;
268 }
269
270 isoch->hw_enqueue = (isoch->hw_enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
271 break;
272
273 case WINDOW_TOO_LATE:
274 /* Missed the opportunity to schedule. Just mark this transfer as skipped. */
275 usb_log_debug2("[isoch] missed feeding buffer %lu at 0x%x by %u uframes",
276 it - isoch->transfers, it->mfindex, wd.offset);
277 it->state = ISOCH_COMPLETE;
278 it->error = EOK;
279 it->size = 0;
280
281 isoch->hw_enqueue = (isoch->hw_enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
282 break;
283 }
284 }
285
286 if (fed) {
287 const uint8_t slot_id = xhci_device_get(ep->base.device)->slot_id;
288 const uint8_t target = xhci_endpoint_index(ep) + 1; /* EP Doorbells start at 1 */
289 hc_ring_doorbell(hc, slot_id, target);
290 }
291
292}
293
294static void isoch_feed_out_timer(void *ep)
295{
296 xhci_isoch_t * const isoch = xhci_endpoint_get(ep)->isoch;
297 fibril_mutex_lock(&isoch->guard);
298 isoch_feed_out(ep);
299 fibril_mutex_unlock(&isoch->guard);
300}
301
302/**
303 * Schedule TRBs with empty, withdrawn buffers to HW. Takes empty isoch
304 * transfers and pushes their TRBs to the ring.
305 *
306 * According to 4.11.2.5, we can't just push all TRBs we have. We must not do
307 * it too late, but also not too soon.
308 */
309static void isoch_feed_in(xhci_endpoint_t *ep)
310{
311 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
312 xhci_isoch_t * const isoch = ep->isoch;
313 assert(fibril_mutex_is_locked(&isoch->guard));
314
315 xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
316 xhci_hc_t *hc = bus->hc;
317
318 bool fed = false;
319
320 while (isoch->transfers[isoch->enqueue].state <= ISOCH_FILLED) {
321 xhci_isoch_transfer_t * const it = &isoch->transfers[isoch->enqueue];
322
323 /* IN buffers are "filled" with free space */
324 if (it->state == ISOCH_EMPTY) {
325 it->size = isoch->max_size;
326 it->state = ISOCH_FILLED;
327 calc_next_mfindex(ep, it);
328 }
329
330 window_decision_t wd;
331 window_decide(&wd, hc, it->mfindex);
332
333 switch (wd.position) {
334 case WINDOW_TOO_SOON: {
335 /* Not allowed to feed yet. Defer to later. */
336 const suseconds_t delay = wd.offset * 125;
337 usb_log_debug2("[isoch] delaying feeding buffer %lu for %ldus",
338 it - isoch->transfers, delay);
339 fibril_timer_set_locked(isoch->feeding_timer, delay,
340 isoch_feed_in_timer, ep);
341 break;
342 }
343
344 case WINDOW_TOO_LATE:
345 usb_log_debug2("[isoch] missed feeding buffer %lu at 0x%x by %u uframes",
346 it - isoch->transfers, it->mfindex, wd.offset);
347 /* Missed the opportunity to schedule. Schedule ASAP. */
348 it->mfindex += wd.offset;
349 // Align to ESIT start boundary
350 it->mfindex += ep->interval - 1;
351 it->mfindex &= ~(ep->interval - 1);
352
353 /* fallthrough */
354 case WINDOW_INSIDE:
355 isoch->enqueue = (isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
356 isoch->last_mfindex = it->mfindex;
357
358 usb_log_debug2("[isoch] feeding buffer %lu at 0x%x",
359 it - isoch->transfers, it->mfindex);
360
361 it->error = schedule_isochronous_trb(ep, it);
362 if (it->error) {
363 it->state = ISOCH_COMPLETE;
364 } else {
365 it->state = ISOCH_FED;
366 fed = true;
367 }
368 break;
369 }
370 }
371
372 if (fed) {
373 const uint8_t slot_id = xhci_device_get(ep->base.device)->slot_id;
374 const uint8_t target = xhci_endpoint_index(ep) + 1; /* EP Doorbells start at 1 */
375 hc_ring_doorbell(hc, slot_id, target);
376 }
377}
378
379static void isoch_feed_in_timer(void *ep)
380{
381 xhci_isoch_t * const isoch = xhci_endpoint_get(ep)->isoch;
382 fibril_mutex_lock(&isoch->guard);
383 isoch_feed_in(ep);
384 fibril_mutex_unlock(&isoch->guard);
385}
386
387/**
388 * First, withdraw all (at least one) results left by previous transfers to
389 * make room in the ring. Stop on first error.
390 *
391 * When there is at least one buffer free, fill it with data. Then try to feed
392 * it to the xHC.
393 */
394int isoch_schedule_out(xhci_transfer_t *transfer)
395{
396 int err = EOK;
397
398 xhci_endpoint_t *ep = xhci_endpoint_get(transfer->batch.ep);
399 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
400 xhci_isoch_t * const isoch = ep->isoch;
401
402 if (transfer->batch.buffer_size > isoch->max_size) {
403 usb_log_error("Cannot schedule an oversized isochronous transfer.");
404 return ELIMIT;
405 }
406
407 fibril_mutex_lock(&isoch->guard);
408
409 /* Get the buffer to write to */
410 xhci_isoch_transfer_t *it = &isoch->transfers[isoch->enqueue];
411
412 /* Wait for the buffer to be completed */
413 while (it->state == ISOCH_FED || it->state == ISOCH_FILLED) {
414 fibril_condvar_wait(&isoch->avail, &isoch->guard);
415 /* The enqueue ptr may have changed while sleeping */
416 it = &isoch->transfers[isoch->enqueue];
417 }
418
419 isoch->enqueue = (isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
420
421 /* Withdraw results from previous transfers. */
422 transfer->batch.transfered_size = 0;
423 xhci_isoch_transfer_t *res = &isoch->transfers[isoch->dequeue];
424 while (res->state == ISOCH_COMPLETE) {
425 isoch->dequeue = (isoch->dequeue + 1) % XHCI_ISOCH_BUFFER_COUNT;
426
427 res->state = ISOCH_EMPTY;
428 transfer->batch.transfered_size += res->size;
429 transfer->batch.error = res->error;
430 if (res->error)
431 break; // Announce one error at a time
432
433 res = &isoch->transfers[isoch->dequeue];
434 }
435
436 assert(it->state == ISOCH_EMPTY);
437
438 /* Calculate when to schedule next transfer */
439 calc_next_mfindex(ep, it);
440 isoch->last_mfindex = it->mfindex;
441 usb_log_debug2("[isoch] buffer %zu will be on schedule at 0x%x", it - isoch->transfers, it->mfindex);
442
443 /* Prepare the transfer. */
444 it->size = transfer->batch.buffer_size;
445 memcpy(it->data.virt, transfer->batch.buffer, it->size);
446 it->state = ISOCH_FILLED;
447
448 fibril_timer_clear_locked(isoch->feeding_timer);
449 isoch_feed_out(ep);
450
451 fibril_mutex_unlock(&isoch->guard);
452
453 usb_transfer_batch_finish(&transfer->batch);
454 return err;
455}
456
457/**
458 * IN is in fact easier than OUT. Our responsibility is just to feed all empty
459 * buffers, and fetch one filled buffer from the ring.
460 */
461int isoch_schedule_in(xhci_transfer_t *transfer)
462{
463 xhci_endpoint_t *ep = xhci_endpoint_get(transfer->batch.ep);
464 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
465 xhci_isoch_t * const isoch = ep->isoch;
466
467 if (transfer->batch.buffer_size < isoch->max_size) {
468 usb_log_error("Cannot schedule an undersized isochronous transfer.");
469 return ELIMIT;
470 }
471
472 fibril_mutex_lock(&isoch->guard);
473
474 xhci_isoch_transfer_t *it = &isoch->transfers[isoch->dequeue];
475
476 /* Wait for at least one transfer to complete. */
477 while (it->state != ISOCH_COMPLETE) {
478 /* First, make sure we will have something to read. */
479 fibril_timer_clear_locked(isoch->feeding_timer);
480 isoch_feed_in(ep);
481
482 usb_log_debug2("[isoch] waiting for buffer %zu to be completed", it - isoch->transfers);
483 fibril_condvar_wait(&isoch->avail, &isoch->guard);
484
485 /* The enqueue ptr may have changed while sleeping */
486 it = &isoch->transfers[isoch->dequeue];
487 }
488
489 isoch->dequeue = (isoch->dequeue + 1) % XHCI_ISOCH_BUFFER_COUNT;
490
491 /* Withdraw results from previous transfer. */
492 if (!it->error) {
493 memcpy(transfer->batch.buffer, it->data.virt, it->size);
494 transfer->batch.transfered_size = it->size;
495 transfer->batch.error = it->error;
496 }
497
498 /* Prepare the empty buffer */
499 it->state = ISOCH_EMPTY;
500
501 fibril_mutex_unlock(&isoch->guard);
502 usb_transfer_batch_finish(&transfer->batch);
503
504 return EOK;
505}
506
507int isoch_handle_transfer_event(xhci_hc_t *hc, xhci_endpoint_t *ep, xhci_trb_t *trb)
508{
509 assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
510 xhci_isoch_t * const isoch = ep->isoch;
511
512 fibril_mutex_lock(&ep->isoch->guard);
513
514 int err;
515 const xhci_trb_completion_code_t completion_code = TRB_COMPLETION_CODE(*trb);
516
517 switch (completion_code) {
518 case XHCI_TRBC_RING_OVERRUN:
519 case XHCI_TRBC_RING_UNDERRUN:
520 /* For OUT, there was nothing to process */
521 /* For IN, the buffer has overfilled, we empty the buffers and readd TRBs */
522 usb_log_warning("Ring over/underrun.");
523 isoch_reset(ep);
524 fibril_condvar_broadcast(&ep->isoch->avail);
525 fibril_mutex_unlock(&ep->isoch->guard);
526 return EOK;
527 case XHCI_TRBC_SHORT_PACKET:
528 case XHCI_TRBC_SUCCESS:
529 err = EOK;
530 break;
531 default:
532 usb_log_warning("Transfer not successfull: %u", completion_code);
533 err = EIO;
534 break;
535 }
536
537 bool found_mine = false;
538 bool found_incomplete = false;
539
540 /*
541 * The order of delivering events is not necessarily the one we would
542 * expect. It is safer to walk the list of our 4 transfers and check
543 * which one it is.
544 */
545 for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
546 xhci_isoch_transfer_t * const it = &isoch->transfers[i];
547
548 switch (it->state) {
549 case ISOCH_FILLED:
550 found_incomplete = true;
551 break;
552
553 case ISOCH_FED:
554 if (it->interrupt_trb_phys != trb->parameter) {
555 found_incomplete = true;
556 break;
557 }
558
559 usb_log_debug2("[isoch] buffer %zu completed", it - isoch->transfers);
560 it->state = ISOCH_COMPLETE;
561 it->size -= TRB_TRANSFER_LENGTH(*trb);
562 it->error = err;
563 found_mine = true;
564 break;
565 default:
566 break;
567 }
568 }
569
570 if (!found_mine) {
571 usb_log_warning("[isoch] A transfer event occured for unknown transfer.");
572 }
573
574 /*
575 * It may happen that the driver already stopped reading (writing),
576 * and our buffers are filled (empty). As QEMU (and possibly others)
577 * does not send RING_UNDERRUN (OVERRUN) event, detect it here.
578 */
579 if (!found_incomplete) {
580 usb_log_warning("[isoch] Endpoint" XHCI_EP_FMT ": Detected "
581 "isochronous ring %s.", XHCI_EP_ARGS(*ep),
582 (ep->base.direction == USB_DIRECTION_IN) ? "underrun" : "overrun");
583 isoch_reset(ep);
584 }
585
586 fibril_condvar_broadcast(&ep->isoch->avail);
587 fibril_mutex_unlock(&ep->isoch->guard);
588 return EOK;
589}
590
591/**
592 * @}
593 */
Note: See TracBrowser for help on using the repository browser.