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

    EHCIinterruptHCD中被分为了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更新完成

    PCDport change detect)是指root hub上某个端口上有设备插入或拔出所产生的中断,需要进一步的读取root hub上各port对应register判断是插入还是拔出

    ERR是指在HC和device间数据传输失败后发出的传输出错中断,出错的原因会被回写到当前用于传输的描述中,如qtd的token中。

    INT是指正确完成一次数据传输后所发出的中断,这个中断并不是在传输完成后马上产生的,它要等到下一个中断时隙的到来后才产生。

 

    EHCI HC中与中断相关的registerUSBSTSUSBINTR两个,而在linux 中的执行EHCIirq处理函数就是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的主要处理部分。主要针对了INTERRIAAPCD中断进行了处理,在三个大的if语句中找出产生中断的具体原因,并做相应的处理。

 

    第35-41行,判断是否产生了STS_INTSTS_ERR中断,注意这两个中断是互斥的,即两者不可能同时产生,STS_INT表明传输成功,STS_ERR则说明传输失败。STS_INTSTS_ERR这两种中断是和数据传输结果相关的。如果两者有其一发生,那么变量bh1。对这两种中断进一步的处理是在110行的ehci_work()函数中。这里暂时先跳过ehci_irq()函数接下来的两个if判断语句,把对STS_INTSTS_ERR中断下一步地处理讲完。

    第109行,如果中断来源是STS_INTSTS_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,二是用timberpolling。这里采用两种方式的原因是提高执行效率,减少interrupt的负担,我们知道在async传输中,如果一次要传输的数据量需要多个qtd才能装载完,那么HCD只会把最后一个qtdIOC位置1,即在最后一个qtd传输完成后才会产生一个中断,如果这时才去处理这些qtd,将会使中断处理的时间过长,并且如果其中某个qtd传输失败也得不到及时的处理,所以会用一个timer来辅助处理已经完成的qtdiTD也做了类似的处理。

    EHCI的整个传输是基于各种descriptor实现的,这些descriptor的可以说就是EHCI的语法,要与一个HC沟通就要按照这些descriptor的语法在内存中组织数据段。函数ehci_work()的工作是要处理HC按照指定的descriptor完成传输后的结果。具体到EHCI的传输类型可以分为asynchronousisochronous两大类,qhqtd的组合常用于asynchronousitd则常用于isochronousAsynchronous的进一步处理入口是在15行的scan_async()函数,isochronous则是在17行的scan_periodic()函数中。这里主要分析前者scan_async()函数。后续的笔记将忽略一些代码上的细节,把重点放在处理的流程上。

 


你可能感兴趣的:(linux,driver,asynchronous,IRQ,EHCI)