Linux那些事儿之我是U盘(21)冬天来了,春天还会远吗?(五)

来源:互联网 发布:二手手机评估价格软件 编辑:程序博客网 时间:2024/04/19 14:11

道不尽红尘舍恋诉不完人间恩恩怨怨.

看完了get_transport()继续看get_protocol()get_pipes().仍然是来自drivers/usb/storage/usb.c:

    647 /* Get the protocol settings */

    648 static int get_protocol(struct us_data *us)

    649 {

    650         switch (us->subclass) {

    651         case US_SC_RBC:

    652                 us->protocol_name = "Reduced Block Commands (RBC)";

    653                 us->proto_handler = usb_stor_transparent_scsi_command;

    654                 break;

    655

    656         case US_SC_8020:

    657                 us->protocol_name = "8020i";

    658                 us->proto_handler = usb_stor_ATAPI_command;

    659                 us->max_lun = 0;

    660                 break;

    661

    662         case US_SC_QIC:

    663                 us->protocol_name = "QIC-157";

    664                 us->proto_handler = usb_stor_qic157_command;

    665                 us->max_lun = 0;

    666                 break;

    667

    668         case US_SC_8070:

    669                 us->protocol_name = "8070i";

    670                 us->proto_handler = usb_stor_ATAPI_command;

    671                 us->max_lun = 0;

    672                 break;

    673

    674         case US_SC_SCSI:

    675                 us->protocol_name = "Transparent SCSI";

    676                 us->proto_handler = usb_stor_transparent_scsi_command;

    677                 break;

    678

    679         case US_SC_UFI:

    680                 us->protocol_name = "Uniform Floppy Interface (UFI)";

    681                 us->proto_handler = usb_stor_ufi_command;

    682                 break;

    683

    684 #ifdef CONFIG_USB_STORAGE_ISD200

    685         case US_SC_ISD200:

    686                 us->protocol_name = "ISD200 ATA/ATAPI";

687                 us->proto_handler = isd200_ata_command;

    688                 break;

    689 #endif

    690

    691         default:

    692                 return -EIO;

    693         }

    694         US_DEBUGP("Protocol: %s/n", us->protocol_name);

    695         return 0;

    696 }

这段代码非常的浅显易懂.我相信即使去问上海火车站附近那些卖黑车的哥们儿,他们也能告诉你这段代码做了什么.就一件事,根据us->subclass来判断.对于U盘来说,spec里边规定了,它的subclassUS_SC_SCSI,所以这里就是两句赋值语句.一个是令usprotocol_name"Transparent SCSI",另一个是令usproto_handlerusb_stor_transparent_scsi_command.后者又是一个函数指针,我们日后必将不可避免的遇到这个函数,暂且不表.

然后是get_pipes().drivers/usb/storage/usb.c:

    698 /* Get the pipe settings */

    699 static int get_pipes(struct us_data *us)

    700 {

    701         struct usb_host_interface *altsetting =

    702                 us->pusb_intf->cur_altsetting;

    703         int i;

    704         struct usb_endpoint_descriptor *ep;

    705         struct usb_endpoint_descriptor *ep_in = NULL;

    706         struct usb_endpoint_descriptor *ep_out = NULL;

    707         struct usb_endpoint_descriptor *ep_int = NULL;

    708

    709         /*

    710          * Find the endpoints we need.

    711          * We are expecting a minimum of 2 endpoints - in and out (bulk).

    712          * An optional interrupt is OK (necessary for CBI protocol).

    713          * We will ignore any others.

    714          */

    715         for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {

    716                 ep = &altsetting->endpoint[i].desc;

    717

    718                 /* Is it a BULK endpoint? */

    719                 if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

    720                                 == USB_ENDPOINT_XFER_BULK) {

    721                         /* BULK in or out? */

    722                         if (ep->bEndpointAddress & USB_DIR_IN)

    723                                 ep_in = ep;

    724                         else

    725                                 ep_out = ep;

    726                 }

    727

    728                 /* Is it an interrupt endpoint? */

    729                 else if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

    730                                 == USB_ENDPOINT_XFER_INT) {

    731                         ep_int = ep;

    732                 }

    733         }

    734

    735         if (!ep_in || !ep_out || (us->protocol == US_PR_CBI && !ep_int)) {

    736                 US_DEBUGP("Endpoint sanity check failed! Rejecting dev./n");

    737                 return -EIO;

    738         }

739

    740         /* Calculate and store the pipe values */

    741         us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0);

    742         us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0);

    743         us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev,

    744                 ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

    745         us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev,

    746                 ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

    747         if (ep_int) {

    748                 us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev,

    749                         ep_int->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

    750                 us->ep_bInterval = ep_int->bInterval;

    751         }

    752         return 0;

    753 }

