《Linux那些事儿之我是USB》我是U盘(20)通往春天的管道

来源:岁月联盟 编辑:exp 时间:2011-10-23

 

1991年,一个在Linux中引入了管道这个概念,并且把管道用在很多地方,如文件系统、设备驱动中。于是后来我们看到在Linux中有了各种各样的管道。但是相同的是,所有管道都是用来传输东西的,只不过有些管道传输的是实实在在的物质,而有些管道传输的是数据。

 

眼下我们在USB代码中看到的管道就是用来传输数据及通信。通信是双方的,不可能自言自语。而在USB的通信中,一方肯定是主机,另一方是什么?是设备吗?说得更确切一点,真正和主机进行通信的是设备内的端点。关于端点,我们也可以专业一点说,从硬件上来看它是实实在在存在的,它被实现为一种FIFO,支持多少个端点是接口芯片的一个重要指标。

 

而从概念上来说,端点是主机和USB设备之间通信流的终点。主机和设备可以进行不同种类的通信,或者说数据传输。首先,设备连接在USB总线上,USB总线为了分辨每一个设备,给每一个设备编上号,然后为了实现多种通信,设备方于是提供了端点,端点多了,自然也要编上号,而让主机最终和端点去联系。

 

在USB的世界里,有四种管道,它们对应USB世界中的四种传输方式:控制传输对应控制管道、中断传输对应中断管道、批量传输对应批量管道及等时传输对应等时管道。每一个USB世界中的设备要在USB世界里生存,就得有它生存的管道。而管道的出现,正是为了让我们分辨端点,或者说连接端点。记得网友“唐伯虎点蚊香”曾经说过:“主机与端点之间的数据链接就称为管道。”

 

比如说,复旦大学有一个主校区,也可以说是教学区,(当然也包括一些试验室),上课什么的都得去教学区,把教学区看做主机,那么与其相对的是,另外有很多学生宿舍楼,宿舍楼多了,就给每个楼编上号,比如1号楼,2号楼,……,36号楼,……,每幢楼算一个设备。

 

复旦主校区是主机,每幢宿舍楼算一个设备,你住的那间宿舍就算端点。那么管道呢?管道很难与现实中的某个实物对应,不能说它是复旦正门通往宿舍的某条路,而应该按别的方式理解。它包含很多东西,你可以把它比做快递的货物上面贴得那张标签,比如它上面写了收货人的地址,包括多少号楼多少号房,在USB里面,就是设备号和端点号,知道了这两个号,货物就能确定它的目的地,而USB主机就能知道和它通信的是哪个端点。

 

而管道除了包含着两个号以外,还包含其他一些信息。比如它包含了通信的方向,也就是说,你能从那张标签上得知36号楼201这个是收件人呢还是寄件人,虽然现实中不需要写明,因为快递员肯定知道你是收件人。管道里还包含了管道的类型,比如你的快递是从深圳递过来,那么怎么传递就得看快递公司了,快递公司肯定提供不同类型的服务,有的快有的慢,有的可能还有保险,看你出多少钱,让你选择不同的服务类型。同样管道也如此,因为USB设备的端点有不同的类型,所以管道里就包含了一个字段来记录这一点。好,让我们来查看实际的管道吧。

 

来看这些宏,头一个闪亮登场的是usb_sndctrlpipe,定义于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)

 

1455

 

先看1439行,把这个宏展开,PIPE_CONTROL也是宏,定义于同一个文件中,include/linux/usb.h:

 

1405 /* NOTE:  these are not the standard USB_ENDPOINT_XFER_* values!! */

 

1406 /* (yet ... they're the values used by usbfs)*/

 

1407 #define PIPE_ISOCHRONOUS               0

 

1408 #define PIPE_INTERRUPT           1

 

1409 #define PIPE_CONTROL               2

 

1410 #define PIPE_BULK                  3

 

USB有四种传输方式,即等时传输、中断传输、控制传输和批量传输。一个设备能支持这四种传输中的哪一种或者哪几种是设备本身的属性,在硬件设计时就确定了。比如一个纯粹的U盘,它肯定是支持批量传输和控制传输的。不同的传输要求有不同的端点,所以对于U盘来说,它一定会有批量端点和控制端点,于是就得使用相应的管道来跟不同的端点联系。在这里我们看到了四个宏,其中,PIPE_ISOCHRONOUS就是标志等时通道,PIPE_INTERRUPT就是中断通道,PIPE_CONTROL就是控制通道,PIPE_BULK就是批量通道。

 

另外__create_pipe也是一个宏,由上面的定义可以看出它为构造一个宏提供了设备号和端点号。在内核中使用一个unsigned int类型的变量来表征一个管道,其中8位~14位是设备号,即devnum,15位~18位是端点号,即endpoint。而咱们还看到有这么一个宏,USB_DIR_IN,是用来在管道里面标志数据传输方向的,一个管道要么只能输入,要么只能输出,鱼和熊掌不可兼得也,咱们前面已经介绍过这个宏了。

 

