Linux那些事儿之我是UHCI(22)Root Hub的中断传输

时间:2022-04-02 15:57:00

来看中断传输,中断传输和下面要讲的等时传输无疑要比之前的那两种传输复杂些,至少它们讲究一个周期性.当年歌坛大姐大那英在看到usb子系统中对这两种传输的实现的复杂性之后,颇为感慨的对写代码的哥们儿唱出了那句就这样被你征服”,而其多年来的老对手田震看了之后心情抑郁,一气之下,嗓子永久性的嘶哑了,但仍然呼吁后人看这些代码的时候要执著”.所以我们看代码的时候看不懂也不用灰心,歌手林志炫的成名曲<<单身情歌>>也就是把自己看代码的亲身经历唱了出来:为了看代码孤军奋斗,早就吃够了看代码的苦,在代码中失落的人到处有,而我只是其中一个.毫无疑问,这首歌唱出了我们看代码的心声,所以歌曲一问世便得到了广大Linux爱好者的追捧并迅速窜红.

和前面那个控制传输一样,中断传输的代码也分为两部分,一个是针对Root Hub,这部分相当简单,另一个是针对非Root Hub,这一部分明显复杂许多.我们先来看Root Hub.从哪里开始看应该不用多说了吧,这些年里,虽然很多事情都已经像沧海变成了桑田,但是咱们跟踪urb的入口依然和<<曲苑杂坛>>的主持人一样,多少年也不会变,依然是usb_submit_urb.

还记得咱们在hub驱动中讲的那个hub_probe?hub驱动的探测过程中,最终咱们会提交一个urb.是一个中断urb.那么咱们来看调用usb_submit_urb()submit这个urb之后究竟会发生什么?

但是与之前控制传输Bulk传输不同的是,之前我们是凌波微步来到了最后一行usb_hcd_submit_urb,而现在在这一行之前还有几行是我们需要关注的.

首先我们注意到243行对临时变量temp赋了值,看到它被赋值为usb_pipetype(pipe),我相信即使是后海的酒托儿也知道,从此以后temp就是管道类型.

于是我们像草上飞一样飞到338,看到这里有一个switch.所有的痛苦都来自选择,所谓幸福,就是没有选择.看到这里你明白为何当初在讲控制传输和Bulk传输的时候我们跳过了这一段了吧,没错,这里只有两个case,PIPE_ISOCHRONOUSPIPE_INTERRUPT,这两个case就是等时管道和中断管道.而控制传输和Bulk传输根本不在这一个选择的考虑范畴之内.所以当时我们很幸福的飘了过去.但现在不行了.实际上这里对于等时传输和对于中断传输,处理方法是一样的.

首先判断urb->interval,我们在hub驱动中已经讲过它的作用,它当然不能小于等于0.

其次根据设备是高速还是全速低速,再一次设置interval.我们当时在hub驱动中也说过,对于高速设备和全速低速设备,这个interval的单位是不一样的.前者的单位是微帧,后者的单位是帧,所以这里对它们有不同的处理方法,但是总的来说,我们可以看到temp无论如何会是2的整数次方,所以372行这么一赋值的效果是,如果你的期待值是介于2n次方和2n+1次方之间,那么我们就把它设置成2n次方.因为最终设置成2的整数次方对我们来说软件上便于实现,而硬件上来说也无所谓,因为usb spec中也是允许的,比如,usb spec 2.0 5.7.4中有这么一段:

The period provided by the system may be shorter than that desired by the device up to the shortest period defined by the USB (125 μs microframe or 1 ms frame). The client software and device can depend only on the fact that the host will ensure that the time duration between two transaction attempts with the endpoint will be no longer than the desired period.

这段话的意思很明确,比如你背着老婆包了一个二奶,她希望你每隔7天去关心她一次,而你如果每隔4天关心她一次那她当然没意见,但如果你每隔10天关心她一次她心里就有想法了.

关于ISO传输的那部分代码咱们后面再看,现在依然先飘过.然后我们就再一次进入了usb_hcd_submit_urb.

而对于Root Hub,我们又再一次进入了rh_urb_enqueue,对于中断传输,我们进入了rh_queue_status.这个函数来自drivers/usb/core/hcd.c:

    599

    600 static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)

    601 {

    602         int             retval;

    603         unsigned long   flags;

    604         int             len = 1 + (urb->dev->maxchild / 8);

    605

    606         spin_lock_irqsave (&hcd_root_hub_lock, flags);

    607         if (urb->status != -EINPROGRESS)        /* already unlinked */

    608                 retval = urb->status;

    609         else if (hcd->status_urb || urb->transfer_buffer_length < len) {

    610                 dev_dbg (hcd->self.controller, "not queuing rh status urb/n");

    611                 retval = -EINVAL;

    612         } else {

    613                 hcd->status_urb = urb;

    614                 urb->hcpriv = hcd;      /* indicate it's queued */

    615

    616                 if (!hcd->uses_new_polling)

    617                         mod_timer (&hcd->rh_timer, jiffies +

    618                                         msecs_to_jiffies(250));

    619

    620                 /* If a status change has already occurred, report it ASAP */

    621                 else if (hcd->poll_pending)

    622                         mod_timer (&hcd->rh_timer, jiffies);

    623                 retval = 0;

    624         }

    625         spin_unlock_irqrestore (&hcd_root_hub_lock, flags);

    626         return retval;

    627 }

还好这个函数不那么变态,由于我们设置了hcd->uses_new_polling1,hcd->poll_pending只有在一个地方被改变,usb_hcd_poll_rh_status(),如果这个函数被调用了而Hub端口处没什么变化,那么poll_pending就会设置为1.但当我们第一次来到这个函数的时候,poll_pending还没有被设定过,所以它只能是0.

假设咱们第一次执行usb_hcd_poll_rh_status的时候,Root Hub的端口确实没有什么信息,即没有连接任何usb设备并且没有任何需要汇报的信息,那么poll_pending就会设置为1.所以下一次当然来到这个函数的时候,622行这个mod_timer会被执行.所以我们将再一次执行usb_hcd_poll_rh_status,并且是立即执行.但关于usb_hcd_poll_rh_status,咱们也没什么好讲的,当初我们已经详细的讲过了.所以基本上我们就知道了,如果Root Hub的端口没有什么改变的话,usb_submit_urbRoot Hub而提交的中断urb也不干什么正经事,我们能看到的是rh_queue_status,rh_urb_enqueue,usb_hcd_submit_urb,usb_submit_urb这四个函数像多米诺骨牌一样一个一个返回0.然后生活还会继续,然后Tomorrow is another day,即使Hub端口里永远不接入任何设备,驱动程序也仍然像沈祥福带超白金一代时冲击雅典奥运会时说过的那句我们还活着”.

不过我最后想提醒一点,由于hub driver中的usb_submit_urb是在hub_probe的过程中被执行的,而这时候实际上咱们正处在register_root_hub,也就是说,咱们是在usb_add_hcd,回过去看这个函数你会发现,1639行调用register_root_hub,1643行调用usb_hcd_poll_rh_status.这也就是说,尽管咱们很早之前就讲过了usb_hcd_poll_rh_status这个函数,但是实际上第一次调用usb_hcd_poll_rh_status发生在rh_queue_status之后.这也就是为什么这里我说第一次进入rh_queue_status的时候,poll_pending的值为0,因为只有调用了usb_hcd_poll_rh_status之后,poll_pending才有可能变成1.