linux EHCI DRIVER之中断处理函数ehci_irq()分析(一)
EHCI的interrupt在HCD中被分为了6种类型,如下宏定义:/* these STS_* flags are also intr_enable bits (USBINTR) */#define STS_IAA (1#define STS_FATAL (1#define STS_FLR (1#define STS_PCD (1#define STS_ERR (1#
/* these STS_* flags are also intr_enable bits (USBINTR) */
#define STS_IAA (1<<5) /* Interrupted on async advance */
#define STS_FATAL (1<<4) /* such as some PCI access errors */
#define STS_FLR (1<<3) /* frame list rolled over */
#define STS_PCD (1<<2) /* port change detect */
#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */
#define STS_INT (1<<0) /* "normal" completion (short, ...) */
这些就是触发EHCI产生中断的各种类型,在中断处理函数ehci_irq()中要对这几种interrupt作出不同的处理,先简单介绍一下各个interrupt的情况。
IAA是指当要从asynchronous schedule传输队列中移除一个QH时,为了解决被移除的QH的值可能在HC(host controller)中有缓存,为了使内存和HC中的 qh保持一致,而需要在HCD(host controller driver)移除某个QH后对HC的缓存进行更新,从而使内存中的QH队列与HC的缓存保持一致,当HC完成缓存更新后,且相应的中断enable,就会发出一个IAA中断告之HCD更新完成。
PCD(port change detect)是指root hub上某个端口上有设备插入或拔出所产生的中断,需要进一步的读取root hub上各port对应的register判断是插入还是拔出。
ERR是指在HC和device间数据传输失败后发出的传输出错中断,出错的原因会被回写到当前用于传输的描述中,如qtd的token中。
INT是指正确完成一次数据传输后所发出的中断,这个中断并不是在传输完成后马上产生的,它要等到下一个中断时隙的到来后才产生。
EHCI HC中与中断相关的register有USBSTS和USBINTR两个,而在linux 中的执行EHCI的irq处理函数就是ehci_irq(),代码如下:
1. static irqreturn_t ehci_irq (struct usb_hcd *hcd)
2. {
3. struct ehci_hcd *ehci = hcd_to_ehci (hcd);
4. u32 status, masked_status, pcd_status = 0, cmd;
5. int bh;
6. spin_lock (&ehci->lock);
7. status = ehci_readl(ehci, &ehci->regs->status);
8. /* e.g. cardbus physical eject */
9. if (status == ~(u32) 0) {
10. ehci_dbg (ehci, "device removed\n");
11. goto dead;
12. }
13. /*
14. * We don't use STS_FLR, but some controllers don't like it to
15. * remain on, so mask it out along with the other status bits.
16. */
17. masked_status = status & (INTR_MASK | STS_FLR);
18. /* Shared IRQ? */
19. if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED)) {
20. spin_unlock(&ehci->lock);
21. return IRQ_NONE;
22. }
23. /* clear (just) interrupts */
24. ehci_writel(ehci, masked_status, &ehci->regs->status);
25. cmd = ehci_readl(ehci, &ehci->regs->command);
26. bh = 0;
27. #ifdef VERBOSE_DEBUG
28. /* unrequested/ignored: Frame List Rollover */
29. dbg_status (ehci, "irq", status);
30. #endif
31. /* INT, ERR, and IAA interrupt rates can be throttled */
32. /* normal [4.15.1.2] or error [4.15.1.1] completion */
33. if (likely ((status & (STS_INT|STS_ERR)) != 0)) {
34. if (likely ((status & STS_ERR) == 0))
35. COUNT (ehci->stats.normal);
36. else
37. COUNT (ehci->stats.error);
38. bh = 1;
39. }
40. /* complete the unlinking of some qh [4.15.2.3] */
41. if (status & STS_IAA) {
42. /* Turn off the IAA watchdog */
43. ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_IAA_WATCHDOG);
44. /*
45. * Mild optimization: Allow another IAAD to reset the
46. * hrtimer, if one occurs before the next expiration.
47. * In theory we could always cancel the hrtimer, but
48. * tests show that about half the time it will be reset
49. * for some other event anyway.
50. */
51. if (ehci->next_hrtimer_event == EHCI_HRTIMER_IAA_WATCHDOG)
52. ++ehci->next_hrtimer_event;
53. /* guard against (alleged) silicon errata */
54. if (cmd & CMD_IAAD)
55. ehci_dbg(ehci, "IAA with IAAD still set?\n");
56. if (ehci->iaa_in_progress)
57. COUNT(ehci->stats.iaa);
58. end_unlink_async(ehci);
59. }
60. /* remote wakeup [4.3.1] */
61. if (status & STS_PCD) {
62. unsigned i = HCS_N_PORTS (ehci->hcs_params);
63. u32 ppcd = ~0;
64. /* kick root hub later */
65. pcd_status = status;
66. /* resume root hub? */
67. if (ehci->rh_state == EHCI_RH_SUSPENDED)
68. usb_hcd_resume_root_hub(hcd);
69. /* get per-port change detect bits */
70. if (ehci->has_ppcd)
71. ppcd = status >> 16;
72. while (i--) {
73. int pstatus;
74. /* leverage per-port change bits feature */
75. if (!(ppcd & (1 << i)))
76. continue;
77. pstatus = ehci_readl(ehci,
78. &ehci->regs->port_status[i]);
79. if (pstatus & PORT_OWNER)
80. continue;
81. if (!(test_bit(i, &ehci->suspended_ports) &&
82. ((pstatus & PORT_RESUME) ||
83. !(pstatus & PORT_SUSPEND)) &&
84. (pstatus & PORT_PE) &&
85. ehci->reset_done[i] == 0))
86. continue;
87. /* start 20 msec resume signaling from this port,
88. * and make khubd collect PORT_STAT_C_SUSPEND to
89. * stop that signaling. Use 5 ms extra for safety,
90. * like usb_port_resume() does.
91. */
92. ehci->reset_done[i] = jiffies + msecs_to_jiffies(25);
93. set_bit(i, &ehci->resuming_ports);
94. ehci_dbg (ehci, "port %d remote wakeup\n", i + 1);
95. usb_hcd_start_port_resume(&hcd->self, i);
96. mod_timer(&hcd->rh_timer, ehci->reset_done[i]);
97. }
98. }
99. /* PCI errors [4.15.2.4] */
100. if (unlikely ((status & STS_FATAL) != 0)) {
101. ehci_err(ehci, "fatal error\n");
102. dbg_cmd(ehci, "fatal", cmd);
103. dbg_status(ehci, "fatal", status);
104. dead:
105. usb_hc_died(hcd);
106. /* Don't let the controller do anything more */
107. ehci->shutdown = true;
108. ehci->rh_state = EHCI_RH_STOPPING;
109. ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE);
110. ehci_writel(ehci, ehci->command, &ehci->regs->command);
111. ehci_writel(ehci, 0, &ehci->regs->intr_enable);
112. ehci_handle_controller_death(ehci);
113. /* Handle completions when the controller stops */
114. bh = 0;
115. }
116. if (bh)
117. ehci_work (ehci);
118. spin_unlock (&ehci->lock);
119. if (pcd_status)
120. usb_hcd_poll_rh_status(hcd);
121. return IRQ_HANDLED;
122. }
整个代码可以分成三个阶段,首先读取中断相关的status register,产生中断的原因包含在其中,再对status进行解析,找出产生中断的原因,最后根据对应产生中断的原因进行各自的处理,当其中还应该包含一个对相应中断位清零。
当中断产生后CPU跳转到中断入口,进入函数ehci_irq()中,第7行,先加一把锁,说明接下来的任务不能被打断。
第8行从状态寄存器USBSTS中读取值,暂存在变量status中。根据EHCI spec的规定,USBSTS的第0位指明STS_INT中断,第1位为STS_ERR中断,第2位代表STS_PCD中断,第4位对应STS_FATAL,第5位表明有STS_IAA中断产生。
第9行,寄存器USBINTR是各个中断的使能控制位,可以操作相应位来开启或关闭某个中断的响应,读取它的值可以获知当前使能的中断类型。
第11行判断status的值,如果status上各位都1,则表明设备被移除了,代码跳到dead标号处,做后续处理。
第20-24行,是在判断EHCI的中断是否是一个共享中断,如果与其他设备共享了同一个中断号,就需要进一步确定中断的来源,把status中仅与中断相关了位的值取出放到变量masked_status中,43行的if语句判断masked_status的值,如果masked_status不为零,表明当前的中断是来至EHCI HC上的,需要对ehci的中断做处理,反之则不是。
第25-28行对USBSTS中产生中断的位清零,并把USBCMD的当前值读到变量cmd中。
从33行开始,才是IRQ的主要处理部分。主要针对了INT、ERR、IAA、PCD中断进行了处理,在三个大的if语句中找出产生中断的具体原因,并做相应的处理。
第35-41行,判断是否产生了STS_INT或STS_ERR中断,注意这两个中断是互斥的,即两者不可能同时产生,STS_INT表明传输成功,STS_ERR则说明传输失败。STS_INT和STS_ERR这两种中断是和数据传输结果相关的。如果两者有其一发生,那么变量bh置1。对这两种中断进一步的处理是在110行的ehci_work()函数中。这里暂时先跳过ehci_irq()函数接下来的两个if判断语句,把对STS_INT和STS_ERR中断下一步地处理讲完。
第109行,如果中断来源是STS_INT或STS_ERR,那么bh的值为1,进入到ehci_work()函数中,代码如下:
1. /*
2. * ehci_work is called from some interrupts, timers, and so on.
3. * it calls driver completion functions, after dropping ehci->lock.
4. */
5. static void ehci_work (struct ehci_hcd *ehci)
6. {
7. /* another CPU may drop ehci->lock during a schedule scan while
8. * it reports urb completions. this flag guards against bogus
9. * attempts at re-entrant schedule scanning.
10. */
11. if (ehci->scanning) {
12. ehci->need_rescan = true;
13. return;
14. }
15. ehci->scanning = true;
16. rescan:
17. ehci->need_rescan = false;
18. if (ehci->async_count)
19. scan_async(ehci);
20. if (ehci->intr_count > 0)
21. scan_intr(ehci);
22. if (ehci->isoc_count > 0)
23. scan_isoc(ehci);
24. if (ehci->need_rescan)
25. goto rescan;
26. ehci->scanning = false;
27. /* the IO watchdog guards against hardware or driver bugs that
28. * misplace IRQs, and should let us run completely without IRQs.
29. * such lossage has been observed on both VT6202 and VT8235.
30. */
31. turn_on_io_watchdog(ehci);
32. }
对ehci_work()函数的调用,可以通过两条路径,一是我们这里的interrupt,二是用timber去polling。这里采用两种方式的原因是提高执行效率,减少interrupt的负担,我们知道在async传输中,如果一次要传输的数据量需要多个qtd才能装载完,那么HCD只会把最后一个qtd的IOC位置1,即在最后一个qtd传输完成后才会产生一个中断,如果这时才去处理这些qtd,将会使中断处理的时间过长,并且如果其中某个qtd传输失败也得不到及时的处理,所以会用一个timer来辅助处理已经完成的qtd,iTD也做了类似的处理。
EHCI的整个传输是基于各种descriptor实现的,这些descriptor的可以说就是EHCI的语法,要与一个HC沟通就要按照这些descriptor的语法在内存中组织数据段。函数ehci_work()的工作是要处理HC按照指定的descriptor完成传输后的结果。具体到EHCI的传输类型可以分为asynchronous和isochronous两大类,qh和qtd的组合常用于asynchronous,itd则常用于isochronous。Asynchronous的进一步处理入口是在15行的scan_async()函数,isochronous则是在17行的scan_periodic()函数中。这里主要分析前者scan_async()函数。后续的笔记将忽略一些代码上的细节,把重点放在处理的流程上。
原文: http://blog.csdn.net/yaongtime/article/details/19753835
更多推荐
所有评论(0)