USB主控器规范包括USB1.1主控器规范和USB2.0主控器规范。USB1.1主控器规范有包括UHCI(Universal Host Controller Interface)和OHCI(Open Host Controller Interface Specification);USB2.0主控器规范为EHCI(Enhanced Host Controller Interface Specification)。UHCI和OHCI在硬件实现以及对底层软件访问上都有所不同,但二者又都完全USB 1.1中对主控制器的要求。
USB主控制器驱动程序的整个系统框架图如图4所示,从图中可以看出USB驱动程序包括客户驱动、通用总线驱动程序、EHCI驱动程序等组成。其中,客户驱动程序是特定USB设备的驱动程序,提供了USB设备的功能操作及特定子类协议封装;USB驱动程序(USBD)是特定操作系统上抽象出的主机控制器驱动程序共有特性,对应于Linux USB驱动程序的HCD层;EHCI控制器驱动程序(EHCD)是依赖于特定硬件寄存器接口定义的主控制器驱动程序。USB设备是执行终端用户功能硬件设备。
EHCI通用构架如图5所示。每个EHCI接口定义了三个接口空间,该个接口空间说明如下:
EHCI支持两种类型传输:异步类型和周期类型。周期类型包括同步传输和中断传输,异步类型包括控制传输和批量传输。EHCI调度接口给两种类型提供了分开的调度。周期调度基于时间发起的帧链表,它代表主机控制器工作条目的滑动窗口。所有的同步和中断传输都通过周期调度来进行。
异步调度是简单的调度工作条目的循环链表,它给所有异步传输提供了循环调度服务。
EHCI使用一个简单的buffer队列数据结构来管理所有的中断、批量和控制传输类型。排队的数据结构提供了自动的、排序的数据传输流。软件能异步地加数据buffer到一个队列并维护数据流。USB定义的短包语法在没有软件干预下完全支持所有的边界条件处理。
USB总线的主机控制器要求应用根集线器,主机控制器模拟了根集线器,它在操作寄存器空间装有端口寄存器,寄存器含有在USB规范中需要管理每个端口的最小硬件状态和控制。事务通过根端口被广播下流的USB设备,端口寄存器提供给系统软件对端口的管理和端口的状态信息,包括:设备的连接与断开、执行设备复位、处理端口功率和端口电源管理。
EHCI控制器提供了两套软件可访问的寄存器:内存映射的主机控制器寄存器和可选的PCI配置寄存器。PCI配置寄存器仅是用到主机控制器的PCI设备需要的。
主机控制器能力寄存器定义了限制、主机控制器使用的能力,如:下行端口数、主机控制器的接口版本号、同步调度门限等。在代码中使用结构ehci_caps来描述。
主机控制器操作寄存器位于能力寄存器之后,是双字对齐读写寄存器。这些寄存器分为两套,第一套从地址00到3Fh,在主控制器核心电源好的情况下使用,包括USB控制命令、状态、中断使能、帧序号寄存器。第二套寄存器从40h到可使用的寄存器空间结尾,在外围辅助电源好的情况下使用,包括每个端口的状态与控制寄存器。在代码中使用结构ehci_regs来描述。
接口数据结构在hcd软件和ehci控制器硬件之间用于通信控制、数据和状态。接口由周期调度、周期帧链表、异步调度、同步事务描述子(iTD)、分离事务同步传输描述子(siTD)、队列头(QH)和队列元素传输描述子(qTD)组成。在代码中,qTD用结构ehci_qtd描述,QH用结构ehci_qh描述。iTD用结构ehci_itd描述,siTD用结构ehci_sitd描述。
EHCI主机控制器带有一个模拟操作的根集线器,通过寄存器可完成对根集线器的各个端口的状态及连接控制,因此,它不会调用到USB核心层中有关HUB的操作函数。
EHCI主机控制器对于URB的提交排队及传输、调度以及控制器的各种状态转移提供了控制。特别是寄存器级的控制函数与EHCI控制器本身结构相关,牵涉到对众多寄存器值的理解,因而这里只说明了ehci控制器的上层功能函数。
EHCI驱动程序的编写思路是:EHCI驱动程序是一个结构hc_driver实例,它应该实现结构hc_driver中的函数,另外,从硬件上层来看,EHCI主控制从PCI总线桥接,应是一个PCI驱动程序实例,因此,应实现结构pci_driver中的函数,并用PCI注册函数pci_register_driver注册此实例。
函数__init init注册了&ehci_pci_driver控制器驱动程序,由于ehci-hcd是通过PCI总线与CPU相连,因而,它被注册成一个新的PCI驱动程序。
函数__init init分析如下(在drivers/usb/host/ehci-hcd.c中):
PCI驱动程序结构实例ehci_pci_driver的一些函数定义如下:
pci_ids 是PCI驱动程序选择元数据,PCI热插拔使用到它,通过它来选择驱动程序ehci_driver。pci_ids列出如下:
ehci_driver是主机控制器结构hc_driver实例,它描述了EHCI控制器信息及各种操作函数,每个主机控制器都有一个这样的结构。ehci_driver列出如下:
下面分析只分析结构实例ehci_pci_driver中的探测函数usb_hcd_pci_probe:
函数 usb_hcd_pci_probe初始化基于PCI的HCD(主机控制器驱动程序),参数dev是被探测的USB主机控制器,参数id是连接控制器到HCD构架的pci热插拔设备ID。这个函数不能从中断上下文中调用。
函数 usb_hcd_pci_probe作为probe()存在HCD的pci_driver结构中,它分配基本的PCI资源给这个USB控制器:它分配一个PCI资源区域、进行I/O映射、创建usb_hcd结构实例并赋上设备操作函数集&usb_hcd_operations,给hcd创建DMA缓冲池,申请中断,注册总线。通过与HCD相关的hotplug条目.driver_data为HCD触发start()方法。
函数 usb_hcd_pci_probe分析如下(在drivers/usb/core/hcd-pci.c中):
函数 usb_create_hcd创建并初始化一个结构usb_hcd实例,参数driver是此HCD使用的HCD驱动程序。如果内存不可用,返回NULL。
函数 usb_create_hcd列出如下(在drivers/usb/core/hcd.c中):
USB大存储(USB Mass Storage)工作组(CWG Class Working Group)规范包括:
其中,CBI传输规范仅用于全速软盘驱动器,不能用于高速设备或其它非软盘设备。
USB大存储类使用几种命令集规范,这些命令集的命令块放在符合USB协议的USB包裹器中,USB大存储类规范定义了下面几种命令集:
USB大存储类设备的接口描述子包含了一个bInterfaceSubClass和bInterfacePortocal的域,bInterfaceSubClass描述了USB大存储类支持的命令块规范,如:它为06h时表示支持的是SCSI传输命令集。bInterfacePortocal描述了USB大存储类支持的接口传输协议,如:它为50h时表示支持的是Bulk-Only传输协议。
大存储设备(Mass Storage)包括U盘、读卡器及USB接口的光驱等其它块存储设备,它们看作是SCSI接口设备,当用户从设备上读写数据时,文件系统将读写操作传送到SCSI协议层,SCSI协议层的读写请求封装成USB请求块(URB)通过USB接口传递给设备,USB设备从URB中解析出SCSI协议命令后再操作块设备。USB接口大存储设备的操作流程图如图6所示。
USB接口大存储设备驱动程序的设计思路是:设计一个控制线程,这个线程被注册为虚拟SCSI控制器,这个线程在设备插入/移去时一直作为SCSI节点存在的。这样,被移去的设备能在再插上时被给以与以前/dev中同一节点。当一个设备被插上时,控制线程从SCSI中间层代码得到命令。控制线程接收命令,在检查后送命令到协议处理函数。这些处理函数负责再写命令(如果必要)到设备得接受的形式。例如:ATAPI设备不能支持6byte命令,这样,它们必须被再写成10byte变量。一旦协议处理函数已再写了命令,它们被送到传输处理函数。传输处理函数负责送命令到设备、交换数据、并接着得到设备的状态。在协议处理函数和传输处理函数之间有一小段代码,来决定REQUEST_SENSE命令是否应该发出。在命令被处理后,scsi_done()被调用来发信号给SCSI层命令已完成。我们准备接收下一条命令。
作为具有操作系统的智能嵌入设备,它使用了SCSI命令块集与Bulk-only传输协议。它既能作为主机来操作其它USB大存储设备,称为大存储设备主机。同时,也能作为USB大存储设备被其它主机控制。下面对具有linux操作系统的嵌入设备分别就两种模式分别进行分析。
Bulk-Only传输协议是USB大容量存贮器类中的USB批量数据传输协议,它定义了仅通过批量端点传输的命令、数据和状态。它使用命令块数据包裹器(CBW)发送命令,使用命令状态数据包裹器(CSW)接收返回的状态。命令块数据包裹器(CBW)是一个包含命令块和相关信息的数据包。 命令状态数据包(CSW)裹器是一个包含命令块状态的数据包。命令块数据包裹器(CBW)的格式如表1所示。
表1 命令块数据包裹器(CBW)格式表Byte Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0-3 | dCBWSignature | |||||||
4-7 | dCBWTag | |||||||
8-11 (08h-0Bh) |
dCBWDataTransferLength | |||||||
12 (0Ch) |
bmCBWFlags | |||||||
13 (0Dh) |
Reserved(0) | bCBWLUN | ||||||
14 (0Eh) |
Reserved(0) | bCBWCBLength | ||||||
15-30 (0Fh-1Eh) |
CBWCB |
命令块数据包裹器(CBW)用下述数据结构描述(在drivers/usb/storage/transport.h中):
命令状态数据包裹器(CSW)的格式如表2所示。
Byte Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0-3 | dCSWSignature | |||||||
4-7 | dCSWTag | |||||||
8-11(Bh) | dCSWDataResidue | |||||||
12(Ch) | dCSWStatus |
命令状态数据包裹器(CSW)用下述数据结构描述(在drivers/usb/storage/transport.h中):
传输过程是:当传输方向是从设备到主机时,则当CBW发送成功后,设备从设备的In端点读取CBW中规定长度的数据CBWCB;当传输方向是从主机到设备时,则当CBW发送成功后,向设备的Out端点发送CBW中规定长度的数据CBWCB。CBWCB是命令块数据,是遵循某一规范的命令集,如:SCSI-2命令集,最长16字节。
当主机与设备之间的数据传送完毕后,主机还需从设备的In端点读取传送状态,主机根据接收的CSW数据包即可判断出通信是否正常。若返回的结果有错误,还须进行相应的出错处理。
样例:从设备读取数据的传输过程
下面是一个从设备读取数据的传输过程的例子,主机先向端点1发出CBW命令,设备解析CBW解析命令后,从主机指定的端点2将数据传回给主机。在传送成功后,主机又读取端点2的状态CSW。主机从设备读到数据的流程图如下图。从图中可看出,第0到第2包是发送CBW的过程,第3到第5包是读取数据的过程,下面接着的第0到第1包是读取CSW的过程。令牌包和握手包是由控制管道(对应ep0)来发送接收的。
第0到第5包的数据格式图列出如图7所示:
图7 第0到第5包的数据格式图在第1包中,CBW传输了31(1FH)个字节的数据。内容含义是:55 53 42 43 是CBW后面固有的特征码;28 E8 31 FE 是由主机产生的CBWTag;00 02 00 00 是CBW数据传输长度,在此情况下是0000,0200H=512字节;80 是后面固有的标志码;00 是后面固有的CBWLUN;0A 是CBWCB长度,意味着命令描述块(CDB)长度是10字节,其中。28表示对应SCSI协议28h读命令。对于命令块,看下节的SCSI命令描述块的结构。
SCSI协议28h读命令是Read(10),在这个CBW中,要求读取0柱0道1扇区共512字节的MBR数据,前446字节为主引导记录,接着的64字节为DPT(Disk Partition Table盘分区表),最后的2字节"55 AA"为有效结束标志。
在第4包中传输了512字节的数据。
CSW包的数据格式图列出如图8所示:
图8 CSW包的数据格式图CSW数据包传输13(0DH)个字节的数据。内容含义是:55 52 42 53是CSW后面固有的特征码;28 E8 31 FF是主机产生的CSWTag;00 00 00 00是CSW的数据冗余;00 指示在此情况下CSW的状态,此例中为OK。
各种SCSI命令描述块具有相似的结构,SCSI命令描述块的结构如表8所示。
表8一个典型的SCSI命令描述块结构7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
0 | 操作码 | |||||||
1 | 命令的指定参数 |
|||||||
… | ||||||||
… | ||||||||
n-1 | ||||||||
n | 控制字节 |
SCSI命令描述块的结构的各项说明如下:
每个命令的0号字节就是操作码,它定义了命令的类型和长度。它的高3位代表了命令所属的命令组,低5位表示命令本身。每个命令组都有一个命令长度。因而,对命令的第一个字节进行解码以后,目标器就知道这个命令还剩下多少字节。操作码在不同设备上含义是不同的。
SCSI常用命令块有查询、读请求、测试单元准备、禁止媒介删除、读缓冲、写缓冲等。
代表命令组的高3位可以有8个不同的组合,所以可以代表8个命令组,当制造商实现自己的标准的时候,就必须使用6号组或者7号组,实际上,使用6号组或者7号组的情况很少发生。命令组的说明如表9所示。
表9 SCSI命令组说明组 | 操作码 | 说明 |
0 | 00h~1Fh | 6字节命令 |
1 | 20h~3Fh | 10字节命令 |
2 | 40h~5Fh | 10字节命令 |
3 | 60h~7Fh | 保留 |
4 | 80h~9Fh | 16字节命令 |
5 | A0h~BFh | 12字节命令 |
6 | C0h~DFh | 厂商自定 |
7 | E0h~FFh | 厂商自定 |
控制字节的格式如表10所示。SCSI-2中,控制字节仅仅包含了在标准中定义的两位,它们是连接位(Link bit)和标志位(flag bit),而且这两位都是可选的。连接位使你可以将几个命令连接成一个命令链,命令链中的每一个命令被称为连接的命令。从而这些连接的命令就形成了一个连接的I/O过程。这就可以阻止其他I/O过程的命令插入这个已形成命令链的I/O过程,这就是在目标器内的优化方法。举个例子,当一个逻辑数据块需要被读取一修改一写回时,这个做法就变得十分有用。而且,连接的命令允许使用逻辑数据块的相对地址。
表10 控制字节的格式位数 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
厂商自定 | 保留 | ACA | 状态 | 连接 |
标志位必须和连接命令一起使用。这引起在连接的命令执行结束之后发送服务响应LINKED COMMAND COMPLETE(WITH FLAG)(0BH),而不是发送服务响应LINKED COMMAND COMPLETE(OAH)。这样,你就可以在一个命令链中标出一个特定的命令。
在SCSI-3中出现了新的标志位:ACA位。ACA是偶然事件自动通信(auto contingent allegiance)的缩写,它是在命令执行过程中万一发生错误时LUN所采取的一种措施。如果ACA位没有被置"1",那么只要下一个命令从同一个启动器中发出时,该错误状态就被取消。如果ACA位被置"1",它就会阻止取消错误状态的行动并保持这种状态。
每个大存储设备用一个对象结构us_data来描述它的设备、管道、SCSI接口、传输、协议等各方面的信息及处理函数。
结构us_data列出如下(在drivers/usb/storage/usb.h中):
函数usb_stor_init注册和初始化大存储驱动程序。函数usb_stor_init列出如下(在drivers/usb/storage/usb.c中):
大存储设备驱动程序结构实例usb_storage_driver列出如下:
在usb_device_id结构类型数组中storage_usb_ids定义了设备类、子类及命令块集的协议类型。部分列出如下:
函数storage_probe 探测看是否能驱动一个新连接的USB设备。创建了大存储设备控制线程usb_stor_control_thread和SCSI设备后期扫描线程usb_stor_scan_thread。函数storage_probe在控制线程中通过虚拟SCSI主机控制器发送SCSI命令,经Bulk-Only协议封装后,再填充为URB包,传送给USB核心层来发送给设备。函数storage_probe调用层次图如图2所示。下面按照这个图分析函数storage_probe。
函数storage_probe列出如下(在drivers/usb/storage/usb.c中):
函数usb_stor_acquire_resources初始化所有的需要的动态资源,启动控制线程,函数列出如下(在drivers/usb/storage/usb.c中):
SCSI主机模板结构被用来分配SCSI主机,结构实例usb_stor_host_template列出如下(在drivers/usb/storage/scsiglue.c中):
线程函数usb_stor_control_thread分析处理SCSI命令请求描述块srb后,调用协议处理函数us->proto_handler来进行封装传输。函数usb_stor_control_thread列出如下:
对于支持SCSI协议的功能设备来说,us->proto_handler协议处理函数就是函数usb_stor_transparent_scsi_command,该函数把SCSI命令发送到传输层处理。该函数列出如下(在drivers/usb/storage/protocol.c中):
函数usb_stor_invoke_transport是传输例程,它触发传输和基本的错误处理/恢复方法,它被协议层用来实际发送消息到设备并接收响应。
函数usb_stor_invoke_transport列出如下(在drivers/usb/storage/transport.c中):
对于USB Mass Storage相适应的设备来说,us->transport(srb, us)调用的是函数usb_stor_Bulk_transport,该函数列出如下(在drivers/usb/storage/transport.c中):
函数usb_stor_bulk_transfer_sg在批量管道上传输带有整个SCSI命令的数据负载。函数列出如下(在drivers/usb/storage/transport.c中):
函数usb_stor_bulk_transfer_buf通过批量管道传输一个buffer,允许过早结束。返回USB_STOR_XFER_xxx代码。函数列出如下:
函数usb_stor_msg_common 是URB消息提交代码的通用部分。所有的来自USB存储设备(含有处理排队的SCSI命令)驱动程序都必须通过这个函数来处理URB的提交机制。
函数usb_stor_msg_common列出如下: