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

然后我们可以来看等时传输了.由于等时传输的特殊性,很多地方它都被特别的对待了.usb_submit_urb开始就显示出了它的白里透红与众不同了.该函数中268, 判断temp是不是PIPE_ISOCHRONOUS,即是不是等时传输,如果是,就执行下面那段代码.

278,int number_of_packetsstruct 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的时候,必须设置好urbiso_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.urbiso_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 list1024个元素,你这个间隔期总不能超过它吧,要不还不乱了去.)

然后进入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_NUMFRAMES1024,同样,urbinterval显然不能比这个还大,它的number_of_packets也不能比这个大.要不然肯定就溢出了.就像伤痛,当眼泪掉下来,一定是伤痛已经超载.

接下来看,URB_ISO_ASAP这个flag是专门给等时传输用的,它的意思就是告诉驱动程序,只要带宽允许,那么就从此点开始设置这个urbstart_frame变量.通常为了尽可能快的得到图像数据,应当在URB中指定这个flag,因为它意味着尽可能快的发出本URB.比如说,之前有一个urb,是针对iso端点的,假设它有两个packets,它们被安排在frame108109,即假设其interval1.现在在假设新的一个urb是在frame 111被提交的,如果设置了URB_ISO_ASAP这个flag,那么这个urb的第一个packet就会在下一个可以接受的frame中被执行,比如frame 112.但是如果没有设置这个URB_ISO_ASAPflag,这个packet就会被安排在上一个urb结束之后的下一个frame,110.尽管frame 110已经过去了,但是这种调度仍然有意义,因为它可以保证一定接下来的packets处于特定的phase,因为有的时候,驱动程序并不在乎丢掉一些包,尤其是等时传输.

我们看到这里qhphase被设置为了-1.所以在uhci_check_bandwidth函数里面我们有一个判断条件是qhphase是否大于等于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计数器,frame01023,然后又从0开始,那么这个数到底是多少呢?这个函数就是获得这个值的,我们看到读了端口,USBFRNUM寄存器.uhci->frame_number用来记录这个frame number,所以这里的做法就是把当前的frame number减去上次保存在uhci->frame_number中的值,然后转换成二进制,得到一个差值,再更新uhciframe_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.urbnumber_of_packetsinterval的乘积就表明将要花掉多少时间,它们加上urbstart_frame就等于这些包传输完之后的时间,或者说frame number.这里的意思就是希望一次传输的东西别太大了,不能越界.-EFBIG这个错误码的含义本身就是File too large.

1298,TD_CTRL_IOS,对应于TDbit25,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物理上指向uhciframe数组中对应元素,framenum是咱们传递进来的参数,其实就是urbstart_frame.frame数组里面的东西又设置为td的物理地址.要知道之前我们曾经在configure_hc中把frame和实际的硬件的frame list给联系了起来,因此我们只要把tdframe联系起来就等于和硬件联系了起来,另一方面这里又把frame_cputd联系起来,所以以后我们只要直接通过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链入到ftdfl_list队列里去.而物理上,也把td给插入到这个队列中来.

如果qhqueue为空,即没有任何urb,就设置qh的几个成员,iso_packet_desc是下一个urbiso_frame_desc,iso_frame则是该iso_packet_descframe,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;

如果逝去的岁月可以CTRLC,如果甜蜜的往事可以CTRLV;

如果一切都可以CTRLALTDEL,那么我们所有的故事是不是永远都不会ALTF4?

 

你可能感兴趣的:(linux,struct,list,IOC,insert,Descriptor)