看完了控制传输,咱们来看Bulk传输,Root hub没有Bulk传输,所以咱们只需要关注非Root Hub.

当然还是从usb_submit_urb()开始.和控制传输一样,可以直接跳到usb_hcd_submit_urb().由于我们在start_rh()中设置了hcd->stateHC_STATE_RUNNING,所以这里list_add_tail会被执行,urb会被加入到epurb_list队列中去.

然后还是老套路,driver->urb_enqueue会被执行,即我们又一次进入了uhci_urb_enqueue.没啥好说的,uhci_alloc_urb_privuhci_alloc_qh会再次被执行以申请urbpqh.但这次uhci_submit_control不会被调用了,取而代之的是uhci_submit_bulk().这个函数来自drivers/usb/host/uhci-q.c:

   1043 static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb,

   1044                 struct uhci_qh *qh)

   1045 {

   1046         int ret;

   1047

   1048         /* Can't have low-speed bulk transfers */

   1049         if (urb->dev->speed == USB_SPEED_LOW)

   1050                 return -EINVAL;

   1051

   1052         if (qh->state != QH_STATE_ACTIVE)

   1053                 qh->skel = SKEL_BULK;

   1054         ret = uhci_submit_common(uhci, urb, qh);

   1055         if (ret == 0)

   1056                 uhci_add_fsbr(uhci, urb);

   1057         return ret;

   1058 }

又是一个很赤裸裸的函数,除了设置qh->skelSKEL_BULK以外,就是调用uhci_submit_common,而这个函数也是我们今后将在中断传输中调用的.因为Bulk传输和中断传输一样,就是一个阶段,直接传递数据就可以了,不用那么多废话.然后成功返回的话在调用uhci_add_fsbrurbp->fsbr设置为1.我们来看一下uhci_submit_common(),这是一个公共的函数,Bulk传输和中断传输都会调用它.来自drivers/usb/host/uhci-q.c:

    924 /*

    925  * Common submit for bulk and interrupt

    926  */

    927 static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb,

    928                 struct uhci_qh *qh)

    929 {

    930         struct uhci_td *td;

    931         unsigned long destination, status;

    932         int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);

    933         int len = urb->transfer_buffer_length;

    934         dma_addr_t data = urb->transfer_dma;

    935         __le32 *plink;

    936         struct urb_priv *urbp = urb->hcpriv;

    937         unsigned int toggle;

    938

    939         if (len < 0)

    940                 return -EINVAL;

    941

    942         /* The "pipe" thing contains the destination in bits 8--18 */

    943         destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);

    944         toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe),

    945                          usb_pipeout(urb->pipe));

    946

    947         /* 3 errors, dummy TD remains inactive */

    948         status = uhci_maxerr(3);

    949         if (urb->dev->speed == USB_SPEED_LOW)

    950                 status |= TD_CTRL_LS;

    951         if (usb_pipein(urb->pipe))

    952                 status |= TD_CTRL_SPD;

    953

    954         /*

    955          * Build the DATA TDs

    956          */

    957         plink = NULL;

    958         td = qh->dummy_td;

    959         do {    /* Allow zero length packets */

    960                 int pktsze = maxsze;

961

    962                 if (len <= pktsze) {            /* The last packet */

    963                         pktsze = len;

    964                         if (!(urb->transfer_flags & URB_SHORT_NOT_OK))

    965                                 status &= ~TD_CTRL_SPD;

    966                 }

    967

    968                 if (plink) {

    969                         td = uhci_alloc_td(uhci);

    970                         if (!td)

    971                                 goto nomem;

    972                         *plink = LINK_TO_TD(td);

    973                 }

    974                 uhci_add_td_to_urbp(td, urbp);

    975                 uhci_fill_td(td, status,

    976                                 destination | uhci_explen(pktsze) |

    977                                         (toggle << TD_TOKEN_TOGGLE_SHIFT),

    978                                 data);

    979                 plink = &td->link;

    980                 status |= TD_CTRL_ACTIVE;

    981

    982                 data += pktsze;

    983                 len -= maxsze;

    984                 toggle ^= 1;

    985         } while (len > 0);

    986

    987         /*

    988          * URB_ZERO_PACKET means adding a 0-length packet, if direction

    989          * is OUT and the transfer_length was an exact multiple of maxsze,

    990          * hence (len = transfer_length - N * maxsze) == 0

    991          * however, if transfer_length == 0, the zero packet was already

    992          * prepared above.

    993          */

    994         if ((urb->transfer_flags & URB_ZERO_PACKET) &&

    995                         usb_pipeout(urb->pipe) && len == 0 &&

    996                         urb->transfer_buffer_length > 0) {

    997                 td = uhci_alloc_td(uhci);

    998                 if (!td)

    999                         goto nomem;

   1000                 *plink = LINK_TO_TD(td);

   1001

   1002                 uhci_add_td_to_urbp(td, urbp);

   1003                 uhci_fill_td(td, status,

   1004                                 destination | uhci_explen(0) |

   1005                                         (toggle << TD_TOKEN_TOGGLE_SHIFT),

   1006                                 data);

   1007                 plink = &td->link;

   1008

   1009                 toggle ^= 1;

   1010         }

   1011

   1012         /* Set the interrupt-on-completion flag on the last packet.

   1013          * A more-or-less typical 4 KB URB (= size of one memory page)

   1014          * will require about 3 ms to transfer; that's a little on the

   1015          * fast side but not enough to justify delaying an interrupt

   1016          * more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT

   1017          * flag setting. */

   1018         td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);

   1019

   1020         /*

   1021          * Build the new dummy TD and activate the old one

   1022          */

   1023         td = uhci_alloc_td(uhci);

   1024         if (!td)

   1025                 goto nomem;

   1026         *plink = LINK_TO_TD(td);

   1027

   1028         uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);

   1029         wmb();

   1030         qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);

   1031         qh->dummy_td = td;

   1032

   1033         usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),

   1034                         usb_pipeout(urb->pipe), toggle);

   1035         return 0;

   1036

   1037 nomem:

   1038         /* Remove the dummy TD from the td_list so it doesn't get freed */

   1039         uhci_remove_td_from_urbp(qh->dummy_td);

   1040         return -ENOMEM;

   1041 }

