分类: LINUX
2014-12-18 18:01:33
原文地址:Linux内核USB从设备驱动程序 作者:Lx_ytw
目录 [] |
从设备是连接到USB主机上的设备,如:连接到计算机上的U盘、打印机等设备。从设备上还可以有高级操作系统,如:可作U盘的手机上有Linux操作系统,这种情况下的从设备驱动程序相对较复杂,它一方面要遵循Linux设备驱动程序模型,另一方面需要配合USB Gadget设备规范。下面分析有Linux操作系统从设备驱动程序。
USB从设备驱动程序分为三个层次,USB从设备驱动程序体系图如图3所示,从底层向上,这些层是设备控制器驱动程序层、Gadget API和Gadget驱动程序层。
设备控制器驱动程序层直接与硬件通信,不同的设备控制器硬件有不同的设备控制器驱动程序。设备控制器的控制功能抽象出接口--Gadget API,Gadget驱动程序使用了Gadget API,Gadget API是硬件控制的抽象层,它将Gadget驱动程序的控制传递给具体的设备控制器硬件驱动程序。不同的设备控制器硬件影响着对ep0的逻辑配置管理部分。
例如:一些控制器有较多的端点,不支持高速及同步传输,有时候,端点有固定的配置。一个Gadget驱动程序可以实现多个"功能",每个功能提供不同的能力给USB主机。如:网络连接或喇叭。有的操作系统称它为"USB客户驱动程序"。
"USB Gadget" API 支持内部运行Linux的USB设备,如:PDA。该API是USB设备控制器(UDC)硬件与"Gadget驱动程序"通信的接口函数。"Gadget驱动程序"是使得硬件扮作诸如"网络连接"或"打印机"这样的用软件实现的设备。
当用户不想在内核中写一个"Gadget驱动程序",那么,你可以使用"gadgetfs"在用户模式编程模仿设备动作。
Linux内核在头文件linux/usb_gadget.h中提供的API函数,使得在嵌入式Linux操作系统中编写USB从设备驱动程序更加容易。PC机及服务器只有USB主机控制器硬件,因而,它们不能作为USB从设备。对嵌入设备来说,USB从设备控制器常被集成进处理器中,嵌入设备的各种功能(如:U盘、网卡等)依赖于这种USB从设备控制器来与主机连接。通过不同的Gadget驱动程序,嵌入设备可实现不同的功能。例如:嵌入设备根据用户的选择可作为U盘、网卡或Modem使用,这些设备都通过软件实现。
嵌入设备Linux操作系统的内核上层(如:网络、文件系统或块I/O子系统)产生或消费Gabget驱动程序,嵌入设备通过从设备控制器驱动程序将数据按一定协议打包传输给主机。
样例:Gadget串口驱动程序的体系结构
Gadget串口驱动程序通过USB与主机PC上的通用串口驱动程序进行通信。Gadget串口驱动程序的体系结构图如图4所示。
在设备侧Linux操作系统,Gadget串口驱动程序看起来象是一个串口设备。在主机系统中,Gadget串口设备看起来象是一个CDA ACM类设备或一个带有批量输入/输出的特定设备,并且,它被看作类似于其它串口设备。
CDC ACM驱动程序将USB设备向操作系统暴露为一个虚拟Modem或一个虚拟COM端口。该驱动程序通过ACM(将数据和AT命令在不同的通道分开)或串行仿真(将AT命令作为数据流的一部分),传送数据和AT命令。
Gadget的对象由设备对象、端点对象、请求对象和驱动程序对象组成,为了遵循驱动程序模型,设备对象从基类device继承,驱动程序对象从device_driver继承。每个对象对应设备某一部分,包含了该部分的属性、运行参数和方法函数。这些对象分别说明如下:
结构usb_gadget代表了USB Gadget设备对象,从类device继承。Gadget通过"Gadget驱动程序"使用设备功能,处理所有的USB配置和接口。Gadget驱动程序通过操作函数集与硬件特定代码间接通信。它将Gadget驱动程序从硬件细节隔开,并且通过通用的I/O队列包装了硬件端点。"usb_gadget"和"usb_ep"接口提供了对硬件的隔离。
在与USB_REQ_SET_CONFIGURATION对应的setup()调用之前,以及在驱动程序的suspend()调用之前,三个OTG设备特征标识被更新。仅在is_otg以及当设备作为B-周边设备(is_a_peripheral是false)时,三个OTG设备特征标识是有效的。
结构usb_gadget分析如下(include/linux/usb/usg_gadget.h中):
结构usb_gadget_ops 是USB设备控制器的设备操作函数集,列出如下:
结构usb_ep是USB端点从设备侧的代表。总线控制器驱动程序列出了在gadget->ep_list里所有通用端点。控制端点(gadget->ep0)不在链表中,并且仅在回应驱动程序的setup()回调函数时被访问。
结构usb_ep列出如下(include/linux/usb/usg_gadget.h中):
结构usb_ep_ops是USB设备控制器的端点操作函数集,设备侧不同的USB控制器支持的端点数及端点的能力是不同的,结构usb_ep_ops列出如下:
结构usb_request 描述了一个i/o请求,它是一个与主机侧URB结构相似的结构,除了它较小并有更多的预分配外。端点来分配/释放结构usb_request。硬件的驱动程序能加格外的per-request数据到它返回的内存里,这常避免了在请求被排队之后分配孤立的内存(潜在的失败),。
请求标识影响着请求处理,如:zero决定0长度包是否被写入,short_not_ok决定着短读是否被当作错误(先阻塞请求队列),no_interrupt与较深的请求队列一起使用时,暗示着不需要中断。
批量端点可以使用任何大小的buffer,也可被用作中断传输。仅能中断传输的端点可能有少得多的功能。
结构usb_request列出如下:
结构 usb_gadget_driver是USB Gadget的驱动程序结构,从类device_driver继承。设备被关闭直到一个Gadget驱动程序被成功bind(),这意味着驱动程序将处理setup()请求来进行硬件枚举。如果gadget->is_otg 是true, gadget在枚举期间必须提供一个OTG描述子。
结构 usb_gadget_driver列出如下:
Gadget API接口函数大多数是对设备控制器的端点操作函数集中的函数进行封装,Gadget API说明如下(include/linux/usg_gadget.h中):
函数usb_ep_enable配置端点,使它可用。每个端点的硬件能力必须匹配端点描述子。如:名为"ep2in-bulk"端点可用作中断传输或批量传输,但不同用作同步传输。其参数ep是需要配置的端点,但不能是ep0端点。驱动程序通过结构usb_gadget 的ep_list 发现端点。参数desc是期望的行为的描述符,调用者保证它直到端点失效前一直有效。数据字节序是小端序(USB标准)。函数usb_ep_enable列出如下:
函数 usb_ep_disable使端点失效不可再用。函数列出如下:
函数 usb_ep_alloc_request 给这个端点分配一个请求对象。请求可能被usb_ep_queue()提交,并接收一个completion回调函数。函数列出如下:
函数 usb_ep_free_request释放一个请求对象。函数列出如下:
函数 usb_ep_alloc_buffer分配一个I/O buffer。参数ep是与buffer相关的端点。参数len是buffer的长度,参数dma是指向buffer的DMA地址,必须是有效的。调用成功时,返回一个新buffer,不能分配时返回NULL。如果使用DMA,这个buffer与DMA相适应并且调用者不必关心与DMA的矛盾和反弹缓存机制。对这样的buffer不要求附加的per-request DMA映射。你不必用这个调用来分配I/O buffer,除非你想确信驱动程序承担不了反弹缓存的拷贝或per-request DMA映射的代价。函数列出如下:
函数 usb_ep_free_buffer释放i/o buffer。函数列出如下:
函数usb_ep_queue 排队(提交)一个I/O请求给一个端点。参数ep是与请求相关的端点,参数req是被提交的请求,参数gfp_flags是GFP_* 标识,用在低层次驱动程序不能预分配所有的请求必需的内存的情况下。
函数usb_ep_queue告诉设备控制器通过这个端点执行特定的请求(读或写一个buffer)。当请求完成时,请求被usb_ep_dequeue()取消,请求的completion例程被调用来把请求返回给Gadget驱动程序。任何端点(除了象ep0控制端点)可以有超过一个传输请求排队,它们按FIFO次序完成。一旦一个gadget驱动程序提交一个请求,p 个请求不可被检测或修改,直到它通过completion回调函数被给回到Gadget驱动程序。
每个请求被转变成一个或多个包,控制器驱动程序从不融合相邻的请求到同一个包。OUT传输有时候使用已缓存在硬件中的数据。对于IN和OUT传输来说,驱动程序依赖于请求缓存区的第一个字节总是相应的一些USB包的第一个字节的事实。
批量端点能排队任何数量的数据,传输被自动打包,如果请求不能完全填满最后一个包,最后一个包将是短的。0长度包(ZLPs)应该避免在可裁剪的协议中,因为不是所有的USB硬件能成功处理0长度包(如果请求中zero标识被设置,0长度包就可能被写)。批量端点可以被用作中断传输,但反过来是不行的,并且一些端点并不是支持每个中断传输(如:768字节包)。
仅中断传输的端点具有比批量传输端点少的功能,如:不支持排队,或不能处理比端点最大包尺寸大的buffer。
控制传输端点 在得到setup()回调函数后,驱动程序排队一个响应(甚至可能是0长度)。在传输在响应里定义的数据后,这激活状态ack。setup函数可以返回负值的错误代码来产生协议停止(注意一些USB设备控制器在某些情况下不允许协议停止响应)。当控制响应被延迟(响应在setup回调函数返回后被写),那么在ep0上usb_ep_set_halt()可能被调用来触发协议停止。
对于周期性传输的端点,如:中断或同步传输端点,USB主机每隔一个间隔选择一个函数运行,Gadget驱动程序通常在这个时刻已排队一些数据来传输。
函数 usb_ep_dequeue从一个端点取消或删除一个I/O请求的排队,如果请求还在端点上是激活的,它将从队列上被取下,并调用completion回调函数(带有状态-ECONNRESET)。函数 usb_ep_dequeue列出如下:
函数usb_ep_set_halt设置端点停止特征。除了控制端点外,端点停止(没有任何数据流)将一直到主机清除这个特征为止,驱动程序可能必须先清除端点的请求队列,来确信不会有不合适的传输发生。
注意当一个端点的CLEAR_FEATURE对Gadget驱动程序不可见时,SET_INTERFACE将不被设置。当切换端点的设置时,最简单的方法是对端点使用函数use usb_ep_enable() 或usb_ep_disable()。
函数usb_ep_set_halt返回0或负值的错误代码。调用成功时,它设置了所依赖的硬件状态,用来阻塞数据传输。如果任何传输请求还在排队,或者在控制器硬件(通常FIFO)还持有主机没有收集的数据时,尝试停止IN端点将会失败。
函数usb_ep_set_halt列出如下:
函数 usb_ep_clear_halt清除端点停止,在清除端点I/O队列中的任何其它状态之后,当对标准的"set interface"请求作出响应时使用这个函数。对于端点来说,这不是再配置。函数调用成功时,返回0,它清除了所依赖的反映端点停止的硬件状态。调用失败时,返回负值的错误码。注意有些硬件不支持这个请求(如:pxa2xx_udc),因此不能正确地利用接口替换配置。
函数 usb_ep_clear_halt列出如下:
函数 usb_ep_fifo_status返回在FIFO中的字节数,如果端点不能使用FIFO或不支持错误处理时返回负值的错误码。端点FIFO在某些情况下(如:在错误退出的传输之后),可能有"没有声明的数据"。主机可能没有收集到Gadget驱动程序写的所有数据(并且被请求的complete回调函数报告)。Gadget驱动程序可能没有收集到主机OUT的所有数据。需要精确处理错误报告或恢复的驱动程序必须使用这个调用。
函数 usb_ep_fifo_status列出如下:
函数 usb_ep_fifo_flush刷新 – 转储清除FIFO的内容。被用来在异常事务结束后转储清除在端点FIFO中的"没声明的数据"。
函数 usb_ep_fifo_flush列出如下:
pxa27x控制器(UDC Usb Device Controller)集成在Intel的PXA 27x系统处理器中,它除了ep0外还有15个端点。该控制器驱动程序与Gadget驱动程序一起工作。Gadget驱动程序返回描述子,利用配置和被主机使用的数据协议来与pxa27x控制器交互,并分配端点给不同的协议接口。pxa27x控制器驱动程序虚拟USB硬件,以便Gadget驱动程序可更换。UDC硬件为了使用众多的USB协议的bit位,限制了USB的配置变化事件种类。pxa2xx控制器驱动程序的代码在drivers/usb/gadget/pxa27x_udc.c中。
控制器pxa27x遵循Gadget驱动程序模型,其设备对象、端点对象、请求对象都从Gadget驱动程序模型的基类继承,分别说明如下:
(1)控制器设备对象结构pxa_udc
控制器pxa27x设备对象用结构pxa_udc描述,它从基类usb_gadget继承,包含了控制器pxa27x的资源信息、端点数组、配置信息、使用该设备的Gadget驱动程序列表等,其列出如下:
(2)控制器端点对象结构
控制器端点对象用结构udc_usb_ep描述,它从端点基类结构usb_ep继承,包含了端点描述子、端点所在的设备对象、pxa设备端点信息。
结构udc_usb_ep列出如下(在drivers/usb/gadget/pxa27x_udc.h中):
结构pxa_ep描述了pxa设备端点对象的信息,包括端点上的请求队列、端点属性、统计状态等信息。当静态地定义结构pxa_ep实例时,用户必须为所有的Gadget猜测所有需要的端点结构pxa_ep实例,该端点实例应可与udc驱动程序一起工作。
结构pxa_ep列出如下:
(3)请求对象结构pxa27x_request
pxa端点上的每个请求对象用结构usb_request描述,它从请求的基类usb_request继承,其列出如下:
编写控制器pxa27x驱动程序的过程实际上是对各种对象的实例化的过程,对于控制器在运行中不变的属性和方法函数,用户通过对象静态实例化指定属性值和方法函数;对于根据启动状态变化的属性和方法函数,通过探测函数动态指定给对象实例;对于运行中会变化的属性和方法函数,通过消息控制切换。下面列出驱动模型的各种静态实例。
设备对象实例memory对设备的端点、设备对象进行了实例化,该结构实例列出如下(在driver/usb/gadget/pxa27x_udc.c中):
驱动程序实例udc_driver列出如下:
函数udc_init注册了设备控制器驱动程序结构实例udc_driver,函数列出如下:
函数pxa_udc_probe探测设备,初始化设备结构,请求中断号,设置中断处理函数,该函数列出如下:
pxa27x设备控制器通过寄存器由软件读取设备状态或控制设备,其中,UDC控制寄存器控制UDC设备的功能,中断控制器打开或关闭中断,中断状态寄存器说明引起中断的原因,端口控制寄存器控制端口的功能。详细寄存器说明请参考厂商的pxa27x设备手册。pxa27x设备控制器的寄存器定义列出如下(在driver/usb/gadget/pxa27x_udc.h中):
pxa27x设备控制器上的数据传输、电源管理等都通过中断处理例程来实现的,中断处理例程的主函数pxa_udc_irq的调用层次图如图5所示。下面按该图分析各个函数。
函数pxa_udc_irq是中断处理例程的主函数,处理所有USB控制设备的中断,参数irq为中断号,参数_dev为设备对象。它从中断状态寄存器中读取中断请求,根据中断请求调用相应的功能函数。如:IN端点数据寄存器发送完数据后,发出中断请求:要求从USB请求中写数据包到端点。OUT端点FIFO数据装满时,发出中断请求:要求将FIFO中数据读出到USB请求中。
函数pxa_udc_irq列出如下:
函数irq_handle_data从请求队列到端点传输数据或者从端点到请求队列传输数据。其列出如下:
函数handle_ep0尝试传输所有挂起的请求数据到端点,或者传输端点中所有挂起的数据到usb请求中。参数udc为USB设备控制器对象;参数fifo_irq表示:如果由FIFO服务类型的中断触发,其值为1;参数opc_irq表示:如果由输出包完成类型中断触发,值为1。
PXA27x硬件可以在不需要驱动程序通知的情况下处理几个标准usb控制请求。可以完全由硬件处理的请求有:SET_ADDRESS, SET_FEATURE, CLEAR_FEATURE, GET_CONFIGURATION, GET_INTERFACE, GET_STATUS。由硬件处理但需要与中断通知一起参与的请求有:SYNCH_FRAME, SET_CONFIGURATION, SET_INTERFACE。剩下的由handle_ep0处理的标准请求有:GET_DESCRIPTOR, SET_DESCRIPTOR和特定的请求。在标准USB 2.0第9章之外的请求由Gadget驱动程序处理。
函数handle_ep0通过状态机控制数据的传输过程,状态机与USB规范不兼容,与《Intel PXA270 developers guide》也不兼容。状态机的要点说明如下:
函数handle_ep0列出如下:
函数handle_ep0_ctrl_req从端点0的数据FIFO中读取SETUP包,不作处理。根据控制端点的控制请求,设置状态机状态,并将请求交给Gadget驱动程序,处理硬件不支持的请求。参数udc为USB设备控制器对象,参数req为端点控制请求对象。
函数handle_ep0_ctrl_req列出如下:
函数write_ep0_fifo 发送一个请求(或请求的一部分)到控制端点(ep0 IN)。如果请求大小不合适,剩余部分将从中断发送。请求满足下面任一条件时被认为完全写入:
如果请求完全写入,函数返回1,如果仅部分发送,返回0.
函数write_ep0_fifo列出如下:
函数write_packet从请求中提取1个包到一个IN端点,参数ep为物理端点对象,参数req为请求对象,参数max为适配于端点的最大字节数。函数write_packet从USB请求中提取字节,通过写数据寄存器传送数据到物理端点。如果没有字节传输,不写任何字节到物理端点。函数返回实际传输的字节字节数。
函数write_packet列出如下:
函数handle_ep尝试传输所有挂起的请求数据到端点或传输端点中所有挂起的数据到USB请求中。传输数据的方法是通过读/写数据寄存器(或FIFO)实现。
函数handle_ep列出如下:
文件系统gadgetfs为USB 设备控制器设计,以便在用户模式下使用文件系统API函数来模拟Gadget的操作。文件系统gadgetfs的实现代码在drivers/usb/gadget/inode.c中。
gadgetfs API映射每个端点到一个文件描述符,以便你能使用标准的同步读/写系统调用来操作I/O。它支持O_NONBLOCK和O_ASYNC/FASYNC 模式的I/O操作。
USB的特殊性是协议定义了与硬件状态机相关的读/写操作,这些操作包括两类文件:一类是设备使用ep0的文件,另一类是IN/OUT端点的文件。在这两种情况里,用户模式驱动程序必须在使用端点之前配置硬件。配置方法说明如下:
首先,当用户模式驱动程序配置/dev/gadget/$CHIP时(通过写配置和设备描述子),它调用函数dev_config。随后,$CHIP可作为一个设备事件源来服务,用来处理除了基本的硬件枚举外所有的控制请求。
接着或在SET_CONFIGURATIO控制请求后,当用户模式驱动程序配置每个/dev/gadget/ep*文件时(通过写端点描述子),它调用函数ep_config()。随后,它通过文件系统的写函数write使用这些文件输出数据到IN端点,通过读函数从OUT端点读取数据。
文件系统gadgetfs继承了文件系统和Gadget驱动模型的基类,除了设备对象和端点对象从基类派生而来外,其他对象为基类实例。设备对象和端点对象结构列出如下:
(1)虚拟Gadget设备对象结构dev_data
从驱动程序的角度来看,gadgetfs是一个虚拟的Gadget设备,从用户空间的角度看,它是一个文件系统。在内核空间,该虚拟Gadget的设备对象用结构dev_data描述,该设备对象从基类usb_gadget、usb_request、super_block、dentry等继承,从继承的基类可以看出,它既有Gadget设备的特征,也有文件系统的特征。
虚拟Gadget设备对象结构dev_data列出如下(在drivers/usb/gadget/inode.c中):
(2)端点对象结构ep_data
结构ep_data代表了一个端点对象,由于在gadgetfs文件系统中,端点用一个文件描述,因而,结构ep_data除了包含端点相关的域(如:请求、状态等)外,还包含了与文件相关的域(如:dentry、inode等)。
结构ep_data列出如下:
函数init注册了文件系统类型gadgetfs_type,函数列出如下:
文件系统类型实例gadgetfs_type列出如下:
当文件系统挂接时,命令mount会通过系统调用来调用函数gadgetfs_get_sb获取超级块对象,该函数列出如下:
文件系统gadgetfs继承了Gadget驱动模型基类和文件系统基类,因此,在初始化超级块函数gadgetfs_fill_super中,一方面应按文件系统模型初始化超级块、dentry、inode等文件系统对象,另一方面,还应按Gadget驱动模型初始化驱动程序、设备等对象。
函数gadgetfs_fill_super初始化超级块,创建根节点,创建名为CHIP的文件,它实际上是ep0的控制文件,在设备控制器被配置之前,这个文件的写操作函数就是对设备控制器进行配置,在设备控制器被配置之后,对这个函数的操作就是对设备控制器进行控制。
函数gadgetfs_fill_super列出如下(在driver/usb/gadget/inode.c中):
由于Gadget驱动程序的功能实现上由文件系统实现,因此,初始化时,Gadget驱动程序对象基本上不作任何操作。该对象实例probe_driver列出如下:
ep0文件对应的操作函数集实例列出如下:
USB设备在使用之前必须进行配置,用户空间应用程序通过文件系统gadgetfs进入内核,调用函数dev_config完成设备配置操作。
函数dev_config从用户空间得到设备描述子和配置描述子,对设备及端点进行配置。它还注册gadgetfs_driver驱动程序,从驱动程序的角度看,gadgetfs是一个虚拟的Gadget驱动程序。
函数dev_config列出如下:
虚拟的Gadget设备驱动程序实例gadgetfs_driver列出如下:
下面仅分析驱动程序实例中的函数bind。
函数gadgetfs_bind将gadget绑定到此gadgetfs设备,初始化设备对象,激活端点文件。其列出如下:
函数activate_ep_files给每个端点创建请求,并创建文件,赋上文件操作函数集实例,通过端点文件,在没有配置端点前,可以通过写函数来配置端点,配置好端点后,可以通过读写函数来发送和接收数据。
函数activate_ep_files分析如下:
在文件系统中,端点以文件的形式存在,用户空间应用程序与端点之间传输数据变成了对端点文件的读写操作。这些文件的读写操作在文件系统的底层I/O接口函数中转换为调用控制器的读写寄存器操作,从而实现数据的传输或控制。
在设备配置后ep0使用的文件操作函数集ep0_io_operations列出如下:
在端点配置前,端点配置文件使用文件操作函数集实例ep_config_operations对端点进行配置,其列出如下:
在端点配置后,端点文件实现对端点的控制和传输数据操作,端点文件的文件操作函数集实例ep_io_operations列出如下:
下面仅分析配置文件的文件操作函数集实例中的写函数(配置函数)和端点文件的写操作函数。
配置文件的文件操作函数集实例中的写函数ep_config用来配置初始化端点,在用户空间,端点初始化的方法如下:
写操作通过函数ep_config建立端点配置,使控制器端点有合适的最大包尺寸等配置,以便处理批量、中断或同步传输。描述子是消息类型1,内容是主机u32字节序。它包括2个描述子,描述子的次序是:全速/低速描述子,接着是可选的高速描述子。
函数ep_config列出如下:
在端点配置后,端点文件的文件操作函数集用来读写数据,这里只分析了写操作函数ep_write,读操作函数ep_read有相似机制,只是数据方向相反。
函数ep_write处理一个同步的IN bulk/intr/iso传输,函数列出如下:
函数ep_io通过调用Gadget API(即具体设备控制器的端点操作函数)将读写请求在端点排队,在端点上进行数据的发送与接收处理。
函数ep_io分析如下:
USB大存储设备(Mass Storage)是以文件为单位进行存储的从设备(Gadget)。在主设备主机(任何操作系统)上它以U盘的形式出现,在有Linux操作系统的从设备主机上,它以Gadget驱动程序形式出现,实现从设备与主设备的通信。
Gadget Mass Storage是USB设备的一个典型的Gadget驱动程序使用例子,它说明了能适应不断增大的吞吐量的双缓存区技术,给出了一个在USB主设备主机上探测大存储设备驱动程序的典型方法。
大存储设备的存储通过常规文件或块设备来实现,它通过模块选项"file"选择大存储设备文件。通过模块选项 "ro"限制访问权限。如果可选的模块选项"removable",指示它是否是一个可移去的介质。
GadgetMass Storage设备支持控制-批量(CB Control-Bulk),控制-批量-中断(CBI Control-Bulk-Interrupt)和仅批量(Bulk-Only)传输,通过可选的"transport"模块选项来选择。它还支持下面的协议:RBC (0x01), ATAPI或 SFF-8020i (0x02), QIC-157 (0c03),通过可选的"protocol"模块选项来选择。
大存储设备可以有多个逻辑单元(LUN),每个LUN有自己的文件,LUN的数量由可选的模块选项"luns"(1到8)选择,相应的文件用逗号分开的"file"或"or"的列表定义。缺省的LUN个数从"file"元素的个数获取,如果"file"没有被提供,则为1。如果"removable"没有被设置,那么,每个LUN必须指定文件。如果"removable"被设置,一个没定义或空的文件名意味着LUN的介质没有装载。大存储设备的模块选项选项说明如表3所示。
表3 大存储设备的模块选项选项说明模块选项 | 说明 |
file=filename[,filename...] | 如果"removable"没被设置,用于存储的文件或块设备名是需要的。 |
ro=b[,b...] | 缺省是false, 仅读访问的逻辑值。 |
removable | 缺省是false, 可移去媒体的逻辑值。 |
luns=N | 缺省值 N = 文件名的个数是支持的LUN的个数。 |
transport=XXX | 缺省值是BBB(Bulk-Only), 传输名(CB, CBI, or BBB)。 |
protocol=YYY | 缺省值是SCSI, 协议名(RBC, 8020 或ATAPI, QIC, UFI, 8070, 或SCSI,也有可能1-6项都支持)。 |
vendor=0xVVVV | 缺省值是0x0525 (NetChip), USB供应商ID。 |
product=0xPPPP | 缺省值是0xa4a5 (FSG), USB产品ID。 |
release=0xRRRR | USB发布号(bcdDevice)。 |
buflen=N | 缺省值 N=16384, 使用的buffer大小(是页大小的整数倍)。 |
stall | 按照USB设备控制器的类型来决定缺省值(通常是true),是否允许驱动程序停止bulk端点的逻辑值。 |
对于Gadget设备来说,最少的要求是:仅一个bulk-in 和一个bulk-out端点是必须的(一个interrupt-out对于CBI来说也是必须的)。内存的最小要求是两个16k的buffer,可通过参数可配置buffer的大小。
如果编译时未设置CONFIG_USB_FILE_STORAGE_TEST选项,则仅"file", "ro","removable",和"luns"选项可用,
文件的路径名和ro设置在sysfs文件系统中的gadget/lun
本Gadget驱动程序基于Gadget Zero,SCSI(Small Computer System Interface)命令接口基于SCSI II接口规范,个别例外(如:操作码0x23,即READ FORMAT CAPACITIES)基于USB大存储类UFI命令规范(Universal Serial Bus Mass Storage Class UFI Command Specification)。
FSG(File Storage Gadget)驱动程序设计是很直接的:它用一个主线程处理大多数工作。
中断处理例程域从控制器驱动程序回调:块(bulk)请求和中断(interrupt)请求完成通知(completion notification)事件、ep0事件以及断开连接事件。通过调用唤醒函数wakeup,完成事件completion被传到主线程。
许多ep0请求在中断期间被处理,但设置接口SetInterface、设置配置SetConfiguration和设备复位请求通过SIGUSR1信号以"exception(例外)"的形式转发给线程(因为它们应该中断任何正在进行的文件I/O操作)。
线程的主要工作是实现SCSI交互的标准命令/数据/状态。该线程和它的子例程充满对挂起sigal/exception(信号/异常)的测试—所有这些选择(polling)判断是必须的,因为内核没有命令集jmp/longjmp的等价语句。
只要线程活着,它将保持对大存储设备文件的引用。这将阻止依赖于文件系统的大存储设备文件的卸载(umount)并可能在诸如系统关闭的操作中引起问题。为了阻止这些问题,线程捕捉INT, TERM, 和KILL信号并把它们转化成EXIT异常操作。
在线程的正常操作中,线程在Gadget的fsg_bind()回调期间开始启动,并且在fsg_unbind()期间停止。但它也能在收到一个信号时退出,并且当线程是死了的时候,让Gadget运行是没意义。因此,在线程退出前,注销Gadget驱动程序。这样存在两个问题:第一,驱动程序在两个地方注销;第二,正退出的线程应能间接地调用fsg_unbind(),fsg_unbind()应能按次序告诉线程退出。第一个问题通过使用REGISTERED原子标识来解决,驱动程序将仅注销一次。第二个问题通过使fsg_unbind()检查fsg->state来解决,如果状态已被设置FSG_STATE_TERMINATED,它将不尝试停止线程。
为了提供最大的吞吐量,驱动程序使用了缓冲区头(结构fsg_buffhd )的环形流水线(circular pipeline)。从原理上来说流水线可以任意长,实际中常用两级(如:双缓冲区)。每个缓冲区头装有一个bulk-in和一个bulk-out请求指针(因为缓冲区可被用作输入和输出,传输方向总是以主机的角度来定义),缓存区头还指向缓冲区和各种状态参数。
流水线的使用遵循一个简单的协议,有一个参数fsg->next_buffhd_to_fill指向下一个使用的缓冲区头。在任何时候缓冲区头可能还在被一个较早的请求使用,因此每个缓冲区头有一个状态参数指示它是EMPTY, FULL, 或BUSY。典型的使用是等待缓冲区头为EMPTY,文件I/O或USB I/O填充缓冲区(在缓冲区头是BUSY期间),当I/O完成时标识缓冲区头为FULL。接着缓冲将被清空(可能再次通过USB I/O,这期间标识为BUSY),并且最后再次标识为EMPTY(可能通过completion例程)。
关于ep0请求的状态级响应有一些细节需要注意。一些诸如设备复位的ep0请求,能中断正在进行的文件I/O操作,这可能在很长的时间中发生。在这个延迟期间,主机可能放弃最初的ep0请求并发出一个新的。当这发生时,驱动程序不应该通知主机最初的请求的完成,因为主机将不再等待它。因此驱动程序给每个ep0请求指定一个唯一的标签,并保持跟踪与一个长期运行的异常相关的请求的标签值(设备复位、接口变化或配置变化)。当异常处理完成时,仅如果当前ep0请求标签等于异常请求的标签值时,状态级反应被提交。这样仅最近接收的ep0请求将得到一个状态级的响应。
FSG驱动程序代码包括Gadget的细节处理、USB Mass Storage处理和SCSI协议的处理。代码在driver/usb/Gadget/file_storage.c中。
大存储设备对象结构fsg_dev代表了一个大存储设备,字符"fsg"是File Storage的缩写。它继承了USB的基类结构usb_gadget、usb_ep、usb_request,包括了传输请求、传输缓冲区、SCSI命令数据块和线程任务等。结构fsg_dev列出如下:
结构fsg_buffhd是缓冲区头结构,它管理着缓冲区的状态及使用缓冲区的USB请求。该结构列出如下:
结构lun代表了一个逻辑单元,结构lun列出如下:
函数fsg_init注册驱动程序结构实例fsg_driver,并且激活了设备控制器。函数分析如下:
驱动程序结构实例fsg_driver列出如下:
函数fsg_bind注册LUN,分配与端点配置相适应的端点,分配数据buffer,创建Gadget设备控制用的主线程fsg_main_thread。
函数fsg_bind分析如下:
线程函数fsg_main_thread是Gadget设备操作的主处理函数,它处理了设备的各种操作及事件。线程函数fsg_main_thread调用层次图如图2所示。
函数fsg_main_thread列出如下(在drivers/usb/gadget/file_storage.c中):
函数get_next_command从端点中得到CBW,并将CBW拷贝到fsg->cmnd中留待后面分析。函数get_next_command列出如下(在drivers/usb/gadget/file_storage.c中):
函数do_scsi_command分析SCSI命令,并进行相应的操作。函数列出如下:
函数do_read与函数do_write有相似的机制,这里只分析函数do_write。
函数do_write通过对2个缓冲区组成的环形缓存进行操作,实现了一方面bulk-out端点向其中一个缓冲区写数据,另一方面,函数vfs_write将另一个缓冲区的数据输出写入到LUN设备文件中。这是操作系统中的典型的读者-写者问题。
函数do_write列出如下(在drivers/usb/gadget/file_storage.c中):