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

来源:互联网 发布:巨人网络借壳a股上市 编辑:程序博客网 时间:2024/03/29 08:58

先上代码,

1. static void scan_async (struct ehci_hcd *ehci)

2. {

3.  struct ehci_qh *qh;

4.  bool check_unlinks_later = false;

5. 

6.  ehci->qh_scan_next = ehci->async->qh_next.qh;

7.  while (ehci->qh_scan_next) {

8.  qh = ehci->qh_scan_next;

9.  ehci->qh_scan_next = qh->qh_next.qh;

10. 

11.  /* clean any finished work for this qh */

12.  if (!list_empty(&qh->qtd_list)) {

13.  int temp;

14. 

15.  /*

16.   * Unlinks could happen here; completion reporting

17.   * drops the lock.  That's why ehci->qh_scan_next

18.   * always holds the next qh to scan; if the next qh

19.   * gets unlinked then ehci->qh_scan_next is adjusted

20.   * in single_unlink_async().

21.   */

22.  temp = qh_completions(ehci, qh);

23.  if (unlikely(temp)) {

24.  start_unlink_async(ehci, qh);

25.  } else if (list_empty(&qh->qtd_list)

26.  && qh->qh_state == QH_STATE_LINKED) {

27.  qh->unlink_cycle = ehci->async_unlink_cycle;

28.  check_unlinks_later = true;

29.  }

30.  }

31.  }

32. 

33.  /*

34.   * Unlink empty entries, reducing DMA usage as well

35.   * as HCD schedule-scanning costs.  Delay for any qh

36.   * we just scanned, there's a not-unusual case that it

37.   * doesn't stay idle for long.

38.   */

39.  if (check_unlinks_later && ehci->rh_state == EHCI_RH_RUNNING &&

40.  !(ehci->enabled_hrtimer_events &

41.  BIT(EHCI_HRTIMER_ASYNC_UNLINKS))) {

42.  ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true);

43.  ++ehci->async_unlink_cycle;

44.  }

45. }

46. 

    Asynchronous传输在EHCI中由qhqtd来完成的,从通信模型的抽象层面上来说,qhqtd分别代表了通信的一方,qh代表USB device方(准确的说是device上的endpoint),qtd代表CPU方(实际上是某块DMA内存区域)。分配给device endpointqh是伴随deviceHCD中一直存在的,只要device没有被拔出,为device endpoint分配的qh就是固定的;相对而言CPU方的用于数据传输的内存区域却是变化的,这也是显而易见的,所以在完成数据传输后分配的qtd会被回收。而scan_async()函数的工作就是去check传输的状况,并回收qtd

    首先了解一下EHCI asynchronous传输是怎样利用qhqtd来完成数据传输的。如图1所示,CPU通过由EHCI HC提供的一组register来与之交互,qhqtd是存在于内存中的数据段,HC需要从内存中将之读取到它的缓存中,HC正是通过EHCI提供的这组register中的AsyncListAddr获取到内存中qh的地址的。具体的过程是,CPU先把多个qh组成的一个循环队列头qh的物理地址写入AsyncListAddr中,HC再根据AsyncListAddr的值去内存寻址qh的所在位置。构成一个qh循环队列是通过qh结构体中的头四个字节,去指向下一个qh的物理地址实现的,并且最后一个qh将指回首个qh,而qtd则使依附在qh中的,即HC通过AsyncListAddr找到qh,在通过qh找到qtd。更详细的细节可参考EHCI spec

 

1

    Linuxehci driverqh的组织方式是先分配一个不依附于任何endpointqh数据结构,作为qh循环队列的头,这个qh不用于实际的数据传输,只用于寻址下一个qh,这个qh的物理地址写入AsyncListAddr,如图1所示,并把qh的虚拟地址保存在ehci->async中。

    回到qtd回收的讨论中来,scan_async()函数中第25行的qh_completions()函数完成了真正的qtd扫尾工作,那就先从这个qh_completions()说起,代码如下。

