您的位置:首页 > 运维架构 > Linux

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

2007-07-02 22:08 579 查看
道不尽红尘舍恋诉不完人间恩恩怨怨.
看完了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里边规定了,它的subclass是US_SC_SCSI,所以这里就是两句赋值语句.一个是令us的protocol_name为"Transparent SCSI",另一个是令us的proto_handler为usb_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.接下来会用到它的成员,desc和endpoint.回顾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. Flash在U盘中扮演什么角色?Flash是用来给用户存储数据的,而U盘中的Flash就相当于PC机中的硬盘,存储数据主要就靠它.那么除了给用户存储数据以外,设备自己还需要存储一些设备本身固有的东西,比如设备姓甚名谁?谁生产的?还有一些信息,比如该设备有几种配置,有几个接口,等等许多特性,这些东西怎么办?复旦大学97电工四大才子之一,我在Intel的老师加师兄曾经这样对我说:这个世界上,除了Flash memory外,还有一个咚咚叫做EEPROM,也是用来存储的,它是EEPROM的前身, 而Flash是基于EEPROM技术发展起来的一种低成本的ROM产品. EEPROM和Flash相同,都是需要电擦除,但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层可以用一段相同的代码把它们都给读出来,而不用再让我们设备驱动程序去自己读了,这就是权力集中的好处,反正大家都要做的事情,干脆让上头一起做了好了,这样的领导真是好啊!
715到733行,循环, 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位,其中bit1和bit0共同称为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中的另一个成员,也是8个bit,或者说1个byte,其中bit7表示的是这个端点的方向,0表示OUT,1表示IN,OUT与IN是对主机而言.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_in和ep_out指向该指的endpoint descriptor.
729就不用再说了,如果这还看不懂的话,可以考虑去复旦的绝情谷找到那棵老树上吊自杀了,可惜如今学校里不断搞建设,绝情谷可能已经不复存在了.729这一个else if的作用就是如果这个端点是中断端点,那么就让ep_int指向它.我们说了,每一类usb其上面有多少端点有何种端点都是不确定的,都得遵守该类设备的规范,而usb mass storage的规范说了,一个usb mass storage设备至少应该有两个bulk端点,除此之外,那个控制端点显然是必须的,毋庸置疑,另外,可能会有一个中断端点,这种设备支持CBI协议,即Control/Bulk/Interrupt协议.我们也说过了,U盘遵守的是Bulk-only协议,它不需要有中断端点.
735到738这段代码,没啥好说的,就是判断是否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个函数说再见了,席慕蓉说过,若不得不分离,也要好好的说声再见,也要在心里存着一份感谢,谢谢她给你一份记忆.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