linux usb mass storage class

if (window.showTocToggle) { var tocShowText = "显示"; var tocHideText = "隐藏"; showTocToggle(); }
ehci-hcd控制器
EHCI构架介绍

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设备是执行终端用户功能硬件设备。


Linux kernel usb mass storage driver 03.gif
图4 USB驱动程序系统框架图

EHCI通用构架如图5所示。每个EHCI接口定义了三个接口空间,该个接口空间说明如下:

  • PCI配置空间包括PCI寄存器,它们用来系统部件枚举和PCI电源管理。
  • 寄存器空间,通常称为I/O空间。它必须被用作内存映射I/O空间。它包括特定应用参数寄存器和能力寄存器、加上可选的控制和状态寄存器。
  • 调度接口空间是特殊分配的内存并且被EHCI驱动程序管理用来周期性或异步调度。
Linux kernel usb massstorage dirver ehci framework.png


图 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驱动程序的编写思路是: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中):

#ifdef CONFIG_PCI #include "ehci-pci.c" #define PCI_DRIVER ehci_pci_driver #endif static int __init ehci_hcd_init ( void ) { int retval = 0 ;  ……  #ifdef PCI_DRIVER //注册驱动程序,初始化ehci_pci_driver并加到内核对象体系中去 retval = pci_register_driver ( &PCI_DRIVER ) ; if (retval < 0 ) goto clean1 ; #endif……clean1 : #endif #ifdef PLATFORM_DRIVER platform_driver_unregister ( &PLATFORM_DRIVER ) ;…… #endif return retval ; }


PCI驱动程序结构实例ehci_pci_driver的一些函数定义如下:

static const char hcd_name [ ] = "ehci_hcd" ; /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver ehci_pci_driver = { . name = ( char * ) hcd_name , . id_table = pci_ids ,  . probe = usb_hcd_pci_probe ,  //探测函数 . remove = usb_hcd_pci_remove ,  //移去设备时的清除函数  #ifdef CONFIG_PM . suspend = usb_hcd_pci_suspend , . resume = usb_hcd_pci_resume , #endif } ;


pci_ids 是PCI驱动程序选择元数据,PCI热插拔使用到它,通过它来选择驱动程序ehci_driver。pci_ids列出如下:

static const struct pci_device_id pci_ids [ ] = { { //处理任何USB 2.0 EHCI控制器 PCI_DEVICE_CLASS ( ( (PCI_CLASS_SERIAL_USB << 8 ) | 0x20 ) , ~ 0 ) , . driver_data = ( unsigned long ) &ehci_driver , } , { /* end: all zeroes */ } } ;


ehci_driver是主机控制器结构hc_driver实例,它描述了EHCI控制器信息及各种操作函数,每个主机控制器都有一个这样的结构。ehci_driver列出如下:

static const struct hc_driver ehci_driver = { . description = hcd_name , . product_desc = "EHCI Host Controller" , . hcd_priv_size = sizeof ( struct ehci_hcd ) ,  /* * 通用与硬件相关联的成员 */ . irq = ehci_irq ,   //中断处理函数 . flags = HCD_MEMORY | HCD_USB2 , //控制器寄存器使用内存|usb2.0  /* * 基本的生命周期操作 */   //初始化HCD和root hub . reset = ehci_hc_reset , //HCD复位到停止状态。 //开始运行,初始化ECHI设备的各种寄存器进入运行状态,   //调用函数hcd_register_root注册根集线器驱动程序。 . start = ehci_start, #ifdef CONFIG_PM . suspend = ehci_suspend , //在所有的设备挂起后调用 . resume = ehci_resume ,  //在所有的设备恢复之前调用 #endif . stop = ehci_stop ,  //HCD停止写内存和I/O操作  /* * 管理i/o请求和相关的设备资源 */ . urb_enqueue = ehci_urb_enqueue ,  //提交URB的具体处理函数 . urb_dequeue = ehci_urb_dequeue , . endpoint_disable = ehci_endpoint_disable ,   /* * 调度支持 */ . get_frame_number = ehci_get_frame ,  //得到当前的帧序号  /* root hub支持,EHCI主机控制器使用了它自己的模拟根集线器,这个集线器通过对寄存器的设置提供了简单的端口状态及连接控制功能。*/ . hub_status_data = ehci_hub_status_data , //端口状态发生变化时,直接控制端口 . hub_control = ehci_hub_control , //hub控制的状态机 . hub_suspend = ehci_hub_suspend , . hub_resume = ehci_hub_resume , //电源恢复,初始化根集线器 } ;


下面分析只分析结构实例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中):

int usb_hcd_pci_probe ( struct pci_dev *dev , const struct pci_device_id *id ) { struct hc_driver *driver ; unsigned long resource , len ; void __iomem *base ; struct usb_hcd *hcd ; int retval , region ; char buf [ 8 ] , *bufp = buf ;  if (usb_disabled ( ) )  //如果没有USB设备 return -ENODEV ; //结构pci_device_id中存有供应商和设备ID, //其成员driver_data指向具体设备驱动程序结构。 if ( !id || ! (driver = ( struct hc_driver * ) id ->driver_data ) ) return -EINVAL ;    //使设备的I/O和设备内存区有效,唤醒设备,在被驱动程序使用前初始化设备 if (pci_enable_device (dev ) < 0 ) return -ENODEV ; dev ->current_state = 0 ; dev ->dev. power. power_state = 0 ;  if ( !dev ->irq ) { //没有中断 dev_err ( &dev ->dev , "Found HC with no IRQ. Check BIOS/PCI %s setup!\n" , pci_name (dev ) ) ; retval = -ENODEV ; goto done ; }  //HC寄存器使用内存 if (driver ->flags & HCD_MEMORY ) { // EHCI, OHCI region = 0 ; resource = pci_resource_start (dev , 0 ) ; //得到PCI设备的0号区域资源 len = pci_resource_len (dev , 0 ) ;     //申请名字为driver->description的I/O内存区域,     //从resource开始,长度为len if ( !request_mem_region (resource , len , driver ->description ) ) { dev_dbg ( &dev ->dev , "controller already in use\n" ) ; retval = -EBUSY ; goto done ; }     //映射resource开始的物理地址到CPU的虚拟地址base base = ioremap_nocache (resource , len ) ; if (base == NULL ) { //映射失败 dev_dbg ( &dev ->dev , "error mapping memory\n" ) ; retval = -EFAULT ;clean_1 : release_mem_region (resource , len ) ;  //释放资源 dev_err ( &dev ->dev , "init %s fail, %d\n" , pci_name (dev ) , retval ) ; goto done ; }  } else { // UHCI resource = len = 0 ;     //标准PCI配置6个region(或说6个bar) for (region = 0 ; region < PCI_ROM_RESOURCE ; region ++ ) { //如果不是IO资源 if ( ! (pci_resource_flags (dev , region ) & IORESOURCE_IO ) ) continue ;  resource = pci_resource_start (dev , region ) ; len = pci_resource_len (dev , region ) ; //申请名字为driver->description的资源 if (request_region (resource , len , driver ->description ) ) break ; } if (region == PCI_ROM_RESOURCE ) { //如果是rom,则说明无资源可用 dev_dbg ( &dev ->dev , "no i/o regions available\n" ) ; retval = -EBUSY ; goto done ; } base = ( void __iomem * ) resource ; }  //创建并初始化结构hcd hcd = usb_create_hcd (driver ) ; …… // hcd zeroed everything hcd ->regs = base ; hcd ->region = region ;  //将hcd驱动程序结构赋给pci设备结构,即dev ->dev->driver_data = hcd pci_set_drvdata (dev , hcd ) ; hcd ->self. bus_name = pci_name (dev ) ; #ifdef CONFIG_PCI_NAMES hcd ->product_desc = dev ->pretty_name ; #endif hcd ->self. controller = &dev ->dev ;  //创建4个DMA池 if ( (retval = hcd_buffer_create (hcd ) ) != 0 ) {clean_3 : pci_set_drvdata (dev , NULL ) ; usb_put_hcd (hcd ) ; goto clean_2 ; }   //打印信息 dev_info (hcd ->self. controller , "%s\n" , hcd ->product_desc ) ;  //到现在为止,HC已在一个不确定状态,调用驱动程序的reset函数复位 if (driver ->reset && (retval = driver ->reset (hcd ) ) < 0 ) { dev_err (hcd ->self. controller , "can't reset\n" ) ; goto clean_3 ; }     //使能设备上的bus-mastering总线 pci_set_master (dev ) ; …   //申请共享中断号,中断处理函数是usb_hcd_irq,设备名为description retval = request_irq (dev ->irq , usb_hcd_irq , SA_SHIRQ , hcd ->driver ->description , hcd ) ; … hcd ->irq = dev ->irq ;  //注册总线到sysfs和/proc文件系统 usb_register_bus ( &hcd ->self ) ; …… return retval ; }


函数 usb_create_hcd创建并初始化一个结构usb_hcd实例,参数driver是此HCD使用的HCD驱动程序。如果内存不可用,返回NULL。

函数 usb_create_hcd列出如下(在drivers/usb/core/hcd.c中):

struct usb_hcd *usb_create_hcd ( const struct hc_driver *driver ) { struct usb_hcd *hcd ;  hcd = kcalloc ( 1 , sizeof ( *hcd ) + driver ->hcd_priv_size , GFP_KERNEL ) ; if ( !hcd ) return NULL ;  usb_bus_init ( &hcd ->self ) ; //初始化usb_bus结构 hcd ->self. op = &usb_hcd_operations ; hcd ->self. hcpriv = hcd ; hcd ->self. release = &hcd_release ;  init_timer ( &hcd ->rh_timer ) ;  hcd ->driver = driver ; hcd ->product_desc = (driver ->product_desc ) ? driver ->product_desc : "USB Host Controller" ; hcd ->state = USB_STATE_HALT ;  return hcd ; }


Mass Storage主机驱动程序
Mass Storage规范介绍

USB大存储(USB Mass Storage)工作组(CWG Class Working Group)规范包括:

  • USB Mass Storage Class Control/Bulk/Interrupt(CBI) Transport 即USB大存储类控制/批量/中断传输协议。
  • USB Mass Storage Class Bulk-Only Transport 即USB大存储类批量传输协议。
  • USB Mass Storage Class UFI Command Specification 即USB大存储类UFI命令规范。
  • USB Mass Storage Class Bootability Specfication 即USB大存储类系统启动规范。
  • USB Mass Storage Class Compliance Test Specification 即USB大存储类遵从测试规范。

其中,CBI传输规范仅用于全速软盘驱动器,不能用于高速设备或其它非软盘设备。

USB大存储类使用几种命令集规范,这些命令集的命令块放在符合USB协议的USB包裹器中,USB大存储类规范定义了下面几种命令集:

  • 软驱、光驱和磁带驱动器使用的ATAPI规范(Advanced Technology Attachment Packet Interface)。
  • 精简块命令(Reduced Block Commands(RBC))
  • 多媒体命令集2(Multi-Media Command Set 2 (MMC-2))
  • SCSI主命令(SCSI Primary Commands-2(SPC-2))
  • USB规范(Universal Serial Bus Specification)

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所示。


Linux kernel usb mass storage driver 02.gif
图6 USB接口大存储设备的操作流程图

USB接口大存储设备驱动程序的设计思路是:设计一个控制线程,这个线程被注册为虚拟SCSI控制器,这个线程在设备插入/移去时一直作为SCSI节点存在的。这样,被移去的设备能在再插上时被给以与以前/dev中同一节点。当一个设备被插上时,控制线程从SCSI中间层代码得到命令。控制线程接收命令,在检查后送命令到协议处理函数。这些处理函数负责再写命令(如果必要)到设备得接受的形式。例如:ATAPI设备不能支持6byte命令,这样,它们必须被再写成10byte变量。一旦协议处理函数已再写了命令,它们被送到传输处理函数。传输处理函数负责送命令到设备、交换数据、并接着得到设备的状态。在协议处理函数和传输处理函数之间有一小段代码,来决定REQUEST_SENSE命令是否应该发出。在命令被处理后,scsi_done()被调用来发信号给SCSI层命令已完成。我们准备接收下一条命令。

作为具有操作系统的智能嵌入设备,它使用了SCSI命令块集与Bulk-only传输协议。它既能作为主机来操作其它USB大存储设备,称为大存储设备主机。同时,也能作为USB大存储设备被其它主机控制。下面对具有linux操作系统的嵌入设备分别就两种模式分别进行分析。

Bulk-Only传输协议介绍

Bulk-Only传输协议是USB大容量存贮器类中的USB批量数据传输协议,它定义了仅通过批量端点传输的命令、数据和状态。它使用命令块数据包裹器(CBW)发送命令,使用命令状态数据包裹器(CSW)接收返回的状态。命令块数据包裹器(CBW)是一个包含命令块和相关信息的数据包。 命令状态数据包(CSW)裹器是一个包含命令块状态的数据包。命令块数据包裹器(CBW)的格式如表1所示。

表1 命令块数据包裹器(CBW)格式表
Byte Bit76543210
0-3dCBWSignature
4-7dCBWTag
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中):

struct bulk_cb_wrap { __le32 Signature ; //签名'USBC' __u32 Tag ; //每个命令唯一的ID __le32 DataTransferLength ; //数据大小 __u8 Flags ; //在bit 0中表示方向 __u8 Lun ; //表示LUN(SCSI逻辑单元)正常为0 __u8 Length ; //数据传输长度 __u8 CDB [ 16 ] ; //传输的命令字节 } ;


命令状态数据包裹器(CSW)的格式如表2所示。

表2 命令状态数据包(CSW)的格式表
Byte Bit76543210
0-3dCSWSignature
4-7dCSWTag
8-11(Bh)dCSWDataResidue
12(Ch)dCSWStatus

命令状态数据包裹器(CSW)用下述数据结构描述(在drivers/usb/storage/transport.h中):

/* 命令状态包裹器*/ struct bulk_cs_wrap { __le32 Signature ; //签名 'USBS' __u32 Tag ; //与CBW中Tag一样 __le32 Residue ; //没有传输完的数据量 __u8 Status ; //操作状态标识,如:成功、失败等 __u8 Filler [ 18 ] ; } ;


 传输过程是:当传输方向是从设备到主机时,则当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)来发送接收的。


Linux kernel usb mass storage driver 01.gif
图 主机从设备读到数据的流程图

第0到第5包的数据格式图列出如图7所示:

Linux kernel usb mass storage driver 05 1024.png

图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所示:

Linux kernel usb mass storage driver 06 1024.png

图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命令描述块具有相似的结构,SCSI命令描述块的结构如表8所示。

表8一个典型的SCSI命令描述块结构
 76543210
0操作码
1

命令的指定参数
n-1
n控制字节

  SCSI命令描述块的结构的各项说明如下:

  • 操作码(Opcode)

  每个命令的0号字节就是操作码,它定义了命令的类型和长度。它的高3位代表了命令所属的命令组,低5位表示命令本身。每个命令组都有一个命令长度。因而,对命令的第一个字节进行解码以后,目标器就知道这个命令还剩下多少字节。操作码在不同设备上含义是不同的。

SCSI常用命令块有查询、读请求、测试单元准备、禁止媒介删除、读缓冲、写缓冲等。

  • 命令组

代表命令组的高3位可以有8个不同的组合,所以可以代表8个命令组,当制造商实现自己的标准的时候,就必须使用6号组或者7号组,实际上,使用6号组或者7号组的情况很少发生。命令组的说明如表9所示。

表9 SCSI命令组说明
操作码说明
000h~1Fh6字节命令
120h~3Fh10字节命令
240h~5Fh10字节命令
360h~7Fh保留
480h~9Fh16字节命令
5A0h~BFh12字节命令
6C0h~DFh厂商自定
7E0h~FFh厂商自定


  • 控制字节

控制字节的格式如表10所示。SCSI-2中,控制字节仅仅包含了在标准中定义的两位,它们是连接位(Link bit)和标志位(flag bit),而且这两位都是可选的。连接位使你可以将几个命令连接成一个命令链,命令链中的每一个命令被称为连接的命令。从而这些连接的命令就形成了一个连接的I/O过程。这就可以阻止其他I/O过程的命令插入这个已形成命令链的I/O过程,这就是在目标器内的优化方法。举个例子,当一个逻辑数据块需要被读取一修改一写回时,这个做法就变得十分有用。而且,连接的命令允许使用逻辑数据块的相对地址。

表10 控制字节的格式
位数76543210
 厂商自定保留ACA状态连接

标志位必须和连接命令一起使用。这引起在连接的命令执行结束之后发送服务响应LINKED COMMAND COMPLETE(WITH FLAG)(0BH),而不是发送服务响应LINKED COMMAND COMPLETE(OAH)。这样,你就可以在一个命令链中标出一个特定的命令。

在SCSI-3中出现了新的标志位:ACA位。ACA是偶然事件自动通信(auto contingent allegiance)的缩写,它是在命令执行过程中万一发生错误时LUN所采取的一种措施。如果ACA位没有被置"1",那么只要下一个命令从同一个启动器中发出时,该错误状态就被取消。如果ACA位被置"1",它就会阻止取消错误状态的行动并保持这种状态。

Mass Storage设备对象结构

每个大存储设备用一个对象结构us_data来描述它的设备、管道、SCSI接口、传输、协议等各方面的信息及处理函数。

结构us_data列出如下(在drivers/usb/storage/usb.h中):

/*我们提供了一个DMA映射I/O buffer给小USB传输使用。CB[I]需要12字节buffer,Bulk-only需要31字节buffer,但Freecom需要64字节buffer,因此,我们分配了64字节的buffer。*/ #define US_IOBUF_SIZE 64   typedef int ( *trans_cmnd ) ( struct scsi_cmnd *, struct us_data * ) ; typedef int ( *trans_reset ) ( struct us_data * ) ; typedef void ( *proto_cmnd ) ( struct scsi_cmnd *, struct us_data * ) ; typedef void ( *extra_data_destructor ) ( void * ) ; //格外的数据析构函数    struct us_data { //工作设备、接口结构及各种管道 struct semaphore dev_semaphore ; //保护pusb_dev struct usb_device *pusb_dev ;    //从类usb_device继承 struct usb_interface *pusb_intf ; //从类usb_interface继承 struct us_unusual_dev *unusual_dev ; //常用的设备链表定义 unsigned long flags ; /* 最初来自过滤器的标识*/ unsigned int send_bulk_pipe ; /* 缓存的管道值*/ unsigned int recv_bulk_pipe ; unsigned int send_ctrl_pipe ; unsigned int recv_ctrl_pipe ; unsigned int recv_intr_pipe ;  //设备的信息 char vendor [USB_STOR_STRING_LEN ] ;  //供应商信息 char product [USB_STOR_STRING_LEN ] ; //产品信息 char serial [USB_STOR_STRING_LEN ] ;  //产品序列号 char *transport_name ;  //传输协议名 char *protocol_name ;  //协议名 u8 subclass ;   //子类 u8 protocol ; u8 max_lun ;  //最大的逻辑单元  u8 ifnum ; //接口数 u8 ep_bInterval ; //中断传输间隔   //设备的函数指针 trans_cmnd transport ;    //传输函数 trans_reset transport_reset ; //传输设备复位 proto_cmnd proto_handler ; //协议处理函数  //SCSI接口 struct Scsi_Host *host ; //虚拟SCSI主机数据结构 struct scsi_cmnd *srb ; //当前SCSI命令描述块  //线程信息 int pid ; //控制线程  //控制和批量通信数据 struct urb *current_urb ; //USB请求 struct usb_ctrlrequest *cr ; //USB控制请求的setup数据 struct usb_sg_request current_sg ; //碎片-收集请求 unsigned char *iobuf ; //I/O buffer dma_addr_t cr_dma ; //控制请求数据buffer的DMA地址 dma_addr_t iobuf_dma ;  // I/O buffer的DMA地址  //互斥保护和同步结构 struct semaphore sema ; /* to sleep thread on */ struct completion notify ; //线程开始/结束时发通知出去 wait_queue_head_t dev_reset_wait ; //在复位期间等待 wait_queue_head_t scsi_scan_wait ; //在SCSI扫描前等待  struct completion scsi_scan_done ; //SCSI扫描线程结束时通知处理函数   //子驱动程序信息 void *extra ; //任何格外的数据 extra_data_destructor extra_destructor ; //格外的数据析构函数  } ;

Mass Storage设备初始化

函数usb_stor_init注册和初始化大存储驱动程序。函数usb_stor_init列出如下(在drivers/usb/storage/usb.c中):

static int __init usb_stor_init ( void ) { int retval ; printk (KERN_INFO "Initializing USB Mass Storage driver...\n" ) ;  //注册驱动程序,如果操作失败,返回负值的错误代码  retval = usb_register ( &usb_storage_driver ) ; if (retval == 0 ) printk (KERN_INFO "USB Mass Storage support registered.\n" ) ;  return retval ; }

大存储设备驱动程序结构实例usb_storage_driver列出如下:

struct usb_driver usb_storage_driver = { . owner = THIS_MODULE , . name = "usb-storage" , . probe = storage_probe ,  //探测并初始化设备 . disconnect = storage_disconnect , //断开连接处理函数 . id_table = storage_usb_ids , } ;

在usb_device_id结构类型数组中storage_usb_ids定义了设备类、子类及命令块集的协议类型。部分列出如下:

static struct usb_device_id storage_usb_ids [ ] = {…… /* Bulk-only transport for all SubClass values */ { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_RBC , US_PR_BULK ) } , { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_8020 , US_PR_BULK ) } , { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_QIC , US_PR_BULK ) } , { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_UFI , US_PR_BULK ) } , { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_8070 , US_PR_BULK ) } , #if !defined(CONFIG_BLK_DEV_UB) && !defined(CONFIG_BLK_DEV_UB_MODULE) { USB_INTERFACE_INFO (USB_CLASS_MASS_STORAGE , US_SC_SCSI , US_PR_BULK ) } , #endif  /* Terminating entry */ { } } ;

探测函数storage_probe分析

函数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。


Linux kernel usb mass storage driver 04.gif
图2 函数storage_probe调用层次图

函数storage_probe列出如下(在drivers/usb/storage/usb.c中):

static int storage_probe ( struct usb_interface *intf , const struct usb_device_id *id ) { struct us_data *us ; const int id_index = id - storage_usb_ids ; int result ;  US_DEBUGP ( "USB Mass Storage device detected\n" ) ;  //分析us_data结构对象空间 us = ( struct us_data * ) kmalloc ( sizeof ( *us ) , GFP_KERNEL ) ; if ( !us ) { printk (KERN_WARNING USB_STORAGE "Out of memory\n" ) ; return -ENOMEM ; } memset (us , 0 , sizeof ( struct us_data ) ) ; init_MUTEX ( & (us ->dev_semaphore ) ) ; init_MUTEX_LOCKED ( & (us ->sema ) ) ; init_completion ( & (us ->notify ) ) ; init_waitqueue_head ( &us ->dev_reset_wait ) ; init_waitqueue_head ( &us ->scsi_scan_wait ) ; init_completion ( &us ->scsi_scan_done ) ;  //将USB设备与结构us_data关联起来   //设置 intf->dev ->driver_data = us,分配buffer result = associate_dev (us , intf ) ; if (result ) goto BadDevice ;  //得到unusual_devs条目和描述子,初始化us。 //id_index与usb_device_id表中序号匹配,找到表中对应的条目。 get_device_info (us , id_index ) ;  #ifdef CONFIG_USB_STORAGE_SDDR09 if (us ->protocol == US_PR_EUSB_SDDR09 ||  //SDDR-09 的SCM-SCSI桥  us ->protocol == US_PR_DPCM_USB ) { // CB/SDDR09混合体 //设置配置,STALL在这儿是一个可接受的反应  if (us ->pusb_dev ->actconfig ->desc. bConfigurationValue != 1 ) { US_DEBUGP ( "active config #%d != 1 ??\n" , us ->pusb_dev ->actconfig ->desc. bConfigurationValue ) ; goto BadDevice ; }     //重置配置,重新初始化端点及接口 result = usb_reset_configuration (us ->pusb_dev ) ; …… } #endif  //将传输方式、协议和管道设置赋给us result = get_transport (us ) ; if (result ) goto BadDevice ; result = get_protocol (us ) ; if (result ) goto BadDevice ; result = get_pipes (us ) ; if (result ) goto BadDevice ;  //初始化所有需要的动态资源  result = usb_stor_acquire_resources (us ) ; if (result ) goto BadDevice ; result = scsi_add_host (us ->host , &intf ->dev ) ; if (result ) { printk (KERN_WARNING USB_STORAGE "Unable to add the scsi host\n" ) ; goto BadDevice ; }  /*线程usb_stor_scan_thread执行延迟的SCSI设备扫描工作,扫描给定的适配器us->host,扫描通道及目标,扫描探测LUN。*/  result = kernel_thread (usb_stor_scan_thread , us , CLONE_VM ) ; if (result < 0 ) { printk (KERN_WARNING USB_STORAGE "Unable to start the device-scanning thread\n" ) ; scsi_remove_host (us ->host ) ; goto BadDevice ; }  return 0 ; …… }

函数usb_stor_acquire_resources初始化所有的需要的动态资源,启动控制线程,函数列出如下(在drivers/usb/storage/usb.c中):

static int usb_stor_acquire_resources ( struct us_data *us ) { int p ;  us ->current_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;  //分配urb结构对象空间 if ( !us ->current_urb ) { US_DEBUGP ( "URB allocation failed\n" ) ; return -ENOMEM ; }  //当我们执行下两个操作时锁住设备。  down ( &us ->dev_semaphore ) ;  //仅对于批量设备,得到最大逻辑单元值,   //在SCSI协议模型中,每个逻辑单元用来操作SCSI设备。 if (us ->protocol == US_PR_BULK ) { p = usb_stor_Bulk_max_lun (us ) ; if (p < 0 ) { up ( &us ->dev_semaphore ) ; return p ; } us ->max_lun = p ; }  //如果设备需要初始化,在开始控制线程前初始化设备 if (us ->unusual_dev ->initFunction ) us ->unusual_dev ->initFunction (us ) ;  up ( &us ->dev_semaphore ) ;  //因为这是一个新设备,我们需要注册一个设备的虚拟SCSI控制器,   //用来处理SCSI层高层协议。 us ->host = scsi_host_alloc ( &usb_stor_host_template , sizeof (us ) ) ; if ( !us ->host ) { printk (KERN_WARNING USB_STORAGE "Unable to allocate the scsi host\n" ) ; return -EBUSY ; }  //设置为SCSI扫描准备的hostdata us ->host ->hostdata [ 0 ] = ( unsigned long ) us ;  //启动控制线程 p = kernel_thread (usb_stor_control_thread , us , CLONE_VM ) ; if (p < 0 ) { printk (KERN_WARNING USB_STORAGE "Unable to start control thread\n" ) ; return p ; } us ->pid = p ;  //等待线程启动 wait_for_completion ( & (us ->notify ) ) ;  return 0 ; }

SCSI主机模板结构被用来分配SCSI主机,结构实例usb_stor_host_template列出如下(在drivers/usb/storage/scsiglue.c中):


struct scsi_host_template usb_stor_host_template = { //基本的用户使用的接口 . name = "usb-storage" , . proc_name = "usb-storage" , . proc_info = proc_info , . info = host_info ,  //命令接口,仅用于排队 . queuecommand = queuecommand ,  //错误及错误退出处理函数 . eh_abort_handler = command_abort , . eh_device_reset_handler = device_reset , . eh_bus_reset_handler = bus_reset ,  //排队命令数,每个LUN仅一个命令。  . can_queue = 1 , . cmd_per_lun = 1 ,  /* unknown initiator id */ . this_id = - 1 ,  . slave_alloc = slave_alloc , . slave_configure = slave_configure ,  //设置一些限制  //能被处理的碎片收集片断数 . sg_tablesize = SG_ALL ,  //一次传输的总大小限制到240扇区,即120 KB . max_sectors = 240 ,  //融合命令... . use_clustering = 1 ,  //模拟HBA . emulated = 1 ,  //当设备或总线复位后做延迟操作。  . skip_settle_delay = 1 ,  //sysfs设备属性 . sdev_attrs = sysfs_device_attr_list ,  /* 用于内核模块管理 */ . module = THIS_MODULE } ;

线程函数usb_stor_control_thread分析处理SCSI命令请求描述块srb后,调用协议处理函数us->proto_handler来进行封装传输。函数usb_stor_control_thread列出如下:

static int usb_stor_control_thread ( void * __us ) { struct us_data *us = ( struct us_data * )__us ; struct Scsi_Host *host = us ->host ;  lock_kernel ( ) ;  //线程后台化,定向成从init进程继承,这样就去掉了不需要的进程资源 daemonize ( "usb-storage" ) ;  current ->flags |= PF_NOFREEZE ;  unlock_kernel ( ) ;  //发信号表示我们已开始了这个线程 complete ( & (us ->notify ) ) ;  for ( ;; ) { US_DEBUGP ( "*** thread sleeping.\n" ) ; if (down_interruptible ( &us ->sema ) ) break ;  US_DEBUGP ( "*** thread awakened.\n" ) ;  /* lock the device pointers */ down ( & (us ->dev_semaphore ) ) ;  //如果us->srb是NULL, 线程被请求退出。 if (us ->srb == NULL ) { US_DEBUGP ( "-- exit command received\n" ) ; up ( & (us ->dev_semaphore ) ) ; break ; }  //锁住SCSI主机控制器 scsi_lock (host ) ;  //命令超时 if (test_bit (US_FLIDX_TIMED_OUT , &us ->flags ) ) { us ->srb ->result = DID_ABORT << 16 ; goto SkipForAbort ; }  //如果USB总线是断开状态,就不做任何事。  if (test_bit (US_FLIDX_DISCONNECTING , &us ->flags ) ) { US_DEBUGP ( "No command during disconnect\n" ) ; goto SkipForDisconnect ; }  scsi_unlock (host ) ;  //如果方向标识是未知的,拒绝命令。  if (us ->srb ->sc_data_direction == DMA_BIDIRECTIONAL ) { US_DEBUGP ( "UNKNOWN data direction\n" ) ; us ->srb ->result = DID_ERROR << 16 ; }  //如果target != 0 或LUN超过最大的已知LUN数,拒绝命令。  else if (us ->srb ->device ->id && ! (us ->flags & US_FL_SCM_MULT_TARG ) ) { US_DEBUGP ( "Bad target number (%d:%d)\n" , us ->srb ->device ->id , us ->srb ->device ->lun ) ; us ->srb ->result = DID_BAD_TARGET << 16 ; }  else if (us ->srb ->device ->lun > us ->max_lun ) { US_DEBUGP ( "Bad LUN (%d:%d)\n" , us ->srb ->device ->id , us ->srb ->device ->lun ) ; us ->srb ->result = DID_BAD_TARGET << 16 ; }  //处理需要伪装它们的查询数据的设备  else if ( (us ->srb ->cmnd [ 0 ] == INQUIRY ) && (us ->flags & US_FL_FIX_INQUIRY ) ) { unsigned char data_ptr [ 36 ] = { 0x00 , 0x80 , 0x02 , 0x02 , 0x1F , 0x00 , 0x00 , 0x00 } ;  US_DEBUGP ( "Faking INQUIRY command\n" ) ; fill_inquiry_response (us , data_ptr , 36 ) ; us ->srb ->result = SAM_STAT_GOOD ; }  //得到一个命令,按照功能设备支持的协议来转换SCSI命令 else { US_DEBUG (usb_stor_show_command (us ->srb ) ) ; us ->proto_handler (us ->srb , us ) ; }  /* 加锁 */ scsi_lock (host ) ;  //指示命令执行完成 if (us ->srb ->result != DID_ABORT << 16 ) { US_DEBUGP ( "scsi cmd done, result=0x%x\n" , us ->srb ->result ) ; us ->srb ->scsi_done (us ->srb ) ; } else {SkipForAbort : US_DEBUGP ( "scsi command aborted\n" ) ; }  /*如果一个错误退出请求被收到,我们需要发信号表示退出完成了。应该测试TIMED_OUT标识而不是srb->result == DID_ABORT,因为timeout/abort请求可能在所有的USB处理完成后被收到的*/ if (test_bit (US_FLIDX_TIMED_OUT , &us ->flags ) ) complete ( & (us ->notify ) ) ;  //完成了在这个命令上的操作SkipForDisconnect : us ->srb = NULL ; scsi_unlock (host ) ;  /* 解锁*/ up ( & (us ->dev_semaphore ) ) ; } /* for (;;) */  //通知exit例程我们实际上正在退出操作。 complete_and_exit ( & (us ->notify ) , 0 ) ; }

对于支持SCSI协议的功能设备来说,us->proto_handler协议处理函数就是函数usb_stor_transparent_scsi_command,该函数把SCSI命令发送到传输层处理。该函数列出如下(在drivers/usb/storage/protocol.c中):

void usb_stor_transparent_scsi_command ( struct scsi_cmnd *srb , struct us_data *us ) { //发送命令到传输层 usb_stor_invoke_transport (srb , us ) ;  if (srb ->result == SAM_STAT_GOOD ) { /* Fix the READ CAPACITY result if necessary */ if (us ->flags & US_FL_FIX_CAPACITY ) fix_read_capacity (srb ) ; } }

函数usb_stor_invoke_transport是传输例程,它触发传输和基本的错误处理/恢复方法,它被协议层用来实际发送消息到设备并接收响应。

函数usb_stor_invoke_transport列出如下(在drivers/usb/storage/transport.c中):

void usb_stor_invoke_transport ( struct scsi_cmnd *srb , struct us_data *us ) { int need_auto_sense ; int result ;  //发送命令到传输层 srb ->resid = 0 ; result = us ->transport (srb , us ) ;  …… srb ->result = SAM_STAT_GOOD ;  //决定是否需要auto-sense标识 need_auto_sense = 0 ;  /*如果我们正在支持CB传输,它不能决定它自己的状态,我们将自动感知(auto-sense),除非操作包括在一个data-in的传输中。设备能通过安装bulk-in管道来发出大多关开data-in错误的信号。*/ if ( (us ->protocol == US_PR_CB || us ->protocol == US_PR_DPCM_USB ) && srb ->sc_data_direction != DMA_FROM_DEVICE ) { US_DEBUGP ( "-- CB transport device requiring auto-sense\n" ) ; need_auto_sense = 1 ; }  //如果有一个操作失败,我们将自动做REQUEST_SENSE。   //注意在传输机制中在“失败”和“错误”之间的命令是不同的。 if (result == USB_STOR_TRANSPORT_FAILED ) { US_DEBUGP ( "-- transport indicates command failure\n" ) ; need_auto_sense = 1 ; }  …… //做auto-sense if (need_auto_sense ) { int temp_result ; void * old_request_buffer ; unsigned short old_sg ; unsigned old_request_bufflen ; unsigned char old_sc_data_direction ; unsigned char old_cmd_len ; unsigned char old_cmnd [MAX_COMMAND_SIZE ] ; unsigned long old_serial_number ; int old_resid ;  US_DEBUGP ( "Issuing auto-REQUEST_SENSE\n" ) ;  //存储旧的命令 memcpy (old_cmnd , srb ->cmnd , MAX_COMMAND_SIZE ) ; old_cmd_len = srb ->cmd_len ;  //设置命令和LUN memset (srb ->cmnd , 0 , MAX_COMMAND_SIZE ) ; srb ->cmnd [ 0 ] = REQUEST_SENSE ; srb ->cmnd [ 1 ] = old_cmnd [ 1 ] & 0xE0 ; srb ->cmnd [ 4 ] = 18 ;  //在这儿必须做协议转换 if (us ->subclass == US_SC_RBC || us ->subclass == US_SC_SCSI ) srb ->cmd_len = 6 ; else srb ->cmd_len = 12 ;  //设置传输方向 old_sc_data_direction = srb ->sc_data_direction ; srb ->sc_data_direction = DMA_FROM_DEVICE ;  //存buffer中内容 old_request_buffer = srb ->request_buffer ; srb ->request_buffer = srb ->sense_buffer ;  //设置buffer传输长度 old_request_bufflen = srb ->request_bufflen ; srb ->request_bufflen = 18 ;  //存碎片收集链表中的碎片数  old_sg = srb ->use_sg ; srb ->use_sg = 0 ;  //改变序号 – 或非高位 old_serial_number = srb ->serial_number ; srb ->serial_number ^= 0x80000000 ;  //发生auto-sense命令 old_resid = srb ->resid ; srb ->resid = 0 ; temp_result = us ->transport (us ->srb , us ) ;  //恢复命令 srb ->resid = old_resid ; srb ->request_buffer = old_request_buffer ; srb ->request_bufflen = old_request_bufflen ; srb ->use_sg = old_sg ; srb ->serial_number = old_serial_number ; srb ->sc_data_direction = old_sc_data_direction ; srb ->cmd_len = old_cmd_len ; memcpy (srb ->cmnd , old_cmnd , MAX_COMMAND_SIZE ) ;  …… /*设置result,让上层得到此值*/ srb ->result = SAM_STAT_CHECK_CONDITION ;  //如果ok,显示它们,sense buffer清0,这样不让高层认识到我们做了一个自愿的auto-sense  if (result == USB_STOR_TRANSPORT_GOOD && /* Filemark 0, ignore EOM, ILI 0, no sense */ (srb ->sense_buffer [ 2 ] & 0xaf ) == 0 && /* 没有ASC或ASCQ */ srb ->sense_buffer [ 12 ] == 0 && srb ->sense_buffer [ 13 ] == 0 ) { srb ->result = SAM_STAT_GOOD ; srb ->sense_buffer [ 0 ] = 0x0 ; } }  //我们传输小于所要求的最小数据量  if (srb ->result == SAM_STAT_GOOD && srb ->request_bufflen - srb ->resid < srb ->underflow ) srb ->result = (DID_ERROR << 16 ) | (SUGGEST_RETRY << 24 ) ;  return ;  //出错退出处理:bulk-only传输在一个出错退出后请求一个复位操作   Handle_Abort : srb ->result = DID_ABORT << 16 ; if (us ->protocol == US_PR_BULK ) us ->transport_reset (us ) ; }

对于USB Mass Storage相适应的设备来说,us->transport(srb, us)调用的是函数usb_stor_Bulk_transport,该函数列出如下(在drivers/usb/storage/transport.c中):

int usb_stor_Bulk_transport ( struct scsi_cmnd *srb , struct us_data *us ) { struct bulk_cb_wrap *bcb = ( struct bulk_cb_wrap * ) us ->iobuf ; struct bulk_cs_wrap *bcs = ( struct bulk_cs_wrap * ) us ->iobuf ; unsigned int transfer_length = srb ->request_bufflen ; unsigned int residue ; int result ; int fake_sense = 0 ; unsigned int cswlen ; unsigned int cbwlen = US_BULK_CB_WRAP_LEN ;  //对于BULK32设备,设置多余字节到0 if ( unlikely (us ->flags & US_FL_BULK32 ) ) { cbwlen = 32 ; us ->iobuf [ 31 ] = 0 ; }  //建立命令包裹 bcb ->Signature = cpu_to_le32 (US_BULK_CB_SIGN ) ;  //加上签名 bcb ->DataTransferLength = cpu_to_le32 (transfer_length ) ; //传输长度 bcb ->Flags = srb ->sc_data_direction == DMA_FROM_DEVICE ? 1 << 7 : 0 ; bcb ->Tag = srb ->serial_number ; bcb ->Lun = srb ->device ->lun ; if (us ->flags & US_FL_SCM_MULT_TARG ) //支持多个目标 bcb ->Lun |= srb ->device ->id << 4 ; bcb ->Length = srb ->cmd_len ;  //命令长度  //拷贝命令负载 memset (bcb ->CDB , 0 , sizeof (bcb ->CDB ) ) ; memcpy (bcb ->CDB , srb ->cmnd , bcb ->Length ) ;  //送命令到端点 result = usb_stor_bulk_transfer_buf (us , us ->send_bulk_pipe , bcb , cbwlen , NULL ) ; US_DEBUGP ( "Bulk command transfer result=%d\n" , result ) ; if (result != USB_STOR_XFER_GOOD ) return USB_STOR_TRANSPORT_ERROR ;  /*数据阶段 */ /*发送/接收数据负载*/  /*Genesys Logic接口芯片在命令阶段与数据阶段之间需要100us延迟。大概由于时钟的不精确的原因,一些设备需要更多一点*/ if (le16_to_cpu (us ->pusb_dev ->descriptor. idVendor ) == USB_VENDOR_ID_GENESYS ) udelay ( 110 ) ; //延迟110us  if (transfer_length ) { //由传输方向确定管道 unsigned int pipe = srb ->sc_data_direction == DMA_FROM_DEVICE ? us ->recv_bulk_pipe : us ->send_bulk_pipe ;     //发送数据 result = usb_stor_bulk_transfer_sg (us , pipe , srb ->request_buffer , transfer_length , srb ->use_sg , &srb ->resid ) ; US_DEBUGP ( "Bulk data transfer result 0x%x\n" , result ) ; if (result == USB_STOR_XFER_ERROR ) return USB_STOR_TRANSPORT_ERROR ;  /*如果设备尝试送回比请求的数据量更多数据,规范要求我们传送CSW 。因为没地方重尝试这个命令,我们返回假sense数据表示不合法的请求,在CDB中的无效域。*/ if (result == USB_STOR_XFER_LONG ) fake_sense = 1 ; }  //为设备状态得到CSW US_DEBUGP ( "Attempting to get CSW...\n" ) ; result = usb_stor_bulk_transfer_buf (us , us ->recv_bulk_pipe , bcs , US_BULK_CS_WRAP_LEN , &cswlen ) ;  /*一些崩溃的设备加不必要的0长度包到它们传输数据的末尾。这种包显示为0长度CSW。如果遇到这种情况,尝试再读取CSW*/ if (result == USB_STOR_XFER_SHORT && cswlen == 0 ) { US_DEBUGP ( "Received 0-length CSW; retrying...\n" ) ; result = usb_stor_bulk_transfer_buf (us , us ->recv_bulk_pipe , bcs , US_BULK_CS_WRAP_LEN , &cswlen ) ; }  //读CSW失败 if (result == USB_STOR_XFER_STALLED ) {  //再得到状态 US_DEBUGP ( "Attempting to get CSW (2nd try)...\n" ) ; result = usb_stor_bulk_transfer_buf (us , us ->recv_bulk_pipe , bcs , US_BULK_CS_WRAP_LEN , NULL ) ; }  //如果到这里还失败,则出现传输错误  US_DEBUGP ( "Bulk status result = %d\n" , result ) ; if (result != USB_STOR_XFER_GOOD ) return USB_STOR_TRANSPORT_ERROR ;  //检查bulk状态,检查各种CSW标识是否正确 residue = le32_to_cpu (bcs ->Residue ) ; if ( (bcs ->Signature != cpu_to_le32 (US_BULK_CS_SIGN ) && bcs ->Signature != cpu_to_le32 (US_BULK_CS_OLYMPUS_SIGN ) ) || bcs ->Tag != srb ->serial_number || bcs ->Status > US_BULK_STAT_PHASE ) { US_DEBUGP ( "Bulk logical error\n" ) ; return USB_STOR_TRANSPORT_ERROR ; }  //计算实际剩余部分,它基于传输的数据量和设备返回的信息。  if (residue ) { if ( ! (us ->flags & US_FL_IGNORE_RESIDUE ) || srb ->sc_data_direction == DMA_TO_DEVICE ) { residue = min (residue , transfer_length ) ; srb ->resid = max (srb ->resid , ( int ) residue ) ; } }  //基于status作出报告 switch (bcs ->Status ) { case US_BULK_STAT_OK : //不清楚设备状态,返回fake sense(假的探测)数据 if (fake_sense ) { memcpy (srb ->sense_buffer , usb_stor_sense_invalidCDB , sizeof (usb_stor_sense_invalidCDB ) ) ; return USB_STOR_TRANSPORT_NO_SENSE ; }  //命令正常结束,注意数据可能是短的 return USB_STOR_TRANSPORT_GOOD ;  case US_BULK_STAT_FAIL : //命令失败 return USB_STOR_TRANSPORT_FAILED ;  case US_BULK_STAT_PHASE : //状态错误,注意一个传输复位可能将被函数invoke_transport()触发。  return USB_STOR_TRANSPORT_ERROR ; }  /* 程序应该从不会运行到这里,否则,程序出错*/ return USB_STOR_TRANSPORT_ERROR ; }

函数usb_stor_bulk_transfer_sg在批量管道上传输带有整个SCSI命令的数据负载。函数列出如下(在drivers/usb/storage/transport.c中):

int usb_stor_bulk_transfer_sg ( struct us_data * us , unsigned int pipe , void *buf , unsigned int length_left , int use_sg , int *residual ) { int result ; unsigned int partial ;  /* are we scatter-gathering? */ if (use_sg ) { //使用usb核心的碎片-收集原理,初始化碎片-收集请求块、进行DMA映射,分块提交URB。 result = usb_stor_bulk_transfer_sglist (us , pipe , ( struct scatterlist * ) buf , use_sg , length_left , &partial ) ; length_left -= partial ; } else { //没有碎片-收集,仅产生请求 result = usb_stor_bulk_transfer_buf (us , pipe , buf , length_left , &partial ) ; length_left -= partial ; }  //存储剩余的并返回错误代码   if (residual ) *residual = length_left ; return result ; }

函数usb_stor_bulk_transfer_buf通过批量管道传输一个buffer,允许过早结束。返回USB_STOR_XFER_xxx代码。函数列出如下:

int usb_stor_bulk_transfer_buf ( struct us_data *us , unsigned int pipe , void *buf , unsigned int length , unsigned int *act_len ) { int result ;  //填充URB usb_fill_bulk_urb (us ->current_urb , us ->pusb_dev , pipe , buf , length , usb_stor_blocking_completion , NULL ) ; result = usb_stor_msg_common (us , 0 ) ;  //提交URB  //存储数据传输的实际长度  if (act_len ) *act_len = us ->current_urb ->actual_length ;   //分析传输结果 return interpret_urb_result (us , pipe , length , result , us ->current_urb ->actual_length ) ; }

函数usb_stor_msg_common 是URB消息提交代码的通用部分。所有的来自USB存储设备(含有处理排队的SCSI命令)驱动程序都必须通过这个函数来处理URB的提交机制。

函数usb_stor_msg_common列出如下:

static int usb_stor_msg_common ( struct us_data *us , int timeout ) { struct completion urb_done ; struct timer_list to_timer ; int status ;  //在错误退出/断开处理期间不提交URB  if (us ->flags & ABORTING_OR_DISCONNECTING ) return -EIO ;  //建立唤醒系统的数据结构  init_completion ( &urb_done ) ;  //填充URB里的常用域  us ->current_urb ->context = &urb_done ; us ->current_urb ->actual_length = 0 ; us ->current_urb ->error_count = 0 ; us ->current_urb ->status = 0 ;  us ->current_urb ->transfer_flags = URB_ASYNC_UNLINK | URB_NO_SETUP_DMA_MAP ; if (us ->current_urb ->transfer_buffer == us ->iobuf ) us ->current_urb ->transfer_flags |= URB_NO_TRANSFER_DMA_MAP ; us ->current_urb ->transfer_dma = us ->iobuf_dma ; us ->current_urb ->setup_dma = us ->cr_dma ;  //提交URB status = usb_submit_urb (us ->current_urb , GFP_NOIO ) ; if (status ) { //发生错误,返回错误代码 return status ; }  //因为URB已被成功提交,现在可以取消它  set_bit (US_FLIDX_URB_ACTIVE , &us ->flags ) ; //设备当前URB在使用标识  //在提交期间是否发生错误退出或断开连接 if (us ->flags & ABORTING_OR_DISCONNECTING ) {  //如果URB还没取消,就取消它  if (test_and_clear_bit (US_FLIDX_URB_ACTIVE , &us ->flags ) ) {  usb_unlink_urb (us ->current_urb ) ;  //取消URB } }  //如果需要定时,提交定时器  if (timeout > 0 ) { init_timer ( &to_timer ) ; to_timer. expires = jiffies + timeout ; to_timer. function = timeout_handler ; to_timer. data = ( unsigned long ) us ; add_timer ( &to_timer ) ; }  //等待URB的完成 wait_for_completion ( &urb_done ) ; clear_bit (US_FLIDX_URB_ACTIVE , &us ->flags ) ;  //完成后清除在使用标识  //清除定时器 if (timeout > 0 ) del_timer_sync ( &to_timer ) ;  //返回URB状态 return us ->current_urb ->status ; }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 要让STM32 USB Mass Storage设备与电脑通信,您需要在STM32设备上实现USB Mass Storage设备固件,并将其连接到电脑的USB端口。然后,您可以使用计算机上的文件管理器或其他支持USB Mass Storage协议的应用程序访问STM32设备上的文件系统。您还可以通过USB接口发送和接收数据,以便在STM32设备和计算机之间传输数据。 ### 回答2: 要让STM32 USB Mass Storage设备与电脑通信,首先需要在STM32 MCU上编写相应的固件代码。 1. 首先,在STM32硬件上连接一个USB接口,并将其配置为USB设备模式。可以选择配置的方式包括使用STM32提供的CubeMX软件来生成USB设备库文件或手动配置USB设备模式。 2. 然后,在STM32上编写固件代码来处理USB通信协议。这涉及到设置USB设备描述符,配置USB传输端点等。 3. 在STM32上实现Mass Storage协议。这个协议定义了通信双方之间传输文件和数据的规则。可以使用相应的软件库或编写自己的代码实现。 4. 配置文件系统。STM32 USB Mass Storage设备通常用于存储和传输文件。因此,需要在STM32上配置适当的文件系统,如FAT文件系统。 5. 将STM32插入电脑的USB端口。电脑会检测到STM32设备并加载其驱动程序。 6. 现在,可以在电脑上使用文件管理器或其他USB Mass Storage设备支持的应用程序来访问STM32设备。可以像访问其他USB存储设备一样使用它,如复制、粘贴、删除文件等。 需要注意以下事项: - 确保STM32和电脑之间使用正确的USB电缆连接。 - 在STM32的固件代码中实现正确的错误处理和状态指示,以便能够识别和解决通信问题。 - 根据系统需求,可以为STM32设备添加额外的功能,如读写保护、加密等。 总之,要使STM32 USB Mass Storage设备能够与电脑通信,必须在STM32上实现USB设备模式和Mass Storage协议,并在电脑上正确加载驱动程序。一旦连接成功,就可以像使用其他USB存储设备一样使用STM32设备。 ### 回答3: 要将STM32作为USB Mass Storage Device连接到电脑上进行通信,可以按照以下步骤进行: 1. 首先,需要在STM32的固件中实现USB MSC(Mass Storage Class)功能。这意味着STM32将会被识别为一个可见的USB存储设备。可以使用STM32提供的软件库或者其他第三方库来实现这个功能。 2. 在STM32的固件中,需要实现设备的基本功能,如初始化USB硬件、配置USB端口、配置USB中断等等。这些步骤可以通过参考相关资料或者使用官方提供的示例来完成。 3. 在电脑上,需要安装USB驱动程序。这些驱动程序通常由芯片或者供应商提供。安装驱动程序可以确保电脑能够正确识别并操作STM32作为USB设备。 4. 在电脑上,需要使用支持USB Mass Storage Device的操作系统。大部分现代操作系统都支持USB Mass Storage Device,如Windows、Linux、macOS等。插入STM32到电脑上后,操作系统会自动识别STM32作为USB存储设备。 5. 一旦STM32被识别为USB存储设备,电脑上就可以像操作常规硬盘一样进行读写操作。用户可以通过资源管理器、文件浏览器等工具来访问STM32存储设备,并操作其中的文件和文件夹。 总结起来,要将STM32作为USB Mass Storage Device与电脑通信,关键是在STM32的固件中实现USB MSC功能,并确保在电脑上安装了相关驱动程序和支持USB Mass Storage Device的操作系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值