1. /*

2.  * Process and free completed qtds for a qh, returning URBs to drivers.

3.  * Chases up to qh->hw_current.  Returns nonzero if the caller should

4.  * unlink qh.

5.  */

6. static unsigned

7. qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)

8. {

9.  struct ehci_qtd *last, *end = qh->dummy;

10.  struct list_head *entry, *tmp;

11.  int last_status;

12.  int stopped;

13.  u8 state;

14.  struct ehci_qh_hw *hw = qh->hw;

15. 

16.  /* completions (or tasks on other cpus) must never clobber HALT

17.   * till we've gone through and cleaned everything up, even when

18.   * they add urbs to this qh's queue or mark them for unlinking.

19.   *

20.   * NOTE:  unlinking expects to be done in queue order.

21.   *

22.   * It's a bug for qh->qh_state to be anything other than

23.   * QH_STATE_IDLE, unless our caller is scan_async() or

24.   * scan_intr().

25.   */

26.  state = qh->qh_state;

27.  qh->qh_state = QH_STATE_COMPLETING;

28.  stopped = (state == QH_STATE_IDLE);

29. 

30.  rescan:

31.  last = NULL;

32.  last_status = -EINPROGRESS;

33.  qh->dequeue_during_giveback = 0;

34. 

35.  /* remove de-activated QTDs from front of queue.

36.   * after faults (including short reads), cleanup this urb

37.   * then let the queue advance.

38.   * if queue is stopped, handles unlinks.

39.   */

40.  list_for_each_safe (entry, tmp, &qh->qtd_list) {

41.  struct ehci_qtd *qtd;

42.  struct urb *urb;

43.  u32 token = 0;

44. 

45.  qtd = list_entry (entry, struct ehci_qtd, qtd_list);

46.  urb = qtd->urb;

47. 

48.  /* clean up any state from previous QTD ...*/

49.  if (last) {

50.  if (likely (last->urb != urb)) {

51.  ehci_urb_done(ehci, last->urb, last_status);

52.  last_status = -EINPROGRESS;

53.  }

54.  ehci_qtd_free (ehci, last);

55.  last = NULL;

56.  }

57. 

58.  /* ignore urbs submitted during completions we reported */

59.  if (qtd == end)

60.  break;

61. 

62.  /* hardware copies qtd out of qh overlay */

63.  rmb ();

64.  token = hc32_to_cpu(ehci, qtd->hw_token);

65. 

66.  /* always clean up qtds the hc de-activated */

67.  retry_xacterr:

68.  if ((token & QTD_STS_ACTIVE) == 0) {

69. 

70.  /* Report Data Buffer Error: non-fatal but useful */

71.  if (token & QTD_STS_DBE)

72.  ehci_dbg(ehci,

73.  "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n",

74.  urb,

75.  usb_endpoint_num(&urb->ep->desc),

76.  usb_endpoint_dir_in(&urb->ep->desc) ? "in" : "out",

77.  urb->transfer_buffer_length,

78.  qtd,

79.  qh);

80. 

81.  /* on STALL, error, and short reads this urb must

82.   * complete and all its qtds must be recycled.

83.   */

84.  if ((token & QTD_STS_HALT) != 0) {

85. 

86.  /* retry transaction errors until we

87.   * reach the software xacterr limit

88.   */

89.  if ((token & QTD_STS_XACT) &&

90.  QTD_CERR(token) == 0 &&

91.  ++qh->xacterrs < QH_XACTERR_MAX &&

92.  !urb->unlinked) {

93.  ehci_dbg(ehci,

94.  "detected XactErr len %zu/%zu retry %d\n",

95.  qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs);

96. 

97.  /* reset the token in the qtd and the

98.   * qh overlay (which still contains

99.   * the qtd) so that we pick up from

100.   * where we left off

101.   */

102.  token &= ~QTD_STS_HALT;

103.  token |= QTD_STS_ACTIVE |

104.  (EHCI_TUNE_CERR << 10);

105.  qtd->hw_token = cpu_to_hc32(ehci,

106.  token);

107.  wmb();

108.  hw->hw_token = cpu_to_hc32(ehci,

109.  token);

110.  goto retry_xacterr;

111.  }

112.  stopped = 1;

113. 

114.  /* magic dummy for some short reads; qh won't advance.

115.   * that silicon quirk can kick in with this dummy too.

116.   *

117.   * other short reads won't stop the queue, including

118.   * control transfers (status stage handles that) or

119.   * most other single-qtd reads ... the queue stops if

120.   * URB_SHORT_NOT_OK was set so the driver submitting

121.   * the urbs could clean it up.

122.   */

123.  } else if (IS_SHORT_READ (token)

124.  && !(qtd->hw_alt_next

125.  & EHCI_LIST_END(ehci))) {

126.  stopped = 1;

127.  }

128. 

129.  /* stop scanning when we reach qtds the hc is using */

130.  } else if (likely (!stopped

131.  && ehci->rh_state >= EHCI_RH_RUNNING)) {

132.  break;

133. 

134.  /* scan the whole queue for unlinks whenever it stops */

135.  } else {

136.  stopped = 1;

137. 

138.  /* cancel everything if we halt, suspend, etc */

139.  if (ehci->rh_state < EHCI_RH_RUNNING)

140.  last_status = -ESHUTDOWN;

141. 

142.  /* this qtd is active; skip it unless a previous qtd

143.   * for its urb faulted, or its urb was canceled.

144.   */

145.  else if (last_status == -EINPROGRESS && !urb->unlinked)

146.  continue;

147. 

148.  /*

149.   * If this was the active qtd when the qh was unlinked

150.   * and the overlay's token is active, then the overlay

151.   * hasn't been written back to the qtd yet so use its

152.   * token instead of the qtd's.  After the qtd is

153.   * processed and removed, the overlay won't be valid

154.   * any more.

155.   */

156.  if (state == QH_STATE_IDLE &&

157.  qh->qtd_list.next == &qtd->qtd_list &&

158.  (hw->hw_token & ACTIVE_BIT(ehci))) {

159.  token = hc32_to_cpu(ehci, hw->hw_token);

160.  hw->hw_token &= ~ACTIVE_BIT(ehci);

161. 

162.  /* An unlink may leave an incomplete

163.   * async transaction in the TT buffer.

164.   * We have to clear it.

165.   */

166.  ehci_clear_tt_buffer(ehci, qh, urb, token);

167.  }

168.  }

169. 

170.  /* unless we already know the urb's status, collect qtd status

171.   * and update count of bytes transferred.  in common short read

172.   * cases with only one data qtd (including control transfers),

173.   * queue processing won't halt.  but with two or more qtds (for

174.   * example, with a 32 KB transfer), when the first qtd gets a

175.   * short read the second must be removed by hand.

176.   */

177.  if (last_status == -EINPROGRESS) {

178.  last_status = qtd_copy_status(ehci, urb,

179.  qtd->length, token);

180.  if (last_status == -EREMOTEIO

181.  && (qtd->hw_alt_next

182.  & EHCI_LIST_END(ehci)))

183.  last_status = -EINPROGRESS;

184. 

185.  /* As part of low/full-speed endpoint-halt processing

186.   * we must clear the TT buffer (11.17.5).

187.   */

188.  if (unlikely(last_status != -EINPROGRESS &&

189.  last_status != -EREMOTEIO)) {

190.  /* The TT's in some hubs malfunction when they

191.   * receive this request following a STALL (they

192.   * stop sending isochronous packets).  Since a

193.   * STALL can't leave the TT buffer in a busy

194.   * state (if you believe Figures 11-48 - 11-51

195.   * in the USB 2.0 spec), we won't clear the TT

196.   * buffer in this case.  Strictly speaking this

197.   * is a violation of the spec.

198.   */

199.  if (last_status != -EPIPE)

200.  ehci_clear_tt_buffer(ehci, qh, urb,

201.  token);

202.  }

203.  }

204. 

205.  /* if we're removing something not at the queue head,

206.   * patch the hardware queue pointer.

207.   */

208.  if (stopped && qtd->qtd_list.prev != &qh->qtd_list) {

209.  last = list_entry (qtd->qtd_list.prev,

210.  struct ehci_qtd, qtd_list);

211.  last->hw_next = qtd->hw_next;

212.  }

213. 

214.  /* remove qtd; it's recycled after possible urb completion */

215.  list_del (&qtd->qtd_list);

216.  last = qtd;

217. 

218.  /* reinit the xacterr counter for the next qtd */

219.  qh->xacterrs = 0;

220.  }

221. 

222.  /* last urb's completion might still need calling */

223.  if (likely (last != NULL)) {

224.  ehci_urb_done(ehci, last->urb, last_status);

225.  ehci_qtd_free (ehci, last);

226.  }

227. 

228.  /* Do we need to rescan for URBs dequeued during a giveback? */

229.  if (unlikely(qh->dequeue_during_giveback)) {

230.  /* If the QH is already unlinked, do the rescan now. */

231.  if (state == QH_STATE_IDLE)

232.  goto rescan;

233. 

234.  /* Otherwise the caller must unlink the QH. */

235.  }

236. 

237.  /* restore original state; caller must unlink or relink */

238.  qh->qh_state = state;

239. 

240.  /* be sure the hardware's done with the qh before refreshing

241.   * it after fault cleanup, or recovering from silicon wrongly

242.   * overlaying the dummy qtd (which reduces DMA chatter).

243.   *

244.   * We won't refresh a QH that's linked (after the HC

245.   * stopped the queue).  That avoids a race:

246.   *  - HC reads first part of QH;

247.   *  - CPU updates that first part and the token;

248.   *  - HC reads rest of that QH, including token

249.   * Result:  HC gets an inconsistent image, and then

250.   * DMAs to/from the wrong memory (corrupting it).

251.   *

252.   * That should be rare for interrupt transfers,

253.   * except maybe high bandwidth ...

254.   */

255.  if (stopped != 0 || hw->hw_qtd_next == EHCI_LIST_END(ehci))

256.  qh->exception = 1;

257. 

258.  /* Let the caller know if the QH needs to be unlinked. */

259.  return qh->exception;

260. }

    函数qh_completions()比较长,但几个if-else的判断语句占了大部分,qh_completions()中通过对qh下链接的qtd进行逐个遍历,来判断传输的情况,HC会把传输的实际状况值回写到qtd中的token字段的status中,Status共有8位,每一位都单独代表一种传输状态,除第7位外均代表了一种出错状态。所以对这样的一段if-else语句,如果数据传输中没有出错,大部分的代码都是会被bypass掉的。

    还是从头讲起吧,第40行的list_for_each_safe中对qtd_list进行遍历,其中函数qh_completions()的参数qh上关联了一个qtd队列,list_for_each_safe逐个的把qh上的qtd取出放在指针entry中,list_for_each_safe的特点是可以中途删除entry,通过指针tmp去找到下一个entry

    第44行,list_entry得到entry所指qtd的指针地址,放在指针变量qtd,通过这样几步就找出了qtd的数据结构实际在内存中的地址。得到这些qtd的实际所在后,就要根据其的实际情况进行处理。

    第47行,指针last的初始值为NULL,首次执行到这里时,直接跳过整个if语句,当再一次执行到此处时,如果前一次的处理中有qtd是执行完传输的(包括传输出错),last此时就会指向了前一个qtd,并在if语句中的ehci_qtd_free()函数中把分配的qtd空间释放掉。由多个qtd形成的队列是一个单向链表,链表的末尾的qtd被设置成不可用的状态,表示传输的完结点,可以通过qh->dummy指针找到这个qtd,即这个qtd是一个dummy,实际的内容为空,用于标记qtd链表的结束。一个qtd链表中的urb指针的指向都是相同的,除了最末这一个dummy qtd,所以在遍历到最后的qtd时“last->urb != urb”满足。49ehci_urb_done()会被调用,ehci_urb_done()要做的一件事是回调urb->complete()函数指针,从而使控制权回到USB device Driver中,这就是我们填充一个urb的回调函数的触发处。57行的判断遍历到最后的dummy qtd,就跳出循环,表明整个qtd链表已被处理完了。

    前面讲过,HC在处理完一个qtd后,反映处理结果的值会回写到当前qtdtoken字段中,HCD读取这个tokenStatus值后,可以获知HC的传输情况。第61行把qtdtoken值暂存到变量token中。Token[7:0]位是状态值,其中第7位为Active位,由HCD1,表明qtd处于激活状态,这样HC可能正在处理该qtd,或准备处理。当传输完成或出错后,Active位由HC0。如果执行该段代码是由中断触发的,那么Active位一定为0,即有一次传输结束。如果是由timer polling执行到此处,那么Active位的值就可能为01,即不一定有传输完成。这里假设捕获到一次qtd的传输完成了,即Active位等于0,看看接下来的处理。

    第66行处,是对Data Buffer Error的处理,token5位被置1,表明发生了这样的错误。这样的错误,在EHCI SPEC里说,不被视作传输错误,会强制endpoint重发一次,所以代码也只是做了打印。

    如果有一次qtd的传输完成,Active位被置为0,进入64行的if语句内部。接下来就是一个检测处理了,token的第6位为Halted位,当该位被HC1时,就表明当前qtd的传输出现了错误,而且与该endpoint的传输都被停掉,同时Active位会被置0Halted位被置1,不能确切的知道错误的类型和原因,token3位进一步指明了halted的原因。Token的第3XactErr当被HC1时,表明HC没有收到device发回的有效应答包,这时HC的传输已经停下来了,传输出错的qtd依然保留在qhoverlay中,出现这样的错误HCD的处理方式是,由软件把Halted位清零,token[11:10] CERR位设为0x3Active位置1再次使能该qtd,让HC重新传输这个qtd。第102行的goto语句返回到retry_xacterr处,重复以上动作,直到传输成功为止。

    如前面的假设Active位为1,那么接下来的else-if-else代码段,就可以忽略了,这两段代码checkHC的运行状态,120-122行检测到HC正在running,所以什么都不做,跳出循环。

    第153行,变量last_status通过qtd_copy_status()获取到,前面代码对一次qtd的传输后的状态处理是针对halted的,在函数qtd_copy_status()中会把实际传输的数据长度更新到urb->actual_length中,如果一个qtd上的数据只完成了部分的传输,即实际传输的数据量小于要求的传输量,那么会被视为一个SHORT错误,qtd_copy_status()会返回这些错误值。

    在182行处的变量stopped104行赋值为1,指针qtd如果指向qtd链表的首个qtd,那么qtd->qtd_list.prev 就指向了qh->qtd_list,qtd链表上的传输要按这样的链表上的顺序依次传输,一旦qtd上的传输完成了,也应该是依次被从链表上remove,被移除的qtd的下一个qtd就被挪到链表首,如果将要从链表上移除的qtd的前一项未指向链表头,而是另一个qtd,这里要把这前一个qtd-> hw_next等于当前要被移除qtd-> hw_next的值。由多个qtd构成的链表,在HC看来是通过物理地址相连的单向链表,在HCD看来是一个双向的链表。

    在188行处把qtd从链表队列中删除,指针last此时就指向这个被移除的qtd,如果qtd不是链表的末项,再一次的循环时47行处qtd被真正的回收。

    前面讲过整个遍历会从58break语句退出,即遍历到dummy qtd后,结束退出。从193行开始,如果指针last非空,那么一定是指向一个qtd链表队列的末尾处(非dummy qtd,前面提到过一个qtd链表队列,构成了一个完整的逻辑传输,整个qtd list被传输完毕后,就可以通知usb device driver即调用urb->complete()回调函数了。最再将这个qtd的空间回收。

    在这里整个qh_completions()函数就差不多讲完了,后面的两个if判断语句,一个是关于再次遍历的问题,另一个是对qh进行refresh。关于qh_refresh()函数可以参考前面的文章。最后qh_completions()函数返回count值,如果成功的完成了一次qtd队列的处理,count返回1

    对qh_completions()做一个小结,对qh上连接的qtd队列,在完成传输后,对qtd项做后续的处理。

 

    现在回到函数scan_async()中,刚才把最长的qh_completions()函数讲完了,现在来讲scan_async()函数就轻松多了。在贴一次scan_async()的代码。

static void scan_async (struct ehci_hcd *ehci)

{

bool stopped;

struct ehci_qh *qh;

enum ehci_timer_action action = TIMER_IO_WATCHDOG;

 

timer_action_done (ehci, TIMER_ASYNC_SHRINK);

stopped = (ehci->rh_state != EHCI_RH_RUNNING);

 

ehci->qh_scan_next = ehci->async->qh_next.qh;

while (ehci->qh_scan_next) {

qh = ehci->qh_scan_next;

ehci->qh_scan_next = qh->qh_next.qh;

 rescan:

/* clean any finished work for this qh */

if (!list_empty(&qh->qtd_list)) {

int temp;

 

/*

 * Unlinks could happen here; completion reporting

 * drops the lock.  That's why ehci->qh_scan_next

 * always holds the next qh to scan; if the next qh

 * gets unlinked then ehci->qh_scan_next is adjusted

 * in start_unlink_async().

 */

qh = qh_get(qh);

temp = qh_completions(ehci, qh);

if (qh->needs_rescan)

unlink_async(ehci, qh);

qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES;

qh_put(qh);

if (temp != 0)

goto rescan;

}

 

/* unlink idle entries, reducing DMA usage as well

 * as HCD schedule-scanning costs.  delay for any qh

 * we just scanned, there's a not-unusual case that it

 * doesn't stay idle for long.

 * (plus, avoids some kind of re-activation race.)

 */

if (list_empty(&qh->qtd_list)

&& qh->qh_state == QH_STATE_LINKED) {

if (!ehci->reclaim && (stopped ||

time_after_eq(jiffies, qh->unlink_time)))

start_unlink_async(ehci, qh);

else

action = TIMER_ASYNC_SHRINK;

}

}

if (action == TIMER_ASYNC_SHRINK)

timer_action (ehci, TIMER_ASYNC_SHRINK);

}

    前面在分析qh_completions()时,知道其是对qtd链表队列进行遍历逐项处理,而这些qtd队列是与某个qh相关联的。类似scan_async()是对qh构成的队列进行遍历,逐个处理qh。如图1右侧所示,即是qh构成的链表。在内存中qhqtd的组成的数据结构,应是图2所示的连接结构,最后的一个qhnext应该指向头个qh,形成一个循环,如图1,这里没有画出来。图中的scan_async箭头,表明了scan_async()函数负责qh的处理,箭头qh_completions是对qtd的处理。

2

    结合图2来表述scan_async()的流程,来qh的循环队列中,依次取出各个qh,并把qh上的qtd_list交给qh_completions()处理。

    函数scan_async()的代码分析就到此为止了。

 

原文:http://blog.csdn.net/yaongtime/article/details/19755143

0 0