在管道里面,第7位(bit 7)是表示方向的。所以这里0x80也就是说设bit 7为1,这就表示传输方向是由设备向主机的,也就是所谓的IN;而如果这一位是0,就表示传输方向是由主机向设备的,也就是所谓的OUT。而正是因为USB_DIR_OUT是0,而USB_DIR_IN是1,所以我们看到定义管道时只有用到了USB_DIR_IN,而没有用到USB_DIR_OUT,因为它是0,任何数和0相或都没有意义。

 

这样,咱们就知道了,get_pipes函数中764行,765行就是为us的控制输入和控制输出管道赋了值,管道是单向的。但是有一个例外,那就是控制端点。控制端点是双向的,比如36号楼201这个端点既可以是往外寄东西,也可以是作为收件人地址。而USB规范规定了,每一个USB设备至少得有一个控制端点,其端点号为0。其他端点有没有得看具体设备而定,但这个端点是不管是什么设备,只要在USB中,那你就得遵守这个规矩,没得商量。所以我们看到764行,765行里传递的endpoint变量值为0。显然其构造的两个管道就是对应这个0号控制端点的。而接下来几行,就是构造bulk管道和中断管道(如果有中断端点的话)。

 

对于批量端点和中断端点(如果有的话),在它们的端点描述符里有一个字段bEndpointAddress,这个字段共八位,但是它包含了挺多信息的,比如这个端点是输入端点还是输出端点,比如这个端点的地址(总线枚举时给它分配的),以及这个端点的端点号。不过要取得它的端点号得用一个掩码USB_ENDPOINT_NUMBER_MASK,让bEndpointAddress和USB_ENDPOINT_NUMBER_MASK相与就能得到它的端点号。(就好比一份藏头诗,你得按着特定的方法才能读懂它,而这里特定的方法就是和USB_ENDPOINT_NUMBER_MASK这个掩码相与就行了。)

 

773行,对于中断端点,您还得使用端点描述符中的bInterval字段,表示端点的中断请求间隔时间。

 

至此,get_pipes函数结束了,信息都保存到了us里面。下面us该发挥它的作用了。回到storage_probe()函数。1005行,把us作为参数传递给了usb_stor_acquire_resources()函数。而这个函数才是故事的高潮。每一个有识之士在看过这个函数之后就会豁然开朗,都会感慨,一下子就看到了春天,看到了光明,原来Linux中的设备驱动程序就是这么工作的啊!

 

1004    /* Acquireall the other resources and add the host */

 

1005  result = usb_stor_acquire_resources(us);

 

1006    if(result)

 

1007       goto BadDevice;

 

1008   result = scsi_add_host(host,&intf->dev);

 

1009   if (result) {

 

1010        printk(KERN_WARNING USB_STORAGE

 

1011                        "Unable to add the scsi host/n");

 

1012         goto BadDevice;

 

1013  }

 

我们来看usb_stor_acquire_resources函数。它被定义于drivers/usb/storage/usb.c中:

 

778 /* Initialize all the dynamic resources weneed */

 

779 static int usb_stor_acquire_resources(structus_data *us)

 

780 {

 

781 int p;

 

782   struct task_struct *th;

 

783

 

784    us->current_urb= usb_alloc_urb(0, GFP_KERNEL);

 

785   if (!us->current_urb) {

 

786        US_DEBUGP("URB allocation failed/n");

 

787       return -ENOMEM;

 

788   }

 

789

 

790    /* Justbefore we start our control thread, initialize

 

791  *the device if it needs initialization */

 

792   if(us->unusual_dev->initFunction) {

 

793       p = us->unusual_dev->initFunction(us);

 

794        if (p)

 

795            return p;

 

796   }

 

797

 

798     /*Start up our control thread */

 

799  th = kthread_create(usb_stor_control_thread,us, "usb-storage");

 

800    if(IS_ERR(th)) {

 

801         printk(KERN_WARNING USB_STORAGE

 

802                       "Unable to start control thread/n");

 

803         return PTR_ERR(th);

 

804   }

 

805

 

806    /* Take areference to the host for the control thread and

 

807     * count it among all the threads we havelaunched.  Then

 

808    * start it up. */

 

809   scsi_host_get(us_to_host(us));

 

810    atomic_inc(&total_threads);

 

811 wake_up_process(th);

 

812

 

813    return 0;

 

814 }

 

“待到山花浪漫时,她在丛中笑”。一个悟性高的人应该一眼就能从这个函数中找出那行“在丛中笑”的代码来,没错,它就是799行,kthread_create(),这个函数造就了许多经典的Linux内核模块,正是因为它的存在,Linux中某些设备驱动程序的编写变得非常简单。

 

可以说,对某些设备驱动程序来说,kthread_create()几乎是整个驱动的灵魂,或者说是该Linux内核模块的灵魂。不管它隐藏得多么深,她总像漆黑中的萤火虫,那样鲜明,那样出众。甚至不夸张地说,对于很多模块来说,只要找到kthread_create()这一行,基本上你就知道这个模块是怎么工作的了