这个函数应该可以说是我们这几个无聊的函数中最后一个了,但它也是相对来说最复杂的一个.请容我慢慢给您道来.702,us->pusb_intf,可还记得,associate_dev中赋得值,如不记得请回过去查一下.没错,us->pusb_intf就是我们故事中最开始一再提到的那个interface(指针).而它的成员cur_altsetting,就是当前的setting,或者说设置.在讲associate_dev的时候也已经遇到过,是一个struct usb_host_interface的结构体指针.现在这里用另一个指针临时代替一下, altsetting.接下来会用到它的成员,descendpoint.回顾struct usb_host_interface,可以看到,它这两个成员,struct usb_interface_descriptor desc,struct usb_host_endpoint *endpoint.其中,desc不用多说,正是这个interface的接口描述符,endpoint这个指针记录的是几个endpoint,它们以数组的形式被存储,endpoint指向数组头.这些冬冬都是在usb core枚举的时候就设置好了,我们无需操任何心,只需拿来用就是了.这里给出struct usb_host_endpoint的定义,来自include/linux/usb.h:

     43 /* host-side wrapper for parsed endpoint descriptors */

     44 struct usb_host_endpoint {

     45         struct usb_endpoint_descriptor  desc;

     46

     47         unsigned char *extra;   /* Extra descriptors */

     48         int extralen;

     49 };

  接着定义了几个struct usb_endpoint_descriptor的结构体指针.顾名思义,这就是对应endpoint的描述符的.其定义来自于include/linux/usb_ch9.h:

    260 /* USB_DT_ENDPOINT: Endpoint descriptor */

    261 struct usb_endpoint_descriptor {

    262         __u8  bLength;

    263         __u8  bDescriptorType;

    264

    265         __u8  bEndpointAddress;

    266         __u8  bmAttributes;

    267         __u16 wMaxPacketSize;

    268         __u8  bInterval;

    269

    270         // NOTE:  these two are _only_ in audio endpoints.

    271         // use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof.

    272         __u8  bRefresh;

    273         __u8  bSynchAddress;

274 } __attribute__ ((packed));

至此,四大描述符一一亮相,在继续讲之前,我们先来小结一下:究竟什么是描述符?每个USB设备都有这四大描述符,不过我们拿U盘来说.听说过Flash Memory?Intel,三星,这些都是做Flash Memory,当然通常人们就简称Flash. FlashU盘中扮演什么角色?Flash是用来给用户存储数据的,U盘中的Flash就相当于PC机中的硬盘,存储数据主要就靠它.那么除了给用户存储数据以外,设备自己还需要存储一些设备本身固有的东西,比如设备姓甚名谁?谁生产的?还有一些信息,比如该设备有几种配置,有几个接口,等等许多特性,这些东西怎么办?复旦大学97电工四大才子之一,我在Intel的老师加师兄曾经这样对我说:这个世界上,除了Flash memory,还有一个咚咚叫做EEPROM,也是用来存储的,它是EEPROM的前身, Flash是基于EEPROM技术发展起来的一种低成本的ROM产品. EEPROMFlash相同,都是需要电擦除,EEPROM可以按字节擦除,而不向Flash那样一次擦除一个block,这样在只需改动很少数据的情况下使用EEPROM就很方便了.因此EEPROM的这一特性,它的电路要复杂些,集成度不高,一个bit需要两个管子,一个用来储存电荷信息,一个充当开关.所以EEPROM的成本高,Flash简化了一些电路,成本降低了很多.因此,通常,USB设备里边,会有一个Flash芯片,会有一个EEPROM芯片,Flash给客户存储数据,EEPROM用来存储设备本身的信息.这就是为什么当我们把Flash芯片卖给Motorola之后,客户看到的手机厂商是摩托罗拉而不是我们Intel,因为我们虽然在做Flash的时候把我们的厂商ID写在了Flash,但是最终的成品对外来看,提供的信息都是来自EEPROM,所以当你把USB设备通过USB接口连到电脑上去,那么电脑上如果能显示厂家,那么一定是最终的包装厂家,而不可能是里边那块Flash的厂家.EEPROM里边写什么?按什么格式写?这正是usb spec规定的,这种格式就是一个个的描述符的格式.设备描述符,配置描述符,接口描述符,端点描述符,以及其它一些某一些类别的设备特有的描述符,比如hub描述符.这些东西都是很规范的,尤其对于这四种标准的描述符,每个usb设备都是规规矩矩的支持的,所以usb core层可以用一段相同的代码把它们都给读出来,而不用再让我们设备驱动程序去自己读了,这就是权力集中的好处,反正大家都要做的事情,干脆让上头一起做了好了,这样的领导真是好啊!

715733,循环, bNumEndpoints就是接口描述符中的成员,表示这个接口有多少个端点,不过这其中不包括0号端点,0号端点是任何一个usb设备都必须是提供的,这个端点专门用于进行控制传输,即它是一个控制端点.正因为如此,所以即使一个设备没有进行任何设置,usb主机也可以开始跟它进行一些通信,因为即使不知道其它的端点,但至少知道它一定有一个0号端点,或者说一个控制端点.此外,通常usb mass storage会有两个bulk端点,用于bulk传输,即所谓的批量传输.我们日常的读写U盘里的文件,就是属于批量传输,所以毫无疑问,对于mass storage设备来说,bulk传输是它的主要工作方式,道理很简单,我们使用U盘就是用来读写文件的,谁没事天天去读它的这种描述符那种描述符呢,吃错药了?和这些描述符打交道无非就是为了帮助我们最终实现读写文件的工作,这才是每一个usb存储设备真正的使命.

于是我们来这段循环到底在干嘛, altsetting->endpoint[i].desc,对照struct usb_host_endpoint这个结构体的定义,可知,desc正是一个struct usb_endpoint_descriptor的变量.咱们刚刚定义了四个这种结构体的指针,ep,ep_in,ep_out,ep_int,很简单,就是用来记录端点描述符的,ep_in用于bulk-in,ep_out用于bulk-out,ep_int用于记录中断端点(如果有的话).ep,只是一个临时指针.

我们看struct usb_endpoint_descriptor,它的成员中, bmAttributes表示属性,总共8,其中bit1bit0共同称为Transfer Type,即传输类型,00表示控制,01表示等时,10表示批量,11表示中断.719行我们看到, USB_ENDPOINT_XFERTYPE_MASK这个宏定义于include/linux/usb_ch9.h:

    286 #define USB_ENDPOINT_XFERTYPE_MASK      0x03    /* in bmAttributes */

    287 #define USB_ENDPOINT_XFER_CONTROL       0

    288 #define USB_ENDPOINT_XFER_ISOC          1

    289 #define USB_ENDPOINT_XFER_BULK          2

    290 #define USB_ENDPOINT_XFER_INT           3

懂一点C语言的人就不难理解,719行就是判断这个端点描述符描述的是不是一个Bulk端点,如果是,继续比较,我们先看bEndpointAddress,这个struct usb_endpoint_descriptor中的另一个成员,也是8bit,或者说1byte,其中bit7表示的是这个端点的方向,0表示OUT,1表示IN,OUTIN是对主机而言.OUT就是从主机到设备,IN就是从设备到主机.而宏USB_DIR_IN仍然来自include/linux/usb_ch9.h

     25 /*

     26  * USB directions

     27  *

     28  * This bit flag is used in endpoint descriptors' bEndpointAddress field.

     29  * It's also one of three fields in control requests bRequestType.

     30  */

     31 #define USB_DIR_OUT                     0               /* to device */

     32 #define USB_DIR_IN                      0x80            /* to host */

所以这里意思很明显,就是为了让ep_inep_out指向该指的endpoint descriptor.

729就不用再说了,如果这还看不懂的话,可以考虑去复旦的绝情谷找到那棵老树上吊自杀了,可惜如今学校里不断搞建设,绝情谷可能已经不复存在了.729这一个else if的作用就是如果这个端点是中断端点,那么就让ep_int指向它.我们说了,每一类usb其上面有多少端点有何种端点都是不确定的,都得遵守该类设备的规范,usb mass storage的规范说了,一个usb mass storage设备至少应该有两个bulk端点,除此之外,那个控制端点显然是必须的,毋庸置疑,另外,可能会有一个中断端点,这种设备支持CBI协议,Control/Bulk/Interrupt协议.我们也说过了,U盘遵守的是Bulk-only协议,它不需要有中断端点.

735738这段代码,没啥好说的,就是判断是否ep_in或者ep_out不存在,或者是遵守CBI协议但是没有中断端点,这些都是不合理的,当然就会出错啰!

剩下一小段代码,我们下节再看.需要说的是,这个函数结束之后我们将开始最精彩的部分,它就是伟大的usb_stor_acquire_resources().黑暗即将过去,黎明已经带我们上路.让我们共同期待吧.同时,我们小结一下,此前我们花了很大的篇幅来为usb_stor_acquire_resources()做铺垫,那我们来回顾一下,究竟做了哪些事情?

首先我们从storage_probe出发,一共调用了五个函数,它们是assocaite_dev,get_device_info,get_transport,get_protocol,get_pipes.我们这样做的目的是什么?很简单,就是为了建立一个数据结构,它就是传说中的struct us_data,它的名字叫做us.我们把她建立了起来,为她申请了内存,为她的各个元素赋了值,目的就是为了让以后我们可以很好的利用她.这五个函数都不难,你一定也会写.难的是如何去定义struct us_data,别忘了这个数据结构是写代码的同志们专门为usb-storage模块而设计的.子曾经曰过,所谓编程,无非就是数据结构加上算法.没错,这个定义于drivers/usb/storage/usb.h中的数据结构长达60,她正是整部戏的主角.关于她的成员,我们还有很多没遇到,不过别急,后面会遇到的.好了,虽然get_pipes还有一小段没讲,但是我们可以提前和这5个函数说再见了,席慕蓉说过,若不得不分离,也要好好的说声再见,也要在心里存着一份感谢,谢谢她给你一份记忆.