发布时间: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个函数说再见了,席慕蓉说过,若不得不分离,也要好好地说声再见,也要在心里存着一份感谢,谢谢她给你一份记忆
linux一键安装web环境全攻略 在linux系统中怎么一键安装web环境方法
Linux网络基本网络配置方法介绍 如何配置Linux系统的网络方法
Linux下DNS服务器搭建详解 Linux下搭建DNS服务器和配置文件
对Linux进行详细的性能监控的方法 Linux 系统性能监控命令详解
linux系统root密码忘了怎么办 linux忘记root密码后找回密码的方法
Linux基本命令有哪些 Linux系统常用操作命令有哪些
Linux必学的网络操作命令 linux网络操作相关命令汇总
linux系统从入侵到提权的详细过程 linux入侵提权服务器方法技巧
linux系统怎么用命令切换用户登录 Linux切换用户的命令是什么
在linux中添加普通新用户登录 如何在Linux中添加一个新的用户
2012-07-10
CentOS 6.3安装(详细图解教程)
Linux怎么查看网卡驱动?Linux下查看网卡的驱动程序
centos修改主机名命令
Ubuntu或UbuntuKyKin14.04Unity桌面风格与Gnome桌面风格的切换
FEDORA 17中设置TIGERVNC远程访问
StartOS 5.0相关介绍,新型的Linux系统!
解决vSphere Client登录linux版vCenter失败
LINUX最新提权 Exploits Linux Kernel <= 2.6.37
nginx在网站中的7层转发功能