我依稀感觉写代码的简直就是黄世仁,而我则是杨白劳,他们就想逼死我.眼睁睁的看着这代码,却无能为力,找不到读代码的理由,再也感觉不到代码的温柔,任最初的一点思绪消失在世界的尽头.几经曲折之后我终于看明白了这个函数,虽然这个函数很无耻,但是它却给了我们一丝亲切的感觉,我们曾经熟悉的urb,曾经熟悉的transfer_buffer_length以及transfer_dma又一次映入了我们的眼帘.这里我们看到它们俩被赋给了lendata.

932,maxsze等于端点描述符里记录的wMaxPacketSize,即包的最大size.

接下来又是一堆的赋值.

第一个,destination,urb->pipe由几个部分组成,这里的两个宏无非就是提取其中的destination,它们都来自drivers/usb/host/uhci-hcd.h:

      7 #define usb_packetid(pipe)      (usb_pipein(pipe) ? USB_PID_IN : USB_PID_OUT)

      8 #define PIPE_DEVEP_MASK         0x0007ff00

显然,PIPE_DEVEP_MASK就是用来获取bit8bit18,usb_packtid就是为了获得传输的方向,IN还是OUT,usb_pipein就是获取pipebit7.当初我们在usb storage里面已经很彻底的分析了一个pipe的组成.所以这里不难理解这些位操作的目的了.

第二个,toggle,这个也是咱们当年在usb-storage中讲过的,usb_gettoggle就是获得这个toggle.

第三个,status,等式右边的uhci_maxerr来自drivers/usb/host/uhci-hcd.h:

    203 #define uhci_maxerr(err)                ((err) << TD_CTRL_C_ERR_SHIFT)

