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

Linux那些事儿之我是UHCI(20)非Root Hub的Bulk传输

2007-11-12 16:09 1076 查看
看完了控制传输,咱们来看Bulk传输,Root hub没有Bulk传输,所以咱们只需要关注非Root Hub.
当然还是从usb_submit_urb()开始.和控制传输一样,可以直接跳到usb_hcd_submit_urb().由于我们在start_rh()中设置了hcd->state为HC_STATE_RUNNING,所以这里list_add_tail会被执行,本urb会被加入到ep的urb_list队列中去.
然后还是老套路,driver->urb_enqueue会被执行,即我们又一次进入了uhci_urb_enqueue.没啥好说的,uhci_alloc_urb_priv和uhci_alloc_qh会再次被执行以申请urbp和qh.但这次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->skel为SKEL_BULK以外,就是调用uhci_submit_common了,而这个函数也是我们今后将在中断传输中调用的.因为Bulk传输和中断传输一样,就是一个阶段,直接传递数据就可以了,不用那么多废话.然后成功返回的话在调用uhci_add_fsbr把urbp->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又一次映入了我们的眼帘.这里我们看到它们俩被赋给了len和data.
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就是用来获取bit8到bit18,而usb_packtid就是为了获得传输的方向,IN还是OUT,usb_pipein就是获取pipe的bit7.当初我们在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,就是专门记录控制和状态信息的,一个双字就是32个bits,bit0到bit31.而其中咱们这里的TD_CTRL_C_ERR_MASK就是为了提取bit28和bit27的.Spec中说,这两位是一个计数器,记录的是这个TD在执行过程中被探测到出现错误的次数,比如你一开始设置它为3,那么它每次出现错误就减一,三次错误之后这个计数器就从一变成了零,于是主机控制器就会把这个TD设置为inactive,即给这个TD宣判死刑.咱们这里设置的就是3次.
接下来几行还是设置这个status,status的bit26标志着这个设备是低速设备还是全速设备.如果为1则表示是低速设备,为0则表示是全速设备.bit29表示Short Packet Detect(SPD),其含义为,如果这一位为1,则当一个包是输入包,并且成功的完成了传输但是实际长度比最大长度要短,则这个TD将会被标为inactive.如果是输出包,则这一位没有任何意义.所以这里判断的是这个管道是不是输入管道.另外,如果传输出现了错误,则这一位也没有任何意义,汇报SPD的前提是数据必须成功的被传输了.
在做好这些前奏工作之后,957行开始干正经事了.
如果传输的长度比pktsze,或者说小于maxsze,则说明这个包是最后一个包了.URB_SHORT_NOT_OK是urb的transfer_flags中众多个标志位的一个,如果设置了这一个flag就表明short包是不能够接受的.反之则说明确实是一个短包,这种情况就把status中SPD这一位给清掉.
接着看,plink一开始被设置为NULL.所以第一次进入循环的话就直接执行974行,uhci_add_td_to_urbp(),
然后调用uhci_fill_td,这个函数咱们已经讲过了.它无非就是设置一个td的status,token和buffer这三个成员.
设置了td之后,令plink等于td->link,td的link也是uhci spec明确规定的4个DWORD之一,被称为Link Pointer的,物理上,正是它把各个TD给连接起来的.
设置好这些之后,再把status中bit23给设置为1,这一位如果为1,则表示enable这个传输了.TD_CTRL_ACTIVE这一位用来表征TD是一个待执行的活跃交互,主机控制器驱动在调度一个交互请求的时候将这一位设成1,而硬件(主机控制器)在完成了一次交互之后,或者成功,或者彻底失败,就将这一位改成0.这样驱动程序只要扫描各个uhci_td数据结构,发现某个uhci_td数据结构的TD_CTRL_ACTIVE位变成了0,就说明这个交互已经完成.
最后增加data,减小len,并且把toggle位置反.如果数据还没传输完,就开始下一轮的循环.
第二次循环的区别在于plink这时候已经有值了,所以这次969行uhci_alloc_td会被执行,这次就将申请一个td.然后让plink里边的内容赋为这个td的dma地址,这样就把这个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行,设置status的bit24.这一位被称为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_td从td_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传输就这么被我们轻松搞定了.同样,在数据真的执行完之后,urb的complete函数会被执行,控制权会转移给设备驱动去.
这一切听上去都很完美,似乎天衣无缝,可问题是,不管是之前说的控制传输还是现在说的Bulk传输,urb->complete究竟是被谁调用的?前面在讲Root Hub的时候咱们看到了usb_hcd_giveback_urb被调用,而它会调用urb->complete.那么对于非Root hub呢?
还记得咱们注册了中断函数吧?中断函数不会吃闲饭,咱们为控制传输和Bulk传输中的最后一个TD设置了IOC,于是该TD完成之后的那个Frame结束时分,主机控制器会向CPU发送中断,于是中断函数会被调用.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: