分类: LINUX
2011-04-01 16:58:47
谨以此文纪念过往的岁月
一. 前言
在前文中我们看过了urb的入队以及td和ed的处理,那在该文中来看我们td处理完成后,HCD的处理。神说:有始必须要有终。咱的urb的入队了,OHCI处理了,咱也不能不管啊,咱还是得处理他,要不然要它传输数据干什么啊!闲话不说,咱们开始干活。
二. OHCI中断
在OHCI中我们有很多程序咱们并没有看,其中一个最主要的就是OHCI的中断处理函数。那在本文中,我们来其具体的实现。我们将其中关于比较特殊的usb处理删除。其实说到中断,我们不得不来看OHCI的中断类型。
#define OHCI_INTR_SO (1 << 0) --调度溢出
当usb在调度当前帧和更新溢出,该bit位会被设置同时导致HcCommandStatus的SchedulingOverrunCount域增加
#define OHCI_INTR_WDH (1 << 1) --回写done_head
在hc将HcDoneHead写到HccaDoneHead时被设置,直到该bit位被清零才会真正的更新HccaDoneHead。Hcd在保存了HccaDoneHead的内容后可以清除该位。
#define OHCI_INTR_SF (1 << 2) --帧传输开始
在每一帧开始和更新HccaFrameNumber后设置该位,一般HC同时产生SOF
#define OHCI_INTR_RD (1 << 3) --监测恢复
当HC监测到一个usb设备重新恢复
#define OHCI_INTR_UE (1 << 4)
HC监测到usb系统错误
#define OHCI_INTR_FNO (1 << 5) --帧计数溢出
当HcFmNumber的最高位值改变,从0->1或1->0,或者在HccaFrameNumber更新后,设置该位。
#define OHCI_INTR_RHSC (1 << 6) --根hub状态改变
#define OHCI_INTR_OC (1 << 30) --所有者改变
#define OHCI_INTR_MIE (1 << 31)
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;
ints = ohci_readl(ohci, ®s->intrstatus); --读取中断状态值
if (ints == ~(u32)0) {
disable (ohci);
return IRQ_HANDLED;
}
ints &= ohci_readl(ohci, ®s->intrenable); --读取中断使能寄存器
if (ints == 0)
return IRQ_NOTMINE;
if (ints & OHCI_INTR_UE) { --usb系统错误,当然重新启动OHCI
disable (ohci);
ohci_dump (ohci, 1);
ohci_usb_reset (ohci);
}
if (ints & OHCI_INTR_RHSC) {
ohci->next_statechange = jiffies + STATECHANGE_DELAY;
ohci_writel(ohci, OHCI_INTR_RD | OHCI_INTR_RHSC,®s->intrstatus);
ohci_writel(ohci, OHCI_INTR_RHSC, ®s->intrdisable);
(hcd); --重新轮询root hub的状态
}
else if (ints & OHCI_INTR_RD) {
ohci_writel(ohci, OHCI_INTR_RD, ®s->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);
}
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);
(void) ohci_readl (ohci, &ohci->regs->control);
}
return IRQ_HANDLED;
}
在OHCI处理td时是不管这个td有没有真正的处理成功,就把它放到done_head中。
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;
}
}
static struct td *dl_reverse_done_list (struct ohci_hcd *ohci)
{
u32 td_dma;
struct td *td_rev = NULL;
struct td *td = NULL;
td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head);
ohci->hcca->done_head = 0;
wmb();
while (td_dma) {
int cc;
td = (ohci, td_dma);
td->hwINFO |= cpu_to_hc32 (ohci, TD_DONE);
cc = TD_CC_GET (hc32_to_cpup (ohci, &td->hwINFO));
if (cc != TD_CC_NOERROR&& (td->ed->hwHeadP&cpu_to_hc32 (ohci, ED_H)))
ed_halted(ohci, td, cc); --如果td传输有错误则停止ed。
td->next_dl_td = td_rev;
td_rev = td;
td_dma = hc32_to_cpup (ohci, &td->hwNextTD);
}
return td_rev;
}
dma_to_td根据td物理地址来查找td,在上文中我们也没有将是如何将td如何保存在ohci->hash中的。从字面意思就可以理解其实是将td保存到hash表中,其保存的规则是:
#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE)
关于hash表是什么东西是怎么用的,这里就不说了,以前看cdev的设备号时看过。在这里还是采用了链表法实现的.