这节分析urb的收发流程。
我们首先知道对于usb device 来讲,读写数据用到的是usb_request。而对于usb host来讲,读写数据用到的是urb,有些类似于网络中skbuff。
无论是进行 读还是写 用到的函数都是 usb_submit_urb。在urb结构体中有一个回调函数指针complete。(usb_request中也有一个回调函数指针)这样,对于一个写请求,complete函数表示写请求结束。对于读请求,complete函数表示要读的数据已经读到,通常在complete函数中进行处理。 因此,无论读写都可以通过usb_submit_urb来实现。既然有回调函数,那么usb_submit_urb就是异步的。
下面,我们来看usb_submit_urb的实现。usb_submit_urb 最终会调用usb_hcd_submit_urb。
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
int status;
struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
usb_get_urb(urb);
atomic_inc(&urb->use_count);
atomic_inc(&urb->dev->urbnum);
usbmon_urb_submit(&hcd->self, urb);
status = map_urb_for_dma(hcd, urb, mem_flags);
if (unlikely(status)) {
usbmon_urb_submit_error(&hcd->self, urb, status);
goto error;
}
if (is_root_hub(urb->dev))
status = rh_urb_enqueue(hcd, urb);
else
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
return status;
}
对于发送目标是普通device(非root hub,root hub的情况我们在后面分析),会调用到hcd->driver->urb_enqueue。这个函数对应着ohci_urb_enqueue。并最终调用到td_submit_urb完成传输。
完成传输之后,我们看看complete回调函数时怎么被调用的。按照常理,不论是发完或者是接收到 新的数据,它的入口都应该在中断函数中,也就是我们之前看到的usb_hcd_irq.在这里又调用了ohci_irq. 为此,我们看这个函数。
static irqreturn_t ohci_irq (struct usb_hcd *hcd)
{
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
struct ohci_regs __iomem *regs = ohci->regs;
int ints;
/* Read interrupt status (and flush pending writes). We ignore the
* optimization of checking the LSB of hcca->done_head; it doesn't
* work on all systems (edge triggering for OHCI can be a factor).
*/
ints = ohci_readl(ohci, *regs->intrstatus);
/* Check for an all 1's result which is a typical consequence
* of dead, unclocked, or unplugged (CardBus...) devices
*/
if (ints == ~(u32)0) {
disable (ohci);
ohci_dbg (ohci, "device removed!\n");
return IRQ_HANDLED;
}
/* We only care about interrupts that are enabled */
ints &= ohci_readl(ohci, ®s->intrenable);
/* interrupt for some other device? */
if (ints == 0)
return IRQ_NOTMINE;
if (ints & OHCI_INTR_RHSC) {
ohci_vdbg(ohci, "rhsc\n");
ohci->next_statechange = jiffies + STATECHANGE_DELAY;
ohci_writel(ohci, OHCI_INTR_RD | OHCI_INTR_RHSC,
®s->intrstatus);
ohci_writel(ohci, OHCI_INTR_RHSC, regs->intrdisable);
usb_hcd_poll_rh_status(hcd);
}
/* For connect and disconnect events, we expect the controller
* to turn on RHSC along with RD. But for remote wakeup events
* this might not happen.
*/
else if (ints & OHCI_INTR_RD) {
ohci_vdbg(ohci, "resume detect\n");
ohci_writel(ohci, OHCI_INTR_RD, regs->intrstatus);
hcd->poll_rh = 1;
if (ohci->autostop) {
spin_lock (&ohci->lock);
ohci_rh_resume (ohci);
spin_unlock (&ohci->lock);
} else
usb_hcd_resume_root_hub(hcd);
}
if (ints & OHCI_INTR_WDH) {
spin_lock (&ohci->lock);
dl_done_list (ohci);
spin_unlock (&ohci->lock);
}
if (quirk_zfmicro(ohci) && (ints & OHCI_INTR_SF)) {
spin_lock(&ohci->lock);
if (ohci->ed_to_check) {
struct ed *ed = ohci->ed_to_check;
if (check_ed(ohci, ed)) {
/* HC thinks the TD list is empty; HCD knows
* at least one TD is outstanding
*/
if (--ohci->zf_delay == 0) {
struct td *td = list_entry(
ed->td_list.next,
struct td, td_list);
ohci_warn(ohci,
"Reclaiming orphan TD %p\n",
td);
takeback_td(ohci, td);
ohci->ed_to_check = NULL;
}
} else
ohci->ed_to_check = NULL;
}
spin_unlock(&ohci->lock);
}
/* could track INTR_SO to reduce available PCI/... bandwidth */
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
* when there's still unlinking to be done (next frame).
*/
spin_lock (&ohci->lock);
if (ohci->ed_rm_list)
finish_unlinks (ohci, ohci_frame_no(ohci));
if ((ints & OHCI_INTR_SF) != 0
&& !ohci->ed_rm_list
&& !ohci->ed_to_check
&& HC_IS_RUNNING(hcd->state))
ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable);
spin_unlock (&ohci->lock);
if (HC_IS_RUNNING(hcd->state)) {
ohci_writel (ohci, ints, ®s->intrstatus);
ohci_writel (ohci, OHCI_INTR_MIE, ®s->intrenable);
// flush those writes
(void) ohci_readl (ohci, &ohci->regs->control);
}
return IRQ_HANDLED;
}
有三类中断需要我们关注。
1. 其中RHSC代表 root hub status change, 这里应该会给root hub发送消息。也就是说root hub感应有设备插拔的入口 也是这个中断函数,root hub并没有单独的中断函数来处理这样的事件。 后面我们在讲述root hub的工作原理。
2. OHCI_INTR_SF SF 是 start frame。 这个是什么呢??
3.. OHCI_INTR_WDH 代表write back of done head。就是在这里调用了complete函数。我们看这个函数。
static void
dl_done_list (struct ohci_hcd *ohci)
{
struct td *td = dl_reverse_done_list (ohci);
while (td) {
struct td *td_next = td->next_dl_td;
takeback_td(ohci, td);
td = td_next;
}
}
后面的调用过程是这样的:takeback_td ----- finish_urb ----- usb_hcd_giveback_urb------ urb->complete。
这样,我们就看到了回调过程的实现。