您的位置:首页 > 运维架构 > Linux

linux EHCI DRIVER之中断处理函数ehci_irq()分析(一)

2014-06-16 10:59 387 查看
EHCI的interrupt在HCD中被分为了6种类型,如下宏定义:
/* 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()函数。后续的笔记将忽略一些代码上的细节,把重点放在处理的流程上。

原文:/article/10442061.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: