- Linux那些事儿之我是USB
- 肖林甫 肖季东 任桥伟
- 5228字
- 2020-08-27 00:52:23
23.设备的生命线(四)
在struct urb中,就是每个写usb驱动的人都需要关心的了,坐这儿看了半天,struct urb才露出来这么一个角。
1136行,urb_list,还记得每个端点都会有的那个urb队列吗?那个队列就是由这里的urb_list一个一个地链接起来的。HCD每收到一个urb,就会将它添加到这个urb指定的那个端点的urb队列里去。这个链表的头儿在哪儿?当然是在端点里,就是端点里的那个struct list_head结构体成员。
1138行,dev,它表示urb要去的那个USB设备。
1139行,pipe,urb到达端点之前,需要经过一个通往端点的管道,就是这个pipe。怎么表示一个管道?管道有两端,一端是主机上的缓冲区,一端是设备上的端点。既然有两端,总要有一个方向吧!前面说过,端点有四种类型,那么与端点相生相依的管道也应该不只一种。
这么说来,确定一条管道至少要知道管道两端的地址、方向和类型,不过这两端里主机是确定的,需要确定的只是另一端设备的地址和端点的地址。怎么将这些内容组合起来表示成一个管道?一个包含了各种成员属性的结构再加上一些操作函数?多么完美的封装,但是不需要这么做,一个整型值再加上一些宏就够了。
先看一看管道,也就是这个整型值的构成,bit7用来表示方向,bit8~ bit14表示设备地址,bit15~ bit18表示端点号,早先说过,设备地址用7位来表示,端点号用4位来表示,剩下来的bit30~ bit31表示管道类型。再看一看围绕管道的一些宏,在include/linux/usb.h中定义。
1407 #define PIPE_ISOCHRONOUS 0 1408 #define PIPE_INTERRUPT 1 1409 #define PIPE_CONTROL 2 1410 #define PIPE_BULK 3 1411 1412 #define usb_pipein(pipe) ((pipe) & USB_DIR_IN) 1413 #define usb_pipeout(pipe) (!usb_pipein(pipe)) 1414 1415 #define usb_pipedevice(pipe) (((pipe) >> 8) & 0x7f) 1416 #define usb_pipeendpoint(pipe) (((pipe) >> 15) & 0xf) 1417 1418 #define usb_pipetype(pipe) (((pipe) >> 30) & 3) 1419 #define usb_pipeisoc(pipe) (usb_pipetype((pipe)) == PIPE_ISOCHRONOUS) 1420 #define usb_pipeint(pipe) (usb_pipetype((pipe)) == PIPE_INTERRUPT) 1421 #define usb_pipecontrol(pipe) (usb_pipetype((pipe)) == PIPE_CONTROL) 1422 #define usb_pipebulk(pipe) (usb_pipetype((pipe)) == PIPE_BULK)
现在来看如何创建一个管道?主机和设备要交流必须通过管道,你必须得创建一个管道给urb,它才知道路怎么走。不过在说怎么创建一个管道前,先说一个有关管道的故事。
1801年,在意大利中部的小山村,有两个名叫柏波罗和布鲁诺的年轻人,他们的最大梦想是成为村子里最富有的人。有一天,喜鹊在枝头唧唧喳喳地叫,他们的好运也就随着来了。村里决定雇两个人把附近河里的水运到村广场的水缸里去,他们得到了这个机会。“我提一桶水,只收他一分钱,我提10桶水赚1毛钱,我一天提一百桶水!能赚多少钱啊!”布鲁诺激动地盘算着。但柏波罗却想着一天才几分钱的报酬,还要这样来回提水,还不如干脆修一条管道将水从河里引到村里去。于是布鲁诺每天辛勤地提着水,很快买了新衣服,买了驴,虽然仍然买不起车也买不起房,但已经被看做是中产阶级了,而柏波罗每天还要抽出一部分提水的时间去挖管道,收入是入不敷出,挖管道的同时还要接收很多人对他的嘲笑。两年后,柏波罗的管道完工了,水哗哗地直往村里流,钱哗哗地直往口袋里钻,而此时布鲁诺因为长时间的提桶工作变得腰弯背驼。柏波罗成了奇迹的创造者,他没有被赞扬冲昏头脑,他想的是创建更多的管道,他邀请布鲁诺加入了他的管道事业,从此他们的管道事业芝麻开花节节高,遍布了全球。
这个故事告诉我们,管道很重要。显然,写代码的人也深刻地认识到了这个道理,于是内核的include/linux/usb.h文件中多了很多专门用来创建不同管道的宏。
1432 static inline unsigned int __create_pipe(struct usb_device *dev, 1433 unsigned int endpoint) 1434 { 1435 return (dev->devnum << 8) | (endpoint << 15); 1436 } 1437 1438 /* Create various pipes... */ 1439 #define usb_sndctrlpipe(dev,endpoint) \ 1440 ((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint)) 1441 #define usb_rcvctrlpipe(dev,endpoint) \ 1442 ((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN) 1443 #define usb_sndisocpipe(dev,endpoint) \ 1444 ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint)) 1445 #define usb_rcvisocpipe(dev,endpoint) \ 1446 ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN) 1447 #define usb_sndbulkpipe(dev,endpoint) \ 1448 ((PIPE_BULK << 30) | __create_pipe(dev,endpoint)) 1449 #define usb_rcvbulkpipe(dev,endpoint) \ 1450 ((PIPE_BULK << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN) 1451 #define usb_sndintpipe(dev,endpoint) \ 1452 ((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint)) 1453 #define usb_rcvintpipe(dev,endpoint) \ 1454 ((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)
端点有四种,对应着管道也就有四种,同时端点是有IN也有OUT的,相应的管道也就有两个方向,于是,上面就出现了八个创建管道的宏。有了struct usb_device结构体,也就是说知道了设备地址,再加上端点号,就可以需要什么管道就创建什么管道了。__create_pipe宏只是一个幕后的角色,用来将设备地址和端点号放在管道正确的位置上。
1140行,status,urb的当前状态。urb当然是可以有多种状态的,urb当前什么状态就是让我们了解出了什么事情。
1141行,transfer_flags,一些标记,可用的值都在include/linux/usb.h里有定义。
942 #define URB_SHORT_NOT_OK 0x0001 /* report short reads as errors */ 943 #define URB_ISO_ASAP 0x0002 /* iso-only, urb->start_frame 944 * ignored */ 945 #define URB_NO_TRANSFER_DMA_MAP 0x0004 /* urb->transfer_dma valid on submit */ 946 #define URB_NO_SETUP_DMA_MAP 0x0008/*urb->setup_dma valid on submit*/ 947 #define URB_NO_FSBR 0x0020 /* UHCI-specific */ 948 #define URB_ZERO_PACKET 0x0040 /* Finish bulk OUT with short packet */ 949 #define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt 950 * needed */
URB_SHORT_NOT_OK,这个标记只对用来从IN端点读取数据的urb有效,意思是如果从一个IN端点那里读取了一个比较短的数据包,就可以认为是错误的。那么这里的short究竟短到什么程度?
之前说到端点时,就知道端点描述符里有一个叫wMaxPacketSize,指明了端点一次能够处理的最大字节数。另外前面也提了,在USB的世界里是有很多种包的,四种PID类型,每种PID下边儿还有一些细分的品种。这四种PID里面,有一个叫Data的,也只有它里边儿有一个数据字段,像其他的Token、Handshake之类的PID类型都是没有这个字段的,所以里里外外看过去,还只有Data PID类型的包最实在,就是用来传输数据的,但是它里面并不是只有一个数据字段,还有SYNC、PID、地址域、CRC等陪伴在数据字段的左右。
现在又有一个问题出来了,每个端点描述符里的wMaxPacketSize所表示的最大字节数都包括了哪些部分?是整个packet的长度吗?我可以负责任地告诉你,它只包括了Data包中面数据字段,俗称data payload,其他的数据字段都是协议本身需要的信息,和TCP/IP里的报头差不多。
wMaxPacketSize与short有什么关系?关系还不小,short是与wMaxPacketSize相比的,如果从IN端点那儿收到了一个比wMaxPacketSize要短的包,同时也设置了URB_SHORT_ NOT_OK这个标志,那么就可以认为传输出错了。
本来如果收到一个比较短的包是意味着这次传输到此为止就结束了,你想一想,data payload的长度最大必须为wMaxPacketSize,这个规定是不可违背的,但是如果端点想给你的数据不止这么多,怎么办?这就需要分成多个wMaxPacketSize大小的data payload来传输,有时数据不会那么凑巧刚好能平分成多个整份。这时,最后一个data payload的长度就会比wMaxPacketSize小,这种情况本来意味着端点已经传完了它想传的数据,释放完了自己的需求,这次传输就该结束了,不过如果你设置了URB_SHORT_NOT_OK标志,HCD这边就会认为发生了错误。
URB_ISO_ASAP,这个标志只是为了方便等时传输使用。等时传输和中断传输在spec里都被认为是periodic transfers,也就是周期传输,我们都知道在USB的世界里都是主机占主导地位,设备是没多少发言权的,但是对于等时传输和中断传输,端点可以对主机表达自己一种美好的期望,希望主机能够隔多长时间访问自己一次,这个期望的时间就是这里说的周期。
当然,期望与现实是有一段距离的,如果期望的都能成为现实,我们还研究USB干什么,端点的这个期望能不能得到满足,要看主机控制器答应不答应。
对于等时传输,一般来说也就一帧(微帧)一次,主机也很忙,再多也抽不出空儿来。那么如果你有一个用于等时传输的urb,提交给HCD时,就得告诉HCD它应该从哪一帧开始的,就要对下面要讲到的start_frame赋值,也就是说告诉HCD等时传输开始的那一帧(微帧)的帧号,如果你留心,应该还会记得前面说过在每帧或微帧(Mircoframe)的开始都会有一个SOF Token包,这个包中就含有一个帧号字段,记录了那一帧的编号。
这样的话,一是要去设置这个start_frame,二是到你设置的那一帧时,如果主机控制器没空儿开始等时传输,怎么办?于是,就出现了URB_ISO_ASAP,它的意思就是告诉HCD什么时候不忙就什么时候开始,就不用指定开始的帧号了。所以说,你如果想进行等时传输,又不想标新立异的话,就还是设置start_frame吧。
URB_NO_TRANSFER_DMA_MAP,还有URB_NO_SETUP_DMA_MAP,这两个标志都是与DMA有关的。什么是DMA?DMA就是“外设”,比如USB摄像头,和内存之间直接进行数据交换,不用通过CPU。本来,在我们的计算机里,CPU自认为是老大,什么事都要去插一脚,都要经过它去协调处理。可是这样的话就影响了数据传输的速度,有DMA和没有DMA区别就是这么大。
USB的世界里也要与时俱进,所以DMA也是少不了的。一般来说,都是驱动里提供了kmalloc等分配的缓冲区,HCD进行一定的DMA映射处理,DMA映射是干什么的?外设和内存之间进行数据交换总要互相认识吧,外设是通过各种总线连到主机里面的,使用的是总线地址,而内存使用的是虚拟地址,它们之间本来就是两条互不相交的平行线,要让它们中间产生连接点,必须得将一个地址转化为另一个地址,这样才能找得到对方,才能互通有无,而DMA映射就是起这个作用的。
这只是粗略说法,实际上即使千言万语也道不完的。DMA映射可是高技术含量的活儿,所以在某些平台上非常费时费力。为了分担HCD的压力,于是就有了这里的两个标志,告诉HCD不要再自己做DMA映射了,驱动提供的urb里已经提供有DMA缓冲区地址。具体提供了哪些DMA缓冲区?就涉及下面的transfer_buffer,transfer_dma,还有setup_packet,setup_dma这两个函数了。
URB_NO_FSBR,这是给UHCI用的函数。
URB_ZERO_PACKET,这个标志表示批量的OUT传输必须使用一个short packet来结束。批量传输的数据大于批量端点的wMaxPacketSize时,需要分成多个Data包来传输,最后一个data payload的长度可能等于wMaxPacketSize,也可能小于wMaxPacketSize,当等于wMaxPacketSize时,如果同时设置了URB_ZERO_PACKET标志,就需要再发送一个长度为0的数据包来结束这次传输,如果小于wMaxPacketSize就没必要多此一举了。如果你要问,当批量传输的数据小于wMaxPacketSize时怎么办?也没必要再发送0长的数据包,因为此时发送的这个数据包本身就是一个short packet。
URB_NO_INTERRUPT,这个标志用来告诉HCD,在URB完成后,不要请求一个硬件中断,当然这就意味着你的结束处理函数可能不会在urb完成后立即被调用,而是在之后的某个时间被调用,USB Core会保证为每个urb调用一次结束处理函数。
还是回到struct urb,1142行到1144行,transfer_buffer,transfer_dma,transfer_buffer_length,前面说过管道的一端是主机上的缓冲区,另一端是设备上的端点,这三个家伙就是描述主机上的缓冲区的函数。
transfer_buffer是使用kmalloc分配的缓冲区,transfer_dma是使用usb_buffer_alloc分配的dma缓冲区。HCD不会同时使用它们两个,如果urb自带了transfer_dma,就要同时设置URB_NO_TRANSFER_DMA_MAP来告诉HCD一声,不用它再费心做DMA映射了。transfer_buffer是必须要设置的,因为不是所有的主机控制器都能够使用DMA的,万一遇到这样的情况,也好有一个备用。transfer_buffer_length指的就是transfer_buffer或transfer_dma的长度。
1145行,actual_length,urb结束之后,会用这个字段告诉你实际上传输了多少数据。
1146行到1147行,setup_packet,setup_dma,同样是两个缓冲区,一个是kmalloc分配的,一个是用usb_buffer_alloc分配的,不过,这两个缓冲区是控制传输专用的,记得struct usb_ctrlrequest吗?它们保存的就是一个struct usb_ctrlrequest结构体,如果你的urb设置了setup_dma,同样要设置URB_NO_SETUP_DMA_MAP标志来告诉HCD。如果进行的是控制传输,setup_packet是必须要设置的,也是为了防止出现主机控制器不能使用DMA的情况。
1148行,start_frame,如果你没有指定URB_ISO_ASAP标志,就必须自己设置start_frame,指定等时传输在哪个帧或哪个微帧开始。如果指定了URB_ISO_ASAP,urb结束时会使用这个值返回实际的开始帧号。
1150行,interval,等时传输和中断传输专用。Interval是间隔时间的意思。什么的间隔时间?就是上面说的端点希望主机轮询自己的时间间隔。这个值和端点描述符里的bInterval是一样的,你不能随便地指定一个,然后就去做春秋大梦,以为到时间了梦里的名车美女都会跑出来,协议中对你能指定的值是有范围限制的。对于中断传输,全速时,这个范围为1 ms ~255 ms,低速时为10 ms ~255 ms,高速时为1 ms ~16 ms,这个1 ms ~16 ms只是bInterval可以取的值,实际的间隔时间为2的(bInterval-1)次方乘以125 ms,也就是2的(bInterval-1)次方个微帧。
对于等时传输,没有低速等时传输根本就不是低速端点负担得起的,对于全速和高速,这个范围也是为1ms~16ms,间隔时间由2的(bInterval-1)次方算出来,单位为帧或微帧。
这样看来,每一帧或微帧里,你最多只能期望有一次等时传输和中断传输,不能再多了。
不过即使完全按照上面的范围来取,你的期望也并不是就肯定可以实现的,因为对于高速传输来说,最多有80%的总线时间用于这两种传输,对于低速传输和全速传输要多一点,达到90%。这个时间的分配都由主机控制器掌握着,所以你的期望能不能实现还要看主机控制器的脸色,没办法,它就有这种权力。
1153行,context,驱动设置了给下面的结束处理函数用的。比如可以将自己驱动里描述自己设备的结构体放在里面,在结束处理函数中就可以取出来。
1154行,complete,一个指向结束处理函数的指针,传输成功完成,或者中间发生错误时就会调用它,驱动可以在这里面检查urb的状态,并做一些处理。比如可以释放这个urb,或者重新提交给HCD。比如说摄像头吧,你向HCD提交了一个等时的urb,从摄像头那里读取视频数据,传输完成时调用了你指定的这个结束处理函数,并在里面取出了urb里面获得的数据进行解码等处理,然后怎么办?总不会这一个urb读取的数据就够了吧,所以需要获得更多的数据,那你也总不会再去创建、初始化一个等时的urb吧,很明显刚刚的那个urb可以继续用,只要将它再次提交给HCD就可以了。这个函数指针的定义在include/linux/usb.h。
961 typedef void (*usb_complete_t)(struct urb *);
还有三个函数,都是等时传输专用的,等时传输与其他传输不一样,可以指定传输多少个packet,每个packet使用struct usb_iso_packet_descriptor结构来描述。1155行的iso_frame_desc就表示了一个变长的struct usb_iso_packet_descriptor结构体数组,而1149行的number_of_ packets指定了这个结构体数组的大小,也就是要传输多少个packet。
要说明的是,这里说的packet不是说在一次等时传输里传输了多个Data packet,而是说在一个urb里指定了多次的等时传输,每个struct usb_iso_packet_descriptor结构体都代表了一次等时传输。
这里说明等时传输底层的packet情况。不像控制传输最少要有SETUP和STATUS两个阶段的transaction,等时传输只有Isochronous transaction,即等时transaction一个阶段,一次等时传输就是一次等时transaction的过程。
而等时transaction也只有两个阶段,就是主机向设备发送OUT Token包,然后发送一个Data包,或者是主机向设备发送IN Token包,然后设备向主机发送一个Data包,这个Data包中data payload的长度只能小于或者等于等时端点的wMaxPacketSize值。
这里没有了Handshake包,是因为不需要,等时传输是不保证数据完全正确无误到达的,没有什么错误重传机制,也就不需要使用Handshake包来汇报。对它来说实时性要比正确性重要得多,你的摄像头一秒钟少给你一帧和多给你一帧没有本质的区别,如果等时传输延迟几秒,感觉就明显不同了。
所以对于等时传输来说,在完成了number_of_packets次传输之后,会去调用你的结束处理函数,在里面对数据做处理,而1152行的error_count记录了这么多次传输中发生错误的次数。
现在看一看struct usb_iso_packet_descriptor结构的定义,在include/linux/usb.h中定义。
952 struct usb_iso_packet_descriptor { 953 unsigned int offset; 954 unsigned int length; /* expected length */ 955 unsigned int actual_length; 956 int status; 957 };
offset表示transfer_buffer里的偏移位置,你不是指定了要进行number_of_packets次等时传输吗,那么也要准备够这么多次传输用的缓冲区,当然不是说让你准备多个缓冲区。没必要,都放transfer_buffer或者transfer_dma里面好了,只要记着每次传输对应的数据偏移就可以。length是预期的这次等时传输Data包中数据的长度,注意这里说的是预期,因为实际传输时因为各种原因可能不会有那么多数据,urb结束时,每个struct usb_iso_packet_descriptor结构体的actual_length就表示了各次等时传输实际传输的数据长度,而status分别记录了它们的状态。