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

然后我们可以来看等时传输了.由于等时传输的特殊性,很多地方它都被特别的对待了.从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?

同时也用对她的怀念来惩罚自己。

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

相关文章:

你感兴趣的文章:

标签云: