知识屋:更实用的电脑技术知识网站
所在位置:首页 > 操作系统 > linux

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

发布时间:2014-09-05 16:46:58作者:知识屋

 

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

 

672 static int get_protocol(struct us_data *us)

 

673 {

 

674   switch (us->subclass) {

 

675    caseUS_SC_RBC:

 

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

 

677        us->proto_handler = usb_stor_transparent_scsi_command;

 

678         break;

 

679

 

680   case US_SC_8020:

 

681         us->protocol_name = "8020i";

 

682         us->proto_handler = usb_stor_ATAPI_command;

 

683         us->max_lun = 0;

 

684         break;

 

685

 

686    caseUS_SC_QIC:

 

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

 

688       us->proto_handler = usb_stor_qic157_command;

 

689        us->max_lun = 0;

 

690         break;

 

691

 

692    caseUS_SC_8070:

 

693       us->protocol_name = "8070i";

 

694        us->proto_handler = usb_stor_ATAPI_command;

 

695      us->max_lun= 0;

 

696        break;

 

697

 

698    caseUS_SC_SCSI:

 

699       us->protocol_name = "Transparent SCSI";

 

700         us->proto_handler = usb_stor_transparent_scsi_command;

 

701         break;

 

702

 

703    caseUS_SC_UFI:

 

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

 

705         us->proto_handler = usb_stor_ufi_command;

 

706        break;

 

707

 

708 #ifdef CONFIG_USB_STORAGE_ISD200

 

709   case US_SC_ISD200:

 

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

 

711         us->proto_handler = isd200_ata_command;

 

712        break;

 

713 #endif

 

714

 

715   default:

 

716        return -EIO;

 

717    }

 

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

 

719    return 0;

 

720 }

 

这段代码非常浅显易懂。根据us->subclass来判断。对于U盘来说,spec里面规定了,它的SubClass是US_SC_SCSI,所以这里就是两句赋值语句:一个是令us的protocol_name为“Transparent SCSI”,另一个是令us的proto_handler为usb_stor_transparent_scsi_command,后者又是一个函数指针。

 

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

 

723 static int get_pipes(struct us_data *us)

 

724 {

 

725    structusb_host_interface *altsetting =

 

726                us->pusb_intf->cur_altsetting;

 

727    int i;

 

728   struct usb_endpoint_descriptor*ep;

 

729   struct usb_endpoint_descriptor*ep_in = NULL;

 

730   struct usb_endpoint_descriptor*ep_out = NULL;

 

731    structusb_endpoint_descriptor *ep_int = NULL;

 

732

 

733    /*

 

734    * Find the first endpoint of each type we need.

 

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

 

736     * An optional interrupt-in is OK(necessary for CBI protocol).

 

737    * We will ignore any others.

 

738    */

 

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

 

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

 

741

 

742         if (usb_endpoint_xfer_bulk(ep)) {

 

743             if (usb_endpoint_dir_in(ep)) {

 

744                 if (!ep_in)

 

745                    ep_in = ep;

 

746             } else {

 

747                  if (!ep_out)

 

748                      ep_out = ep;

 

749              }

 

750       }

 

751

 

752        else if (usb_endpoint_is_int_in(ep)) {

 

753           if (!ep_int)

 

754                ep_int = ep;

 

755        }

 

756   }

 

757

 

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

 

759        US_DEBUGP("Endpoint sanity check failed! Rejectingdev./n");

 

760         return -EIO;

 

761    }

 

762

 

763   /* Calculate and store the pipevalues */

 

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

 

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

 

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

 

767                ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

 

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

 

769                ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);

 

770   if (ep_int) {

 

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

 

772               ep_int->bEndpointAddress& USB_ENDPOINT_NUMBER_MASK);

 

773         us->ep_bInterval = ep_int->bInterval;

 

774  }

 

775   return 0;

 

776 }

 

这个函数应该可以说是比较复杂的一个。请容我慢慢给您道来。

 

726行,us->pusb_intf,可还记得,在associate_dev中赋的值,如不记得请回过去查一下。没错,us->pusb_intf就是我们故事中最开始一再提到的interface(指针)。而它的成员cur_altsetting,就是当前的设置。在讲associate_dev时也已经遇到过,它是一个structusb_host_interface的结构体指针。现在这里用另一个指针临时代替一下,altsetting。接下来会用到它的成员:desc和endpoint。

 

回顾structusb_host_interface,可以看到它两个成员:struct usb_interface_descriptor desc和struct usb_host_endpoint *endpoint。其中,desc不用多说,正是这个接口的接口描述符,而endpoint这个指针记录的是几个端点,它们以数组的形式被存储,而endpoint指向数组头。这些都是在USB Core枚举时就设置好了,我们无需操任何心,只需拿来使用就是了。这里给出struct usb_host_endpoint的定义,来自include/linux/usb.h:

 

59 struct usb_host_endpoint {

 

60    structusb_endpoint_descriptor  desc;

 

61     structlist_head               urb_list;

 

62     void                           *hcpriv;

 

63   struct ep_device               *ep_dev;        /* For sysfs info */

 

64

 

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

 

66     intextralen;

 

67 };

 

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

 

312 /* USB_DT_ENDPOINT: Endpoint descriptor */

 

313 struct usb_endpoint_descriptor {

 

314  __u8 bLength;

 

315    __u8  bDescriptorType;

 

316

 

317     __u8  bEndpointAddress;

 

318     __u8  bmAttributes;

 

319    __le16wMaxPacketSize;

 

320    __u8  bInterval;

 

321

 

322   /* NOTE:  these two are _only_ in audio endpoints. */

 

323   /* use USB_DT_ENDPOINT*_SIZE inbLength, not sizeof. */

 

324    __u8  bRefresh;

 

325   __u8  bSynchAddress;

 

326 } __attribute__ ((packed));

 

至此,四大描述符一一亮相,在继续讲之前,我们先来小结一下:究竟什么是描述符?每个USB设备都有四大描述符,这里拿U盘来举例。听说过Flash Memory吗?Intel、三星,这些都是做Flash Memory的,当然通常人们就简称Flash。Flash在U盘中扮演什么角色?Flash是用来给用户存储数据的,而U盘中的Flash就相当于PC中的硬盘,存储数据主要就靠它。那么除了给用户存储数据以外,设备自己还需要存储一些设备本身固有的信息,比如设备姓甚名谁?谁生产的?还有一些信息,比如该设备有几种配置,有几个接口,等许多特性。

 

这个世界上,除了Flashmemory外,还有一个东西叫做EEPROM,也是用来存储的,它是EEPROM的前身,而Flash是基于EEPROM技术发展起来的一种低成本的ROM产品。

 

EEPROM和Flash相同,都是需要电擦除,但EEPROM可以按字节擦除,而不像Flash那样一次擦除一个block,这样在只需改动很少数据的情况下使用EEPROM就很方便了。因此EEPROM的这一特性使它的电路要复杂一些,并且集成度不高,一个bit需要两个管子:一个用来储存电荷信息,一个充当开关。所以EEPROM的成本高,Flash简化了一些电路,成本降低了很多。

 

因此,在USB设备中通常会有一个Flash芯片,以及一个EEPROM芯片。Flash用于为客户存储数据,而EEPROM用来存储设备本身的信息。这就是为什么当Intel把Flash芯片卖给摩托罗拉之后,客户看到的手机厂商是摩托罗拉而不是Intel,因为我们虽然在做Flash时把我们的厂商ID写在了Flash上,但是对于最终的成品对外来看,提供的信息都是来自EEPROM,所以当你把USB设备通过USB接口连到电脑上去,电脑上如果能显示厂家,那么一定是最终的包装厂家,而不可能是那块Flash的厂家。而EEPROM里边写什么?按什么格式写?这正是USB spec规定的,这种格式就是一个个描述符的格式。设备描述符、配置描述符、接口描述符、端点描述符,以及其他一些某一些类别的设备特有的描述符,比如Hub描述符都是很规范的,尤其对于这四种标准的描述符,每个USB设备都是规规矩矩地支持的。所以USB Core层可以用一段相同的代码把它们都给读出来,而不用再让我们设备驱动程序去自己读了,这就是权力集中的好处,反正大家都要做的事情,干脆让上头一起做了好了。

 