在同一个文件中还定义了这样一些宏:

    177 /*

    178  *      Transfer Descriptors

    179  */

    180

    181 /*

    182  * for TD <status>:

    183  */

    184 #define TD_CTRL_SPD             (1 << 29)       /* Short Packet Detect */

    185 #define TD_CTRL_C_ERR_MASK      (3 << 27)       /* Error Counter bits */

    186 #define TD_CTRL_C_ERR_SHIFT     27

    187 #define TD_CTRL_LS              (1 << 26)       /* Low Speed Device */

    188 #define TD_CTRL_IOS             (1 << 25)       /* Isochronous Select */

    189 #define TD_CTRL_IOC             (1 << 24)       /* Interrupt on Complete */

    190 #define TD_CTRL_ACTIVE          (1 << 23)       /* TD Active */

    191 #define TD_CTRL_STALLED         (1 << 22)       /* TD Stalled */

    192 #define TD_CTRL_DBUFERR         (1 << 21)       /* Data Buffer Error */

    193 #define TD_CTRL_BABBLE          (1 << 20)       /* Babble Detected */

    194 #define TD_CTRL_NAK             (1 << 19)       /* NAK Received */

    195 #define TD_CTRL_CRCTIMEO        (1 << 18)       /* CRC/Time Out Error */

    196 #define TD_CTRL_BITSTUFF        (1 << 17)       /* Bit Stuff Error */

    197 #define TD_CTRL_ACTLEN_MASK     0x7FF   /* actual length, encoded as n - 1 */

    198

    199 #define TD_CTRL_ANY_ERROR       (TD_CTRL_STALLED | TD_CTRL_DBUFERR | /

    200                                  TD_CTRL_BABBLE | TD_CTRL_CRCTIME | /

    201                                  TD_CTRL_BITSTUFF)

这些宏看似很莫名其妙,其实是有来历的,UHCI spec中对TD有明确的描述,硬件上来看,它一共有四个双字(DWORD).这其中第二个双字被称为TD CONTROL AND STATUS,就是专门记录控制和状态信息的,一个双字就是32bits,bit0bit31.而其中咱们这里的TD_CTRL_C_ERR_MASK就是为了提取bit28bit27.Spec中说,这两位是一个计数器,记录的是这个TD在执行过程中被探测到出现错误的次数,比如你一开始设置它为3,那么它每次出现错误就减一,三次错误之后这个计数器就从一变成了零,于是主机控制器就会把这个TD设置为inactive,即给这个TD宣判死刑.咱们这里设置的就是3.

接下来几行还是设置这个status,statusbit26标志着这个设备是低速设备还是全速设备.如果为1则表示是低速设备,0则表示是全速设备.bit29表示Short Packet Detect(SPD),其含义为,如果这一位为1,则当一个包是输入包,并且成功的完成了传输但是实际长度比最大长度要短,则这个TD将会被标为inactive.如果是输出包,则这一位没有任何意义.所以这里判断的是这个管道是不是输入管道.另外,如果传输出现了错误,则这一位也没有任何意义,汇报SPD的前提是数据必须成功的被传输了.

在做好这些前奏工作之后,957行开始干正经事了.

如果传输的长度比pktsze,或者说小于maxsze,则说明这个包是最后一个包了.URB_SHORT_NOT_OKurbtransfer_flags中众多个标志位的一个,如果设置了这一个flag就表明short包是不能够接受的.反之则说明确实是一个短包,这种情况就把statusSPD这一位给清掉.

接着看,plink一开始被设置为NULL.所以第一次进入循环的话就直接执行974,uhci_add_td_to_urbp(),

然后调用uhci_fill_td,这个函数咱们已经讲过了.它无非就是设置一个tdstatus,tokenbuffer这三个成员.

设置了td之后,plink等于td->link,tdlink也是uhci spec明确规定的4DWORD之一,被称为Link Pointer,物理上,正是它把各个TD给连接起来的.

设置好这些之后,再把statusbit23给设置为1,这一位如果为1,则表示enable这个传输了.TD_CTRL_ACTIVE这一位用来表征TD是一个待执行的活跃交互,主机控制器驱动在调度一个交互请求的时候将这一位设成1,而硬件(主机控制器)在完成了一次交互之后,或者成功,或者彻底失败,就将这一位改成0.这样驱动程序只要扫描各个uhci_td数据结构,发现某个uhci_td数据结构的TD_CTRL_ACTIVE位变成了0,就说明这个交互已经完成.

最后增加data,减小len,并且把toggle位置反.如果数据还没传输完,就开始下一轮的循环.

第二次循环的区别在于plink这时候已经有值了,所以这次969uhci_alloc_td会被执行,这次就将申请一个td.然后让plink里边的内容赋为这个tddma地址,这样就把这个td和之前的td给连接了起来.而其它的事情则和第一次循环的时候一样.

