Linux那些事儿之我是UHCI(11)一个函数引发的故事(二)
571行之前全是些初始化的代码,先飘过,用到了再回来看.571行到582行,上次我们看到DEBUG_CONFIGURED是在uhci的初始化代码中,即uhci_hcd_init函数中,这是一个编译开关,打开开关就是1,不打开就是0,我们假设打开.因为我们有必要了解一下debugfs的更多,毕竟我们当初已经接触过debugfs了.而且当时已经看到函数debugfs_create_dir在/sys
571行之前全是些初始化的代码,先飘过,用到了再回来看.
571行到582行,上次我们看到DEBUG_CONFIGURED是在uhci的初始化代码中,即uhci_hcd_init函数中,这是一个编译开关,打开开关就是1,不打开就是0,我们假设打开.因为我们有必要了解一下debugfs的更多,毕竟我们当初已经接触过debugfs了.而且当时已经看到函数debugfs_create_dir在/sys/kernel/debug下面建立了一个uhci的目录,而现在我们看到的这个debugfs_create_file很自然,就是在/sys/kernel/debug/uhci下面建立文件,比如:
localhost:~ # ls -l /sys/kernel/debug/uhci/
total 0
-rw-r--r-- 1 root root 0 Oct 8 13:18 0000:00:1d.0
-rw-r--r-- 1 root root 0 Oct 8 13:18 0000:00:1d.1
-rw-r--r-- 1 root root 0 Oct 8 13:18 0000:00:1d.2
-rw-r--r-- 1 root root 0 Oct 8 13:18 0000:00:1d.3
可以看见,在这个目录下面针对每个uhci主机控制器各建立了一个文件,文件名就是该设备的pci名,即域名+总线名+插槽号+功能号.(domain,bus,device,function)
接下来从584行到628行,全都是内存申请相关的,包括可恶的DMA.不过这些函数我们已经不再陌生,dma_alloc_coherent,dma_pool_create都已经讲过,但要说清楚这些实际的代码,则必须借助一张经典的图,来自UHCI spec:
这张图叫做调度图.有了这张图,你就可以运筹帷幄之内决胜千里之外.这张图对于我们研究uhci的意义,就好比1993年那部何家劲版的少年张三丰中那张藏宝图,所有的人都想得到它,因为得到了它就意味着得到一切.而对于所有写uhci主机驱动的人来说,他们对于这幅图的共同心声是:输了你,赢了世界又如何?
之所以这幅图如此重要,是因为uhci主机控制器的原理完全集中在这幅图中.
Frame List,有人管它叫框架表,有人管它叫帧链表.我觉得怎么叫都不爽,所以还是用英文吧,就叫它Frame List,而Frame我也不翻译过来了,理由是,我在伟大的微软拼音输入法里竟然没有找到” 帧”这个字.主机控制器最重要的一个职责是调度.那么它如何调度呢?首先你得把这个Frame List的起始地址告诉它,由这个List将会牵出许多的TD和QH来.
首先Frame List是一个数组,最多1024个元素.每一个元素代表一个frame,时间上来说就是1ms.而和每一个Frame相联系的是transaction,即交易.比如说一次传输,就可以算作一笔交易.而TD和QH就是用来表征这些交易的.Frame List的每一个元素会包含指针指向TD或者QH,实际上Frame List的每一个元素被称作一个Frame List Pointer,它一共有32个bit,其中bit31到bit4则表示的是TD或者QH的地址,bit1则表示指向的到底是QH还是TD.
而从硬件上来说,访问这个Frame List的方法是使用一个基址寄存器和一个计数器.即图中我们看到的那个Frame List Base Address Register和Frame Counter.下面这张图也许更能说明这两个东西的作用.
实际上在主机控制器中有一个时钟,用我们电子专业的术语来说就是主机控制器内部肯定有一个晶体振荡器,从而实现一个周期为1msec的信号,于是Frame List Base Address Register和Frame Counter就去遍历这张Frame List,它们在这张表里一毫秒前进一格,走到1023了就又再次归零.
那么驱动程序的责任是什么呢?为上面那张调度图准备好相应的数据结构.填充Frame List Pointer,建立并填充TD,建立并填充QH.说了这么些,那么到底什么是TD什么是QH呢?
来看TD,TD实际上描述的就是最终要在USB总线上传输的数据包,它的学名叫Transaction Descriptor,它是主机控制器的基本执行单位.UHCI spec定义了两类TD,Iso TD和non-ISO TD.即等时TD和非等时TD.我们知道USB一共四种传输方式,中断,批量,控制,等时.这其中等时传输最帅,所以它的TD也会有所不同,虽然从数据结构的格式来说是一样的,但是作用不一样.从这张调度图来看,等时的TD也是专门被列出来了.主机控制器驱动程序负责填充TD,主机控制器将会去获取TD,然后执行相应的数据传输.
然后来看QH,QH就是队列头(Queue Head).从这张调度图里我们也能看到,QH实际上把各个非等时TD给连接了起来,组织成了若干队列.
从图中我们看到一个现象,对主机控制器来说,四种传输方式是有优先级的区别的,等时传输最帅,所以它总是最先被满足,最先被执行,然后是中断传输,再然后才是控制传输和Bulk传输.等时传输和中断传输都叫做周期传输,或者说定期传输.
再强调一下,驱动程序的任务就是填充这张图,然后硬件的作用就是按照这张图去执行,这种分工是很明确的.
Ok,现在让我们结合代码和结构体定义来看看.
首先584行,使用dma_alloc_coherent申请内存,赋给uhci->frame,而与此同时建立了dma映射,frame_dma_handle,frame是以后我们从软件方面来指代这个frame list的,而frame_dma_handle因为是物理地址,我们要让它和真正的硬件联系起来,稍后在一个叫做configure_hc的函数中你会看到,我们会把它写入Frame List的基址寄存器.这样我们以后操作uhci->frame就等于真正的操作硬件了.这更意味着以后我们只要把申请的TD啊,QH啊,和uhci->frame联系起来就可以了.这里我们也注意到,UHCI_NUMFRAMES就是一个宏,它的值正是1024.到目前为止,一切看起来都那么和谐.
而594行这个frame_cpu则是和frame相对应的,它是一个纯粹软件意义上的frame list.即frame身上承担着硬件的使命,而frame_cpu则属于我们从软件角度来说记录这张frame list的.
609行这两个dma_poll_create就是创建内存池,给我们在下面为TD和QH申请内存带来方便.
616行调用的这个uhci_alloc_td以及623行调用的uhci_alloc_qh则都是来自drivers/usb/host/uhci-q.c,先看前者,uhci_alloc_td,顺便把它的搭档也一并贴出来.
106 static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci)
107 {
108 dma_addr_t dma_handle;
109 struct uhci_td *td;
110
111 td = dma_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle);
112 if (!td)
113 return NULL;
114
115 td->dma_handle = dma_handle;
116 td->frame = -1;
117
118 INIT_LIST_HEAD(&td->list);
119 INIT_LIST_HEAD(&td->fl_list);
120
121 return td;
122 }
123
124 static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td)
125 {
126 if (!list_empty(&td->list)) {
127 dev_warn(uhci_dev(uhci), "td %p still in list!/n", td);
128 WARN_ON(1);
129 }
130 if (!list_empty(&td->fl_list)) {
131 dev_warn(uhci_dev(uhci), "td %p still in fl_list!/n", td);
132 WARN_ON(1);
133 }
134
135 dma_pool_free(uhci->td_pool, td, td->dma_handle);
136 }
这意思很明了.再来看后者,uhci_alloc_qh以及它的搭档uhci_free_qh.
247 static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
248 struct usb_device *udev, struct usb_host_endpoint *hep)
249 {
250 dma_addr_t dma_handle;
251 struct uhci_qh *qh;
252
253 qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);
254 if (!qh)
255 return NULL;
256
257 memset(qh, 0, sizeof(*qh));
258 qh->dma_handle = dma_handle;
259
260 qh->element = UHCI_PTR_TERM;
261 qh->link = UHCI_PTR_TERM;
262
263 INIT_LIST_HEAD(&qh->queue);
264 INIT_LIST_HEAD(&qh->node);
265
266 if (udev) { /* Normal QH */
267 qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
268 if (qh->type != USB_ENDPOINT_XFER_ISOC) {
269 qh->dummy_td = uhci_alloc_td(uhci);
270 if (!qh->dummy_td) {
271 dma_pool_free(uhci->qh_pool, qh, dma_handle);
272 return NULL;
273 }
274 }
275 qh->state = QH_STATE_IDLE;
276 qh->hep = hep;
277 qh->udev = udev;
278 hep->hcpriv = qh;
279
280 if (qh->type == USB_ENDPOINT_XFER_INT ||
281 qh->type == USB_ENDPOINT_XFER_ISOC)
282 qh->load = usb_calc_bus_time(udev->speed,
283 usb_endpoint_dir_in(&hep->desc),
284 qh->type == USB_ENDPOINT_XFER_ISOC,
285 le16_to_cpu(hep->desc.wMaxPacketSize))
286 / 1000 + 1;
287
288 } else { /* Skeleton QH */
289 qh->state = QH_STATE_ACTIVE;
290 qh->type = -1;
291 }
292 return qh;
293 }
294
295 static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
296 {
297 WARN_ON(qh->state != QH_STATE_IDLE && qh->udev);
298 if (!list_empty(&qh->queue)) {
299 dev_warn(uhci_dev(uhci), "qh %p list not empty!/n", qh);
300 WARN_ON(1);
301 }
302
303 list_del(&qh->node);
304 if (qh->udev) {
305 qh->hep->hcpriv = NULL;
306 if (qh->dummy_td)
307 uhci_free_td(uhci, qh->dummy_td);
308 }
309 dma_pool_free(uhci->qh_pool, qh, qh->dma_handle);
310 }
这个就明显复杂一些了.关于uhci_alloc_qh,它的执行有两条路径,一种是udev有所指,一种是udev为空.我们这里传进来的是NULL,所以暂时可以不看另一种路径,到时候需要看的时候再去看.这么一来就意味着此时此刻我们不需要看266到287这些行了.而这意味着这个函数我们现在能看到的就是一些简单的赋值操作而已.但是我们必须理解为什么这里有两种情况,因为这正是uhci-hcd这个驱动的精妙之处.
为了堆砌出那幅调度图 , 一个很简单的方法是 , 每次有传输任务 , 建立一个或者几个 TD, 把 urb 转换成 urb, 然后把 TD 挂入 frame list pointer, 不就可以了么 ? 朋友 , 如果生活真的是这么简单 , 如果世界真的是这么单纯 , 那么也许现在我依然是一张洁白的宣纸 , 绝不是现在这张被上海的梅雨湿润过的废纸 .更多推荐
所有评论(0)