739行到756行,循环,bNumEndpoints就是接口描述符中的成员,表示这个接口有多少个端点,不过这其中不包括0号端点,0号端点是任何一个USB设备都必须是提供的,这个端点专门用于进行控制传输,即它是一个控制端点。正因为如此,所以即使一个设备没有进行任何设置,USB主机也可以开始跟它进行一些通信,因为即使不知道其他端点,但至少知道它一定有一个0号端点,或者说一个控制端点。

 

此外,通常USB Mass Storage会有两个批量端点,用于批量传输,即所谓的批量传输。我们日常的读写U盘里的文件,就是属于批量传输。所以毫无疑问,对于Mass Storage设备来说,批量传输是它的主要工作方式,道理很简单,我们使用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,只是一个临时指针。

 

我们看structusb_endpoint_descriptor,在它的成员中,bmAttributes表示属性,总共8位,其中bit1和bit0共同称为Transfer Type,即传输类型,00表示控制,01表示等时,10表示批量,11表示中断。因此通过比较这些成员,就可以判断该端点的传输类型,这个函数usb_endpoint_xfer_bulk()定义于include/linux/usb.h中:

 

571 static inline int usb_endpoint_xfer_bulk(conststruct usb_endpoint_ descriptor *epd)

 

572 {

 

573    return((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==

 

574                USB_ENDPOINT_XFER_BULK);

 

575 }

 

而USB_ENDPOINT_XFERTYPE_MASK这个宏定义于include/linux/usb/ch9.h中:

 

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

 

339 #define USB_ENDPOINT_XFER_CONTROL       0

 

340 #define USB_ENDPOINT_XFER_ISOC      1

 

341 #define USB_ENDPOINT_XFER_BULK      2

 

342 #define USB_ENDPOINT_XFER_INT       3

 

从上面的注释可以看出,742行就是判断这个端点描述符描述的是不是一个批量端点。如果是,继续比较。我们先看bEndpointAddress,这个struct usb_endpoint_descriptor中的另一个成员,也是8个bit,或者说1个Byte,其中bit7表示这个端点的方向,0表示OUT,1表示IN。OUT与IN是对主机而言,OUT就是从主机到设备,IN就是从设备到主机。因此比较这个成员就可以判断端点的方向,干这件事的函数usb_endpoint_dir_in()也来自include/linux/usb.h:

 

549 static inline int usb_endpoint_dir_in(conststruct usb_endpoint_ descriptor *epd)

 

550 {

 

551 return ((epd->bEndpointAddress&USB_ENDPOINT_DIR_MASK)==USB_DIR_IN);

 

552 }

 

而宏USB_DIR_IN和USB_ENDPOINT_DIR_MASK仍然来自include/linux/usb_ch9.h:

 

48 #define USB_DIR_OUT                    0              /* to device */

 

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

 

336 #define USB_ENDPOINT_DIR_MASK           0x80

 

所以这里意思很明显,就是为了让ep_in和ep_out指向该指的endpoint descriptor。

 

而接下来752行,usb_endpoint_is_int_in()来自include/linux/usb.h:

 

646 static inline int usb_endpoint_is_int_in(conststruct usb_endpoint_ descriptor *epd)

 

647 {

 

648   return (usb_endpoint_xfer_int(epd)&& usb_endpoint_dir_in(epd));

 

649 }

 

有了前面两个函数作为基础,这个函数就不用再说了。总之,752行中else if的作用就是如果这个端点是中断端点,那么就让ep_int指向它。我们说了,每一类USB设备其上面有多少端点有何种端点都是不确定的,都得遵守该类设备的规范,而USB Mass Storage的规范中规定了,一个USB Mass Storage设备至少应该有两个批量端点,控制端点显然是必需的。毋庸置疑,另外,可能会有一个中断端点,这种设备支持CBI协议,即Control/Bulk/Interrupt协议。我们也说过了,U盘遵守的是Bulk-only协议,它不需要有中断端点。

 

758行到761行这段代码,没什么好说的,就是判断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个函数说再见了,席慕蓉说过,若不得不分离,也要好好地说声再见,也要在心里存着一份感谢,谢谢她给你一份记忆

(免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)
收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