不过网友善解人衣问了这么一个问题,这里貌似有两个队列呀,一个是td->list,一个是td->link,这是什么原因?我们看到struct uhci_td,一个是__le32 link,一个是struct list_head list,后者就是一个经典的队列头,而前者是一个链接指针,实际上它们构成了两个队列,或者说两个链表,前者使用的物理地址,后者使用的是虚拟地址.因为USB主机控制器显然不认识虚拟地址,关于物理地址和虚拟地址,主机控制器的心声是我的心里只有你没有她.所以我们要让USB主机控制器能够顺着各个TD来执行,就得为它准备一个物理地址链接起来的队列,但是同时,从软件角度来说,要保证CPU能够访问各个TD,则又必须以虚拟地址的方式组建一个队列,从而使得CPU可以对uhci_td数据结构进行常规的队列操作.所以,在我们的故事中出现了两个队列. uhci_submit_common函数结束后,各个td就组成了一个qh.

这个循环结束之后,主机控制器的驱动的工作就算是完成了,我们知道处理器的基本职责是取指令和执行指令,类似的,uhci主机控制器的基本职责就是取TD和执行TD,这里因为TD也建好了,也连入该连接的地方了,剩下的具体执行就是硬件的事情了.你尽管放心,如果硬件连这点事情都做不好,那么我们复旦大学微电子系那个所谓的专用集成电路与系统国家重点实验室就可以关闭了.

其实这个建立TD队列的过程是很简单的,反反复复的就是在调用这三个函数uhci_alloc_td, uhci_add_td_to_urbp,uhci_fill_td.其意图很明显,基本上就是三步走,申请td,将其加入大部队,填充好.其中每一次调用了uhci_alloc_td之后都要判断是否申请成功,如果不成功就直接goto nomem.正如我们曾经说过的,内存对设备驱动的重要性就好比房子对我们谈婚论嫁的重要性,这年头,女孩子找对象的基本要求是,有车有房,父母双亡,床上豺狼,床下绵羊.都说婚姻是爱情的坟墓,可是如果没有房子,你连坟墓都进不去!

然后还有一些细节的工作.994,判断urb的另一个transfer_flags,URB_ZERO_PACKET是否设置了,如果设置了,并且传输方向是输出,而且len等于0,并且需要传输的数据长度是大于0,(这说明最初len并不是0,而现在是0,即说明transfer_buffer_length的长度恰好等于整数个maxsze.)这个flag的含义是这个传输最后需要有一个零长度的包.对于这种情况,没啥好说的,申请一个td,连接好,填充好,然后把toggle位置反,ok.

1018,设置statusbit24.这一位被称为Interrupt on Complete(IOC).这一位如果为1,则表示主机控制器会在这个TD执行的frame结束的时候触发中断.当初咱们在uhci_submit_control中也给状态阶段的TD设置了这一位.

再接下来的这一小段代码基本上就是处理那个dummy_td.当年咱们在uhci_alloc_qh中曾经刻意为qh->dummy_td给申请了空间.这个td是用来结束一个队列的,或者说它表征队列的结束.

这里结束之后,这个函数就结束了,返回0.只有刚才申请td的时候失败了才会跳到下面去执行uhci_remove_td_from_urbp(),dummy_tdtd_list中删除.这个函数也是粉简单的,来自drivers/usb/host/uhci-q.c:

    151 static void uhci_remove_td_from_urbp(struct uhci_td *td)

    152 {

    153         list_del_init(&td->list);

    154 }

然后,uhci_submit_common结束之后我们回到uhci_submit_bulk,并进而回到uhci_urb_enqueue,而剩下的代码和控制传输就一样了,无需多说.这样,传说中的Bulk传输就这么被我们轻松搞定了.同样,在数据真的执行完之后,urbcomplete函数会被执行,控制权会转移给设备驱动去.

这一切听上去都很完美,似乎天衣无缝,可问题是,不管是之前说的控制传输还是现在说的Bulk传输,urb->complete究竟是被谁调用的?前面在讲Root Hub的时候咱们看到了usb_hcd_giveback_urb被调用,而它会调用urb->complete.那么对于非Root hub?

还记得咱们注册了中断函数吧?中断函数不会吃闲饭,咱们为控制传输和Bulk传输中的最后一个TD设置了IOC,于是该TD完成之后的那个Frame结束时分,主机控制器会向CPU发送中断,于是中断函数会被调用.

 
Logo

更多推荐