📄 usb-uhci.c
字号:
// mode: 0: unlink but no deletion mark (step 1 of async_unlink)// 1: regular (unlink/delete-mark)// 2: deletion mark for QH (step 2 of async_unlink)// looks a bit complicated because of all the bulk queueing goodies_static void uhci_clean_transfer (uhci_t *s, urb_t *urb, uhci_desc_t *qh, int mode){ uhci_desc_t *bqh, *nqh, *prevqh, *prevtd; int now; urb_priv_t *priv=(urb_priv_t*)urb->hcpriv; now=UHCI_GET_CURRENT_FRAME(s); bqh=priv->bottom_qh; if (!priv->next_queued_urb) { // no more appended bulk queues queue_dbg("uhci_clean_transfer: No more bulks for urb %p, qh %p, bqh %p, nqh %p",urb, qh, bqh, priv->next_qh); if (priv->prev_queued_urb) { // qh not top of the queue urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv; if (mode != 2) { unsigned long flags; spin_lock_irqsave (&s->qh_lock, flags); prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list); prevtd = list_entry (prevqh->vertical.prev, uhci_desc_t, vertical); prevtd->hw.td.link = virt_to_bus(priv->bottom_qh) | UHCI_PTR_QH; // skip current qh mb(); queue_dbg("uhci_clean_transfer: relink pqh %p, ptd %p",prevqh, prevtd); spin_unlock_irqrestore (&s->qh_lock, flags); ppriv->bottom_qh = priv->bottom_qh; ppriv->next_queued_urb = NULL; } } else { // queue is dead, qh is top of the queue if (mode!=2) unlink_qh(s, qh); // remove qh from horizontal chain if (bqh) { // remove remainings of bulk queue nqh=priv->next_qh; if (mode != 2) unlink_qh(s, nqh); // remove nqh from horizontal chain if (mode) { nqh->last_used = bqh->last_used = now; list_add_tail (&nqh->horizontal, &s->free_desc); list_add_tail (&bqh->horizontal, &s->free_desc); } } } } else { // there are queued urbs following queue_dbg("uhci_clean_transfer: urb %p, prevurb %p, nexturb %p, qh %p, bqh %p, nqh %p", urb, priv->prev_queued_urb, priv->next_queued_urb, qh, bqh, priv->next_qh); if (mode !=2) { // no work for cleanup at unlink-completion urb_t *nurb; unsigned long flags; nurb = priv->next_queued_urb; spin_lock_irqsave (&s->qh_lock, flags); if (!priv->prev_queued_urb) { // top QH prevqh = list_entry (qh->horizontal.prev, uhci_desc_t, horizontal); prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH; list_del (&qh->horizontal); // remove this qh form horizontal chain list_add (&bqh->horizontal, &prevqh->horizontal); // insert next bqh in horizontal chain } else { // intermediate QH urb_priv_t* ppriv=(urb_priv_t*)priv->prev_queued_urb->hcpriv; urb_priv_t* npriv=(urb_priv_t*)nurb->hcpriv; uhci_desc_t * bnqh; bnqh = list_entry (npriv->desc_list.next, uhci_desc_t, desc_list); ppriv->bottom_qh = bnqh; ppriv->next_queued_urb = nurb; prevqh = list_entry (ppriv->desc_list.next, uhci_desc_t, desc_list); prevqh->hw.qh.head = virt_to_bus(bqh) | UHCI_PTR_QH; } mb(); spin_unlock_irqrestore (&s->qh_lock, flags); ((urb_priv_t*)nurb->hcpriv)->prev_queued_urb=priv->prev_queued_urb; } } if (mode) { qh->last_used = now; list_add_tail (&qh->horizontal, &s->free_desc); // mark for later deletion/kfree }}/*-------------------------------------------------------------------*/// Release bandwidth for Interrupt or Isoc. transfers _static void uhci_release_bandwidth(urb_t *urb){ if (urb->bandwidth) { switch (usb_pipetype(urb->pipe)) { case PIPE_INTERRUPT: usb_release_bandwidth (urb->dev, urb, 0); break; case PIPE_ISOCHRONOUS: usb_release_bandwidth (urb->dev, urb, 1); break; default: break; } } }/*-------------------------------------------------------------------*/// unlinks an urb by dequeuing its qh, waits some frames and forgets it_static int uhci_unlink_urb_sync (uhci_t *s, urb_t *urb){ uhci_desc_t *qh; urb_priv_t *urb_priv; unsigned long flags=0; struct usb_device *usb_dev; spin_lock_irqsave (&s->urb_list_lock, flags); if (!in_interrupt()) // shouldn't be called from interrupt at all... spin_lock(&urb->lock); if (urb->status == -EINPROGRESS) { // URB probably still in work dequeue_urb (s, urb); uhci_switch_timer_int(s); s->unlink_urb_done=1; uhci_release_bandwidth(urb); urb->status = -ENOENT; // mark urb as killed if (!in_interrupt()) spin_unlock(&urb->lock); spin_unlock_irqrestore (&s->urb_list_lock, flags); urb_priv = urb->hcpriv; switch (usb_pipetype (urb->pipe)) { case PIPE_INTERRUPT: usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); case PIPE_ISOCHRONOUS: uhci_clean_iso_step1(s, urb_priv); uhci_wait_ms(1); uhci_clean_iso_step2(s, urb_priv); break; case PIPE_BULK: case PIPE_CONTROL: spin_lock_irqsave (&s->urb_list_lock, flags); qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list); uhci_clean_transfer(s, urb, qh, 1); spin_unlock_irqrestore (&s->urb_list_lock, flags); uhci_wait_ms(1); } #ifdef DEBUG_SLAB kmem_cache_free (urb_priv_kmem, urb->hcpriv);#else kfree (urb->hcpriv);#endif usb_dev = urb->dev; if (urb->complete) { dbg("unlink_urb: calling completion"); urb->dev = NULL; urb->complete ((struct urb *) urb); } usb_dec_dev_use (usb_dev); } else { if (!in_interrupt()) spin_unlock(&urb->lock); spin_unlock_irqrestore (&s->urb_list_lock, flags); } return 0;}/*-------------------------------------------------------------------*/// async unlink_urb completion/cleanup work// has to be protected by urb_list_lock!// features: if set in transfer_flags, the resulting status of the killed// transaction is not overwritten_static void uhci_cleanup_unlink(uhci_t *s, int force){ struct list_head *q; urb_t *urb; struct usb_device *dev; int pipe,now; urb_priv_t *urb_priv; q=s->urb_unlinked.next; now=UHCI_GET_CURRENT_FRAME(s); while (q != &s->urb_unlinked) { urb = list_entry (q, urb_t, urb_list); urb_priv = (urb_priv_t*)urb->hcpriv; q = urb->urb_list.next; if (force || ((urb_priv->started != 0xffffffff) && (urb_priv->started != now))) { async_dbg("async cleanup %p",urb); switch (usb_pipetype (urb->pipe)) { // process descriptors case PIPE_CONTROL: process_transfer (s, urb, 2); // 2: don't unlink (already done) break; case PIPE_BULK: if (!s->avoid_bulk.counter) process_transfer (s, urb, 2); // don't unlink (already done) else continue; break; case PIPE_ISOCHRONOUS: process_iso (s, urb, 1); // force, don't unlink break; case PIPE_INTERRUPT: process_interrupt (s, urb); break; } if (!(urb->transfer_flags & USB_TIMEOUT_KILLED)) urb->status = -ECONNRESET; // mark as asynchronously killed pipe = urb->pipe; // completion may destroy all... dev = urb->dev; urb_priv = urb->hcpriv; if (urb->complete) { spin_unlock(&s->urb_list_lock); urb->dev = NULL; urb->complete ((struct urb *) urb); spin_lock(&s->urb_list_lock); } if (!(urb->transfer_flags & USB_TIMEOUT_KILLED)) urb->status = -ENOENT; // now the urb is really dead switch (usb_pipetype (pipe)) { case PIPE_ISOCHRONOUS: case PIPE_INTERRUPT: uhci_clean_iso_step2(s, urb_priv); break; } usb_dec_dev_use (dev);#ifdef DEBUG_SLAB kmem_cache_free (urb_priv_kmem, urb_priv);#else kfree (urb_priv);#endif list_del (&urb->urb_list); } }}/*-------------------------------------------------------------------*/// needs urb_list_lock!_static int uhci_unlink_urb_async (uhci_t *s,urb_t *urb){ uhci_desc_t *qh; urb_priv_t *urb_priv; async_dbg("unlink_urb_async called %p",urb); if ((urb->status == -EINPROGRESS) || ((usb_pipetype (urb->pipe) == PIPE_INTERRUPT) && ((urb_priv_t*)urb->hcpriv)->flags)) { ((urb_priv_t*)urb->hcpriv)->started = ~0; dequeue_urb (s, urb); list_add_tail (&urb->urb_list, &s->urb_unlinked); // store urb uhci_switch_timer_int(s); s->unlink_urb_done = 1; urb->status = -ECONNABORTED; // mark urb as "waiting to be killed" urb_priv = (urb_priv_t*)urb->hcpriv; switch (usb_pipetype (urb->pipe)) { case PIPE_INTERRUPT: usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); case PIPE_ISOCHRONOUS: uhci_clean_iso_step1 (s, urb_priv); break; case PIPE_BULK: case PIPE_CONTROL: qh = list_entry (urb_priv->desc_list.next, uhci_desc_t, desc_list); uhci_clean_transfer (s, urb, qh, 0); break; } ((urb_priv_t*)urb->hcpriv)->started = UHCI_GET_CURRENT_FRAME(s); return -EINPROGRESS; // completion will follow } return 0; // URB already dead}/*-------------------------------------------------------------------*/_static int uhci_unlink_urb (urb_t *urb){ uhci_t *s; unsigned long flags=0; dbg("uhci_unlink_urb called for %p",urb); if (!urb || !urb->dev) // you never know... return -EINVAL; s = (uhci_t*) urb->dev->bus->hcpriv; if (usb_pipedevice (urb->pipe) == s->rh.devnum) return rh_unlink_urb (urb); if (!urb->hcpriv) return -EINVAL; if (urb->transfer_flags & USB_ASYNC_UNLINK) { int ret; spin_lock_irqsave (&s->urb_list_lock, flags); // The URB needs to be locked if called outside completion context if (!in_interrupt()) spin_lock(&urb->lock); uhci_release_bandwidth(urb); ret = uhci_unlink_urb_async(s, urb); if (!in_interrupt()) spin_unlock(&urb->lock); spin_unlock_irqrestore (&s->urb_list_lock, flags); return ret; } else return uhci_unlink_urb_sync(s, urb);}/*-------------------------------------------------------------------*/// In case of ASAP iso transfer, search the URB-list for already queued URBs// for this EP and calculate the earliest start frame for the new// URB (easy seamless URB continuation!)_static int find_iso_limits (urb_t *urb, unsigned int *start, unsigned int *end){ urb_t *u, *last_urb = NULL; uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; struct list_head *p; int ret=-1; unsigned long flags; spin_lock_irqsave (&s->urb_list_lock, flags); p=s->urb_list.prev; for (; p != &s->urb_list; p = p->prev) { u = list_entry (p, urb_t, urb_list); // look for pending URBs with identical pipe handle // works only because iso doesn't toggle the data bit! if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && (u->status == -EINPROGRESS)) { if (!last_urb) *start = u->start_frame; last_urb = u; } } if (last_urb) { *end = (last_urb->start_frame + last_urb->number_of_packets) & 1023; ret=0; } spin_unlock_irqrestore(&s->urb_list_lock, flags); return ret;}/*-------------------------------------------------------------------*/// adjust start_frame according to scheduling constraints (ASAP etc)_static int iso_find_start (urb_t *urb){ uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; unsigned int now; unsigned int start_limit = 0, stop_limit = 0, queued_size; int limits; now = UHCI_GET_CURRENT_FRAME (s) & 1023; if ((unsigned) urb->number_of_packets > 900) return -EFBIG; limits = find_iso_limits (urb, &start_limit, &stop_limit); queued_size = (stop_limit - start_limit) & 1023; if (urb->transfer_flags & USB_ISO_ASAP) { // first iso if (limits) { // 10ms setup should be enough //FIXME! urb->start_frame = (now + 10) & 1023; } else { urb->start_frame = stop_limit; //seamless linkage if (((now - urb->start_frame) & 1023) <= (unsigned) urb->number_of_packets) { info("iso_find_start: gap in seamless isochronous scheduling"); dbg("iso_find_start: now %u start_frame %u number_of_packets %u pipe 0x%08x", now, urb->start_frame, urb->number_of_packets, urb->pipe); urb->start_frame = (now + 5) & 1023; // 5ms setup should be enough //FIXME! } } } else { urb->start_frame &= 1023; if (((now - urb->start_frame) & 1023) < (unsigned) urb->number_of_packets) { dbg("iso_find_start: now between start_frame and end"); return -EAGAIN; } } /* check if either start_frame or start_frame+number_of_packets-1 lies between start_limit and stop_limit */ if (limits) return 0; if (((urb->start_frame - start_limit) & 1023) < queued_size || ((urb->start_frame + urb->number_of_packets - 1 - start_limit) & 1023) < queued_size) { dbg("iso_find_start: start_frame %u number_of_packets %u start_limit %u stop_limit %u", urb->start_frame, urb->number_of_packets, start_limit, stop_limit); return -EAGAIN; } return 0;}/*-------------------------------------------------------------------*/// submits USB interrupt (ie. polling ;-) // ASAP-flag set implicitely// if period==0, the the transfer is only done once_static int uhci_submit_int_urb (urb_t *urb){ uhci_t *s = (uhci_t*) urb->dev->bus->hcpriv; urb_priv_t *urb_priv = urb->hcpriv; int nint, n, ret; uhci_desc_t *td; int status, destination; int info; unsigned int pipe = urb->pipe; if (urb->interval < 0 || urb->interval >= 256) return -EINVAL; if (urb->interval == 0) nint = 0; else { for (nint = 0, n = 1; nint <= 8; nint++, n += n) // round interval down to 2^n { if (urb->interval < n) { urb->interval = n / 2; break; } } nint--; } dbg("Rounded interval to %i, chain %i", urb->interval, nint); urb->start_frame = UHCI_GET_CURRENT_FRAME (s) & 1023; // remember start frame, just in case... urb->number_of_packets = 1; // INT allows only one packet if (urb->transfer_buffer_length > usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe))) return -EINVAL; ret = alloc_td (&td, UHCI_PTR_DEPTH); if (ret) return -ENOMEM;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -