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

Linux那些事儿之我是UHCI(24)等时传输

2007-11-15 17:50 585 查看
然后我们可以来看等时传输了.由于等时传输的特殊性,很多地方它都被特别的对待了.从usb_submit_urb开始就显示出了它的白里透红与众不同了.该函数中268行, 判断temp是不是PIPE_ISOCHRONOUS,即是不是等时传输,如果是,就执行下面那段代码.
278行,int number_of_packets是struct urb的一个成员,它用来指定该urb所处理的等时传输缓冲区的数量,或者说这个等时传输要传输多少个packet,每一个packet用一个struct usb_iso_packet_descriptor结构体变量来描述,对于每一个packet,需要建立一个td.
同时,我们还注意到struct urb有另外一个成员,struct usb_iso_packet_descriptor iso_frame_desc[0],又是一个零长度数组,这个数组用来帮助这个urb定义多个等时传输,而这个数组的实际长度恰恰就是我们前面提到的那个number_of_packets.设备驱动程序在提交等时传输urb的时候,必须设置好urb的iso_frame_desc数组.网友”只羡鸳鸯不献血”对我说,为何iso_frame_desc数组的长度恰好是number_of_packets?从哪里看出来的?还记得很久很久以前,我们曾经讲过一个叫做usb_alloc_urb()的函数么?不管是在usb-storage中还是在hub驱动中,我们都曾经见过这个函数,它的作用就是申请urb,但是你或许忘记了这个函数的参数,在include/linux/usb.h中我们找到了它的原型:
1266 extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
这其中第一个参数,iso_packets,其实就是咱们这里的number_of_packets.正如这个城市里每一个人都有几张脸一样,它们两者只是同一个概念的不同的表现形式罢了.所以,设备驱动在申请等时urb的时候,必须指定需要传输多少个packets.虽然曾经贴过usb_alloc_urb(),但是这里我还是在贴一遍吧,来自drivers/usb/core/urb.c:
40 /**
41 * usb_alloc_urb - creates a new urb for a USB driver to use
42 * @iso_packets: number of iso packets for this urb
43 * @mem_flags: the type of memory to allocate, see kmalloc() for a list of
44 * valid options for this.
45 *
46 * Creates an urb for the USB driver to use, initializes a few internal
47 * structures, incrementes the usage counter, and returns a pointer to it.
48 *
49 * If no memory is available, NULL is returned.
50 *
51 * If the driver want to use this urb for interrupt, control, or bulk
52 * endpoints, pass '0' as the number of iso packets.
53 *
54 * The driver must call usb_free_urb() when it is finished with the urb.
55 */
56 struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
57 {
58 struct urb *urb;
59
60 urb = kmalloc(sizeof(struct urb) +
61 iso_packets * sizeof(struct usb_iso_packet_descriptor),
62 mem_flags);
63 if (!urb) {
64 err("alloc_urb: kmalloc failed");
65 return NULL;
66 }
67 usb_init_urb(urb);
68 return urb;
69 }
再一次看到了零长度数组的应用.或者叫做变长度数组的应用.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 };
这个结构体的意思很简洁明了.这个结构体描述的就是一个iso包.而urb的iso_frame_desc数组的元素都是在设备驱动提交urb之前就设置好了.其中length就如注释里说的一样,是期待长度.而actual_length是实际长度,这里我们先把它设置为0.
至于348行,对于HIGH Speed的设备,如果urb->interval大于1024*8,则设置为1024*8,注意这里单位是微帧,即125微秒,以及360行,对于全速设备的ISO传输,如果urb->interval大于1024,则设置为1024,注意这里单位是帧,即1毫秒.关于这两条,Alan Stern的解释是,由于主机控制器驱动中并不支持超过1024个毫秒的interval,(想想也很简单,比如uhci吧,总共frame list才1024个元素,你这个间隔期总不能超过它吧,要不还不乱了去.)
然后进入usb_hcd_submit_urb.然后因为Root Hub是不会有等时传输的,所以针对非Root Hub,调用uhci_urb_enqueue.1419行,调用uhci_submit_isochronous().这个函数来自drivers/usb/host/uhci-q.c:
1228 /*
1229 * Isochronous transfers
1230 */
1231 static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
1232 struct uhci_qh *qh)
1233 {
1234 struct uhci_td *td = NULL; /* Since urb->number_of_packets > 0 */
1235 int i, frame;
1236 unsigned long destination, status;
1237 struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
1238
1239 /* Values must not be too big (could overflow below) */
1240 if (urb->interval >= UHCI_NUMFRAMES ||
1241 urb->number_of_packets >= UHCI_NUMFRAMES)
1242 return -EFBIG;
1243
1244 /* Check the period and figure out the starting frame number */
1245 if (!qh->bandwidth_reserved) {
1246 qh->period = urb->interval;
1247 if (urb->transfer_flags & URB_ISO_ASAP) {
1248 qh->phase = -1; /* Find the best phase */
1249 i = uhci_check_bandwidth(uhci, qh);
1250 if (i)
1251 return i;
1252
1253 /* Allow a little time to allocate the TDs */
1254 uhci_get_current_frame_number(uhci);
1255 frame = uhci->frame_number + 10;
1256
1257 /* Move forward to the first frame having the
1258 * correct phase */
1259 urb->start_frame = frame + ((qh->phase - frame) &
1260 (qh->period - 1));
1261 } else {
1262 i = urb->start_frame - uhci->last_iso_frame;
1263 if (i <= 0 || i >= UHCI_NUMFRAMES)
1264 return -EINVAL;
1265 qh->phase = urb->start_frame & (qh->period - 1);
1266 i = uhci_check_bandwidth(uhci, qh);
1267 if (i)
1268 return i;
1269 }
1270
1271 } else if (qh->period != urb->interval) {
1272 return -EINVAL; /* Can't change the period */
1273
1274 } else { /* Pick up where the last URB leaves off */
1275 if (list_empty(&qh->queue)) {
1276 frame = qh->iso_frame;
1277 } else {
1278 struct urb *lurb;
1279
1280 lurb = list_entry(qh->queue.prev,
1281 struct urb_priv, node)->urb;
1282 frame = lurb->start_frame +
1283 lurb->number_of_packets *
1284 lurb->interval;
1285 }
1286 if (urb->transfer_flags & URB_ISO_ASAP)
1287 urb->start_frame = frame;
1288 else if (urb->start_frame != frame)
1289 return -EINVAL;
1290 }
1291
1292 /* Make sure we won't have to go too far into the future */
1293 if (uhci_frame_before_eq(uhci->last_iso_frame + UHCI_NUMFRAMES,
1294 urb->start_frame + urb->number_of_packets *
1295 urb->interval))
1296 return -EFBIG;
1297
1298 status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
1299 destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
1300
1301 for (i = 0; i < urb->number_of_packets; i++) {
1302 td = uhci_alloc_td(uhci);
1303 if (!td)
1304 return -ENOMEM;
1305
1306 uhci_add_td_to_urbp(td, urbp);
1307 uhci_fill_td(td, status, destination |
1308 uhci_explen(urb->iso_frame_desc[i].length),
1309 urb->transfer_dma +
1310 urb->iso_frame_desc[i].offset);
1311 }
1312
1313 /* Set the interrupt-on-completion flag on the last packet. */
1314 td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
1315
1316 /* Add the TDs to the frame list */
1317 frame = urb->start_frame;
1318 list_for_each_entry(td, &urbp->td_list, list) {
1319 uhci_insert_td_in_frame_list(uhci, td, frame);
1320 frame += qh->period;
1321 }
1322
1323 if (list_empty(&qh->queue)) {
1324 qh->iso_packet_desc = &urb->iso_frame_desc[0];
1325 qh->iso_frame = urb->start_frame;
1326 qh->iso_status = 0;
1327 }
1328
1329 qh->skel = SKEL_ISO;
1330 if (!qh->bandwidth_reserved)
1331 uhci_reserve_bandwidth(uhci, qh);
1332 return 0;
1333 }
1240行,UHCI_NUMFRAMES是1024,同样,urb的interval显然不能比这个还大,它的number_of_packets也不能比这个大.要不然肯定就溢出了.就像伤痛,当眼泪掉下来,一定是伤痛已经超载.
接下来看,URB_ISO_ASAP这个flag是专门给等时传输用的,它的意思就是告诉驱动程序,只要带宽允许,那么就从此点开始设置这个urb的start_frame变量.通常为了尽可能快的得到图像数据,应当在URB中指定这个flag,因为它意味着尽可能快的发出本URB.比如说,之前有一个urb,是针对iso端点的,假设它有两个packets,它们被安排在frame号108和109,即假设其interval是1.现在在假设新的一个urb是在frame 111被提交的,如果设置了URB_ISO_ASAP这个flag,那么这个urb的第一个packet就会在下一个可以接受的frame中被执行,比如frame 112.但是如果没有设置这个URB_ISO_ASAP的flag呢,这个packet就会被安排在上一个urb结束之后的下一个frame,即110.尽管frame 110已经过去了,但是这种调度仍然有意义,因为它可以保证一定接下来的packets处于特定的phase,因为有的时候,驱动程序并不在乎丢掉一些包,尤其是等时传输.
我们看到这里qh的phase被设置为了-1.所以在uhci_check_bandwidth函数里面我们有一个判断条件是qh的phase是否大于等于0.如果调用uhci_check_bandwidth之前设置了phase大于等于0,则表明咱们手工设置了phase,否则的话这里通过一种算法来选择出一个合适的phase.这个函数正常应该返回0.
接下来,uhci_get_current_frame_number().
433 /*
434 * Store the current frame number in uhci->frame_number if the controller
435 * is runnning. Expand from 11 bits (of which we use only 10) to a
436 * full-sized integer.
437 *
438 * Like many other parts of the driver, this code relies on being polled
439 * more than once per second as long as the controller is running.
440 */
441 static void uhci_get_current_frame_number(struct uhci_hcd *uhci)
442 {
443 if (!uhci->is_stopped) {
444 unsigned delta;
445
446 delta = (inw(uhci->io_addr + USBFRNUM) - uhci->frame_number) &
447 (UHCI_NUMFRAMES - 1);
448 uhci->frame_number += delta;
449 }
450 }
我们说过,uhci主机控制器有一个frame计数器,frame从0到1023,然后又从0开始,那么这个数到底是多少呢?这个函数就是获得这个值的,我们看到读了端口,读USBFRNUM寄存器.uhci->frame_number用来记录这个frame number,所以这里的做法就是把当前的frame number减去上次保存在uhci->frame_number中的值,然后转换成二进制,得到一个差值,再更新uhci的frame_number.
而start_frame就是这个传输开始的frame.这里咱们让frame等于当前的frame加上10,就是给个延时,如注释所说的那样,给内存申请一点点时间.然后咱们让start_frame等于frame加上一个东西,(qh->phase-frame)和(qh->period-1)相与.熟悉二进制运算的同志们应该不难知道这样做最终得到的start_frame是什么,很显然,它会满足phase的要求.
1261行,else,就是驱动程序指定了start_frame,这种情况下就是直接设置phase,last_iso_frame就对应于刚才这个例子中的frame 109.
1293行,uhci_frame_before_eq就是一个普通的宏,来自drivers/usb/host/uhci-hcd.h:
441 /* Utility macro for comparing frame numbers */
442 #define uhci_frame_before_eq(f1, f2) (0 <= (int) ((f2) - (f1)))
其实就是比较两个frame number.如果第二个比第一个大的话,就返回真,反之就返回假.而咱们这里代码的意思是,如果第二个比第一个大,那么说明出错了.last_iso_frame是记录着上一次扫描时的frame号,在uhci_scan_schedule中会设置,UHCI_NUMFRAMES我们知道是1024.urb的number_of_packets与interval的乘积就表明将要花掉多少时间,它们加上urb的start_frame就等于这些包传输完之后的时间,或者说frame number.这里的意思就是希望一次传输的东西别太大了,不能越界.-EFBIG这个错误码的含义本身就是File too large.
1298行,TD_CTRL_IOS,对应于TD的bit25,IOS的意思是Isochronous Select,这一位为1表示这是这个TD是一个Isochronous Transfer Descriptor,即等时传输描述符,如果为0则表示这是一个非等时传输描述符.等时传输的TD在执行完之后会被主机控制器设置为inactive,不管执行的结果是什么.下面还设置了TD_CTRL_IOC,这个没啥好说的,告诉主机控制器在这个TD执行的Frame结束的时候发送一个中断.
然后根据packets的数量申请td,再把本urb的各个TD给加入到frame list中去.uhci_insert_td_in_frame_list是来自drivers/usb/host/uhci-q.c:
156 /*
157 * We insert Isochronous URBs directly into the frame list at the beginning
158 */
159 static inline void uhci_insert_td_in_frame_list(struct uhci_hcd *uhci,
160 struct uhci_td *td, unsigned framenum)
161 {
162 framenum &= (UHCI_NUMFRAMES - 1);
163
164 td->frame = framenum;
165
166 /* Is there a TD already mapped there? */
167 if (uhci->frame_cpu[framenum]) {
168 struct uhci_td *ftd, *ltd;
169
170 ftd = uhci->frame_cpu[framenum];
171 ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);
172
173 list_add_tail(&td->fl_list, &ftd->fl_list);
174
175 td->link = ltd->link;
176 wmb();
177 ltd->link = LINK_TO_TD(td);
178 } else {
179 td->link = uhci->frame[framenum];
180 wmb();
181 uhci->frame[framenum] = LINK_TO_TD(td);
182 uhci->frame_cpu[framenum] = td;
183 }
184 }
只有等时传输才需要使用这个函数.我们先看else这一段,让td物理上指向uhci的frame数组中对应元素,framenum是咱们传递进来的参数,其实就是urb的start_frame.而frame数组里面的东西又设置为td的物理地址.要知道之前我们曾经在configure_hc中把frame和实际的硬件的frame list给联系了起来,因此我们只要把td和frame联系起来就等于和硬件联系了起来,另一方面这里又把frame_cpu和td联系起来,所以以后我们只要直接通过frame_cpu来操作队列即可.正如下面在if段所看到的那样.
来看if这一段,struct uhci_td有一个成员struct list_head fl_list,struct uhci_hcd中有一个成员void **frame_cpu,当初咱们在uhci_start函数中为uhci->frame_cpu申请好了内存,而刚才在else里面我们看到每次会把frame_cpu数组的元素赋值为td,所以这里就是把td通过fl_list链入到ftd的fl_list队列里去.而物理上,也把td给插入到这个队列中来.
如果qh的queue为空,即没有任何urb,就设置qh的几个成员,iso_packet_desc是下一个urb的iso_frame_desc,iso_frame则是该iso_packet_desc的frame号,iso_status则是该iso urb的状态.
最后,令qh->skel等于SKEL_ISO,然后调用uhci_reserve_bandwidth保留带宽.
至此,uhci_submit_isochronous就结束了.回到uhci_urb_enqueue,下一步执行,uhci_activate_qh,而在这个函数中,我们将调用link_iso.
那么link_iso呢,同样来自drivers/usb/host/uhci-q.c:
425 /*
426 * Link an Isochronous QH into its skeleton's list
427 */
428 static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh)
429 {
430 list_add_tail(&qh->node, &uhci->skel_iso_qh->node);
431
432 /* Isochronous QHs aren't linked by the hardware */
433 }
这就简单多了,直接加入到skel_iso_qh这支队伍去就可以了.
终于,四大传输也就这样结束了.而我们的故事也即将ALT+F4了.我只是说也许.
如果失败的人生可以F5,如果莫名的悲伤可以DEL;
如果逝去的岁月可以CTRL+C,如果甜蜜的往事可以CTRL+V;
如果一切都可以CTRL+ALT+DEL,那么我们所有的故事是不是永远都不会ALT+F4?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: