Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1944304
  • 博文数量: 1000
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7921
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-20 09:23
个人简介

storage R&D guy.

文章分类

全部博文(1000)

文章存档

2019年(5)

2017年(47)

2016年(38)

2015年(539)

2014年(193)

2013年(178)

分类: LINUX

2015-05-20 16:05:09

目录
[]
USB从设备(Gadget)驱动程序模型

从设备是连接到USB主机上的设备,如:连接到计算机上的U盘、打印机等设备。从设备上还可以有高级操作系统,如:可作U盘的手机上有Linux操作系统,这种情况下的从设备驱动程序相对较复杂,它一方面要遵循Linux设备驱动程序模型,另一方面需要配合USB Gadget设备规范。下面分析有Linux操作系统从设备驱动程序。

Gadget设备驱动程序构架介绍

USB从设备驱动程序分为三个层次,USB从设备驱动程序体系图如图3所示,从底层向上,这些层是设备控制器驱动程序层、Gadget API和Gadget驱动程序层。


Linux kernel usb gadget driver 01.gif
图3 USB从设备驱动程序体系图

设备控制器驱动程序层直接与硬件通信,不同的设备控制器硬件有不同的设备控制器驱动程序。设备控制器的控制功能抽象出接口--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 kernel usb gadget driver 03.gif
图4 串口驱动程序的体系结构图

在设备侧Linux操作系统,Gadget串口驱动程序看起来象是一个串口设备。在主机系统中,Gadget串口设备看起来象是一个CDA ACM类设备或一个带有批量输入/输出的特定设备,并且,它被看作类似于其它串口设备。

CDC ACM驱动程序将USB设备向操作系统暴露为一个虚拟Modem或一个虚拟COM端口。该驱动程序通过ACM(将数据和AT命令在不同的通道分开)或串行仿真(将AT命令作为数据流的一部分),传送数据和AT命令。

Gadget对象结构

Gadget的对象由设备对象、端点对象、请求对象和驱动程序对象组成,为了遵循驱动程序模型,设备对象从基类device继承,驱动程序对象从device_driver继承。每个对象对应设备某一部分,包含了该部分的属性、运行参数和方法函数。这些对象分别说明如下:

(1)Gadget设备对象结构usb_gadget

结构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中):

struct usb_gadget { /* 对Gadget驱动程序仅读*/ //用来访问硬件特定操作的函数指针 const struct usb_gadget_ops *ops;    //0端点,当读呀写时用来对驱动程序的setup()请求的反应。 struct usb_ep *ep0; struct list_head ep_list; //设备支持的其它端点的链表 enum usb_device_speed speed; //当前连接到USB主控制器的速度     //如果支持高速或全速时为True。Gadget驱动程序也必须两者都支持。 unsigned is_dualspeed:1;   /*如果USB设备端口使用Mini-AB 插头,以致于Gadget驱动程序必须提供OTG描述子,这时为True*/ unsigned is_otg:1;   /*除非is_otg, USB电览“A”端是在Mini-AB插座里,并且HNP被用来切换角色,以致于“A”设备当前作为A周边设备而不是A-主机。*/ unsigned is_a_peripheral:1;   // OTG设备特征标识,指示A-主机在这个端口支持HNP。 unsigned b_hnp_enable:1; //设备特征标识,指示A-主机激活HNP支持。 unsigned a_hnp_support:1; //设备特征标识,指示A-主机仅在不同的根端口上支持HNPHost。 unsigned a_alt_hnp_support:1; const char *name; struct device dev; //这个设备的抽象驱动程序模型。 };


结构usb_gadget_ops 是USB设备控制器的设备操作函数集,列出如下:

struct usb_gadget_ops { int (*get_frame)(struct usb_gadget *); int (*wakeup)(struct usb_gadget *); int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered); int (*vbus_session) (struct usb_gadget *, int is_active); int (*vbus_draw) (struct usb_gadget *, unsigned mA); int (*pullup) (struct usb_gadget *, int is_on); int (*ioctl)(struct usb_gadget *, unsigned code, unsigned long param); };


(2)Gadget端点对象结构

结构usb_ep是USB端点从设备侧的代表。总线控制器驱动程序列出了在gadget->ep_list里所有通用端点。控制端点(gadget->ep0)不在链表中,并且仅在回应驱动程序的setup()回调函数时被访问。

结构usb_ep列出如下(include/linux/usb/usg_gadget.h中):

struct usb_ep {   // Gadget驱动程序使用,所有其它域对于Gadget驱动程序来说是仅读的。 void *driver_data; //端点的名字如:“ep-a”或“ep9in-bulk”。 const char *name; //用来访问硬件特定操作的函数指针。 const struct usb_ep_ops *ops; // Gadget的端点链表,连接所有的端点。 struct list_head ep_list; //这个端点上的最大包尺寸。按照配置端点的端点描述子,初始值有时降低(硬件允许时) unsigned maxpacket:16; };


结构usb_ep_ops是USB设备控制器的端点操作函数集,设备侧不同的USB控制器支持的端点数及端点的能力是不同的,结构usb_ep_ops列出如下:

struct usb_ep_ops { int (*enable) (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc); int (*disable) (struct usb_ep *ep);   struct usb_request *(*alloc_request) (struct usb_ep *ep, int gfp_flags); void (*free_request) (struct usb_ep *ep, struct usb_request *req);   void *(*alloc_buffer) (struct usb_ep *ep, unsigned bytes, dma_addr_t *dma, int gfp_flags); void (*free_buffer) (struct usb_ep *ep, void *buf, dma_addr_t dma, unsigned bytes);     int (*queue) (struct usb_ep *ep, struct usb_request *req, int gfp_flags); int (*dequeue) (struct usb_ep *ep, struct usb_request *req);   int (*set_halt) (struct usb_ep *ep, int value); int (*fifo_status) (struct usb_ep *ep); void (*fifo_flush) (struct usb_ep *ep); };


(3)请求对象结构usb_request

结构usb_request 描述了一个i/o请求,它是一个与主机侧URB结构相似的结构,除了它较小并有更多的预分配外。端点来分配/释放结构usb_request。硬件的驱动程序能加格外的per-request数据到它返回的内存里,这常避免了在请求被排队之后分配孤立的内存(潜在的失败),。

请求标识影响着请求处理,如:zero决定0长度包是否被写入,short_not_ok决定着短读是否被当作错误(先阻塞请求队列),no_interrupt与较深的请求队列一起使用时,暗示着不需要中断。

批量端点可以使用任何大小的buffer,也可被用作中断传输。仅能中断传输的端点可能有少得多的功能。

结构usb_request列出如下:

struct usb_request {    //数据使用的Buffer。一些控制器仅使用PIO或一些端点不用DMA。 void *buf; //数据的长度。 unsigned length; dma_addr_t dma;   /*如果是true,暗示不需要completion中断。对于直接被DMA控制器处理的较深的请求队列来说,有时是有帮助的。*/ unsigned no_interrupt:1; //如果是true,当写数据时,通过加一个必要的0长度包,使最后一个包是“短的”。 unsigned zero:1; //当读数据时,使短包被看作错误(队列先停止直到清除)。 unsigned short_not_ok:1;   /*当请求完成时调用的函数,这样,这个请求和它的buffer可以被再使用。当写结束时,一些数据字节将还在移动(通常在硬件fifo中)。错误(读或写的)将先停止队列直到comletion函数返回,以致于任何错误标为无效的传输可以首先被从队列上拿下来。*/ void (*complete)(struct usb_ep *ep,struct usb_request *req); // completion回调函数使用的参数 void *context; // gadget驱动程序使用 struct list_head list;   /*报告completion函数返回状态,0或一个负值的errno。错误阻塞了传输队列直到completion回调函数返回。“-ESHUTDOWN”表示设备断开或当驱动程序关闭端点引起completion。*/ int status;   /*报告来到/离开buffer的传输的字节数。对于读来说(OUT 传输)这可以小于请求的长度。如果short_not_ok被设置,短读被看作错误,甚至当状态指示成功完成。对于写来说(IN传输),当请求被报告作完成时,一些数据字节可以还残存在设备侧的FIFO中。*/ unsigned actual; };


(4)Gadget驱动程序对象结构usb_gadget_driver

结构 usb_gadget_driver是USB Gadget的驱动程序结构,从类device_driver继承。设备被关闭直到一个Gadget驱动程序被成功bind(),这意味着驱动程序将处理setup()请求来进行硬件枚举。如果gadget->is_otg 是true, gadget在枚举期间必须提供一个OTG描述子。

结构 usb_gadget_driver列出如下:

struct usb_gadget_driver { char *function; //功能的字符描述。 enum usb_device_speed speed;// 驱动程序处理的最高速度。   /*当驱动程序绑定到一个Gadget时触发它,通常在注册驱动程序后。在这点,ep0被完全初始化,并且ep_list持有当前可用的所有端点。在允许睡眠的上下文中调用。*/ int (*bind)(struct usb_gadget *); void (*unbind)(struct usb_gadget *);   /*为ep0控制请求触发,这些控制请求不被硬件级驱动程序处理。大多的调用必须被Gadget驱动程序处理,包括描述符及配置的管理。*/ int (*setup)(struct usb_gadget *, const struct usb_ctrlrequest *); //在主机被断开时,在所有的传输停止后被触发。 void (*disconnect)(struct usb_gadget *); void (*suspend)(struct usb_gadget *); void (*resume)(struct usb_gadget *);   struct device_driver driver; };


Gadget API接口函数说明

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列出如下:

static inline int usb_ep_enable (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) { return ep->ops->enable (ep, desc); }


函数 usb_ep_disable使端点失效不可再用。函数列出如下:

static inline int usb_ep_disable (struct usb_ep *ep) { return ep->ops->disable (ep); }


函数 usb_ep_alloc_request 给这个端点分配一个请求对象。请求可能被usb_ep_queue()提交,并接收一个completion回调函数。函数列出如下:

static inline struct usb_request *usb_ep_alloc_request (struct usb_ep *ep, int gfp_flags) { return ep->ops->alloc_request (ep, gfp_flags); }


函数 usb_ep_free_request释放一个请求对象。函数列出如下:

static inline void usb_ep_free_request (struct usb_ep *ep, struct usb_request *req) { ep->ops->free_request (ep, req); }


函数 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映射的代价。函数列出如下:

static inline void *usb_ep_alloc_buffer (struct usb_ep *ep,         unsigned len, dma_addr_t *dma, int gfp_flags) { return ep->ops->alloc_buffer (ep, len, dma, gfp_flags); }


函数 usb_ep_free_buffer释放i/o buffer。函数列出如下:

static inline void usb_ep_free_buffer (struct usb_ep *ep, void *buf, dma_addr_t dma, unsigned len) { ep->ops->free_buffer (ep, buf, dma, len); }


函数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驱动程序通常在这个时刻已排队一些数据来传输。

static inline int usb_ep_queue (struct usb_ep *ep, struct usb_request *req, int gfp_flags) { return ep->ops->queue (ep, req, gfp_flags); }


函数 usb_ep_dequeue从一个端点取消或删除一个I/O请求的排队,如果请求还在端点上是激活的,它将从队列上被取下,并调用completion回调函数(带有状态-ECONNRESET)。函数 usb_ep_dequeue列出如下:

static inline int usb_ep_dequeue (struct usb_ep *ep, struct usb_request *req) { return ep->ops->dequeue (ep, req); }


函数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列出如下:

static inline int usb_ep_set_halt (struct usb_ep *ep) { return ep->ops->set_halt (ep, 1); }


函数 usb_ep_clear_halt清除端点停止,在清除端点I/O队列中的任何其它状态之后,当对标准的"set interface"请求作出响应时使用这个函数。对于端点来说,这不是再配置。函数调用成功时,返回0,它清除了所依赖的反映端点停止的硬件状态。调用失败时,返回负值的错误码。注意有些硬件不支持这个请求(如:pxa2xx_udc),因此不能正确地利用接口替换配置。

函数 usb_ep_clear_halt列出如下:

static inline int usb_ep_clear_halt (struct usb_ep *ep) { return ep->ops->set_halt (ep, 0); }


函数 usb_ep_fifo_status返回在FIFO中的字节数,如果端点不能使用FIFO或不支持错误处理时返回负值的错误码。端点FIFO在某些情况下(如:在错误退出的传输之后),可能有"没有声明的数据"。主机可能没有收集到Gadget驱动程序写的所有数据(并且被请求的complete回调函数报告)。Gadget驱动程序可能没有收集到主机OUT的所有数据。需要精确处理错误报告或恢复的驱动程序必须使用这个调用。

函数 usb_ep_fifo_status列出如下:

static inline int usb_ep_fifo_status (struct usb_ep *ep) { if (ep->ops->fifo_status) return ep->ops->fifo_status (ep); else return -EOPNOTSUPP; }


函数 usb_ep_fifo_flush刷新 – 转储清除FIFO的内容。被用来在异常事务结束后转储清除在端点FIFO中的"没声明的数据"。

函数 usb_ep_fifo_flush列出如下:

static inline void usb_ep_fifo_flush (struct usb_ep *ep) { if (ep->ops->fifo_flush) ep->ops->fifo_flush (ep); }


Gadget控制器pxa27x驱动程序

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的对象结构

控制器pxa27x遵循Gadget驱动程序模型,其设备对象、端点对象、请求对象都从Gadget驱动程序模型的基类继承,分别说明如下:

(1)控制器设备对象结构pxa_udc

控制器pxa27x设备对象用结构pxa_udc描述,它从基类usb_gadget继承,包含了控制器pxa27x的资源信息、端点数组、配置信息、使用该设备的Gadget驱动程序列表等,其列出如下:

struct pxa_udc { void __iomem *regs; /*映射的I/O空间*/ int irq; /*udc中断*/ struct clk *clk; /*ude时钟*/   struct usb_gadget gadget; /*udc的Gadget基类结构*/ /*绑定的Gadget驱动程序表,如:大存储驱动程序、zero设备驱动程序等*/ struct usb_gadget_driver *driver; struct device *dev; /*设备*/ struct pxa2xx_udc_mach_info *mach; /*机器信息,用于激活特定的GPIO*/ enum ep0_state ep0state; /*控制端点的状态机状态*/ struct udc_stats stats; /*udc使用的统计信息*/   struct udc_usb_ep udc_usb_ep[NR_USB_ENDPOINTS]; /*Gadget提供的usb端点数组*/ struct pxa_ep pxa_ep[NR_PXA_ENDPOINTS]; /*pxa可用的端点数组*/   unsigned config:2; /*UDC激活的配置*/ unsigned last_interface:3; /*上一次SET_INTERFACE主机请求的udc接口*/ unsigned last_alternate:3; /*上一次SET_INTERFACE主机请求的udc替代配置*/   #ifdef CONFIG_PM unsigned udccsr0; /*在挂起情况下的udccsr0存储值*/ #endif };


(2)控制器端点对象结构

控制器端点对象用结构udc_usb_ep描述,它从端点基类结构usb_ep继承,包含了端点描述子、端点所在的设备对象、pxa设备端点信息。

结构udc_usb_ep列出如下(在drivers/usb/gadget/pxa27x_udc.h中):

struct udc_usb_ep { struct usb_ep usb_ep; /*表示从基类结构继承*/ struct usb_endpoint_descriptor desc; /*端点描述子*/ struct pxa_udc *dev; /*管理此端点的udc设备对象*/ struct pxa_ep *pxa_ep; /*pxa设备端点对象*/ };


结构pxa_ep描述了pxa设备端点对象的信息,包括端点上的请求队列、端点属性、统计状态等信息。当静态地定义结构pxa_ep实例时,用户必须为所有的Gadget猜测所有需要的端点结构pxa_ep实例,该端点实例应可与udc驱动程序一起工作。

结构pxa_ep列出如下:

struct pxa_ep { struct pxa_udc *dev; /*端点所在的设备对象*/   struct list_head queue; /*端点上的请求队列*/ spinlock_t lock; /* 保护此结构成员(queue,stat)的锁*/ unsigned enabled:1; /*当端点激活时设置为1,激活的端点不能由gadget层停止*/   unsigned idx:5; /*端点索引值(1 => epA, 2 => epB, ..., 24 => epX)*/ char *name; /*端点名,用于跟踪或调试目的*/   /*指定的pxa端点数据,硬件初始化时需要*/ unsigned dir_in:1; /*端点传输方向,IN端点为1,OUT端点为0*/ unsigned addr:3; /*usb端点号*/ unsigned config:2; /*端点激活时的配置*/ unsigned interface:3; /*端点激活时的接口*/ unsigned alternate:3; /*端点激活时的替代配置*/ unsigned fifo_size; /*端点FIFO的最大包尺寸*/ unsigned type; /*端点类型(bulk, iso, int, ...)*/   #ifdef CONFIG_PM u32 udccsr_value; /*用于挂起/恢复的UDCCSR0寄存器的存储值*/ u32 udccr_value; /*用于挂起/恢复的UDCCR寄存器的存储值*/ #endif struct stats stats; /*端点的统计信息*/ };


(3)请求对象结构pxa27x_request

pxa端点上的每个请求对象用结构usb_request描述,它从请求的基类usb_request继承,其列出如下:

struct pxa27x_request { struct usb_request req; /*从请求的基类结构继承*/ struct udc_usb_ep *udc_usb_ep; /*请求提交的端点*/ unsigned in_use:1; /*检查请求是否已在端点结构pxa_ep实例上排队*/ struct list_head queue; /*用于链接在pxa_ep->queue上*/ };


控制器pxa27x的对象实例化

编写控制器pxa27x驱动程序的过程实际上是对各种对象的实例化的过程,对于控制器在运行中不变的属性和方法函数,用户通过对象静态实例化指定属性值和方法函数;对于根据启动状态变化的属性和方法函数,通过探测函数动态指定给对象实例;对于运行中会变化的属性和方法函数,通过消息控制切换。下面列出驱动模型的各种静态实例。

(1)设备对象实例化

设备对象实例memory对设备的端点、设备对象进行了实例化,该结构实例列出如下(在driver/usb/gadget/pxa27x_udc.c中):

static struct pxa_udc memory = { .gadget = { .ops = &pxa_udc_ops, .ep0 = &memory.udc_usb_ep[0].usb_ep, .name = driver_name, .dev = { .bus_id = "gadget", }, },   .udc_usb_ep = { USB_EP_CTRL, USB_EP_OUT_BULK(1), USB_EP_IN_BULK(2), USB_EP_IN_ISO(3), USB_EP_OUT_ISO(4), USB_EP_IN_INT(5), },   .pxa_ep = { /*将15个端点分配给不同Gadget,指定端点的配置、方向、类型等*/ PXA_EP_CTRL, /* 用于Gadget zero的端点 */ PXA_EP_OUT_BULK(1, 1, 3, 0, 0), PXA_EP_IN_BULK(2, 2, 3, 0, 0), /* 用以太网、文件存储Gadget的端点*/ PXA_EP_OUT_BULK(3, 1, 1, 0, 0), PXA_EP_IN_BULK(4, 2, 1, 0, 0), PXA_EP_IN_ISO(5, 3, 1, 0, 0), PXA_EP_OUT_ISO(6, 4, 1, 0, 0), PXA_EP_IN_INT(7, 5, 1, 0, 0), /* 用于RNDIS、串口的端点*/ PXA_EP_OUT_BULK(8, 1, 2, 0, 0), PXA_EP_IN_BULK(9, 2, 2, 0, 0), PXA_EP_IN_INT(10, 5, 2, 0, 0), /*下面两个端点仅用于completion操作*/ PXA_EP_OUT_BULK(11, 1, 2, 1, 0), PXA_EP_IN_BULK(12, 2, 2, 1, 0), /* 用于CDC以太网的端点E */ PXA_EP_OUT_BULK(13, 1, 1, 1, 1), PXA_EP_IN_BULK(14, 2, 1, 1, 1), } };   static const struct usb_gadget_ops pxa_udc_ops = { .get_frame = pxa_udc_get_frame, .wakeup = pxa_udc_wakeup, };


(2)驱动程序对象实例化

驱动程序实例udc_driver列出如下:

static struct platform_driver udc_driver = { .driver = { .name = "pxa27x-udc", .owner = THIS_MODULE, }, .remove = __exit_p(pxa_udc_remove), .shutdown = pxa_udc_shutdown, #ifdef CONFIG_PM .suspend = pxa_udc_suspend, .resume = pxa_udc_resume #endif };


(3)设备控制器的初始化

函数udc_init注册了设备控制器驱动程序结构实例udc_driver,函数列出如下:

static int __init udc_init(void) { if (!cpu_is_pxa27x()) return -ENODEV;   printk(KERN_INFO "%s: version %s\n", driver_name, DRIVER_VERSION); /*将探测函数指定给驱动模型基类device中的probe指针*/ return platform_driver_probe(&udc_driver, pxa_udc_probe); }


设备控制器的探测函数

函数pxa_udc_probe探测设备,初始化设备结构,请求中断号,设置中断处理函数,该函数列出如下:

static int __init pxa_udc_probe(struct platform_device *pdev) { struct resource *regs; struct pxa_udc *udc = &memory; int retval;   regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) return -ENXIO; udc->irq = platform_get_irq(pdev, 0); /*获取中断号*/ if (udc->irq < 0) return udc->irq;   udc->dev = &pdev->dev; udc->mach = pdev->dev.platform_data;   udc->clk = clk_get(&pdev->dev, "UDCCLK"); /*获取时钟*/ …… /*省略打印出错信息*/   retval = -ENOMEM; udc->regs = ioremap(regs->start, regs->end - regs->start + 1); /*I/O映射*/ …… /*省略打印出错信息*/   device_initialize(&udc->gadget.dev); /*驱动程序模型初始化*/ udc->gadget.dev.parent = &pdev->dev; udc->gadget.dev.dma_mask = NULL;   the_controller = udc; platform_set_drvdata(pdev, udc); udc_init_data(udc); pxa_eps_setup(udc);   /* 在旧的硬件状态清除后,建立中断*/ /*中断处理例程pxa_udc_irq根据中断状态寄存器的标识值对设点进行相应处理,如:接收数据*/ retval = request_irq(udc->irq, pxa_udc_irq, IRQF_SHARED, driver_name, udc); …… /*省略打印出错信息*/   pxa_init_debugfs(udc); /*初始化调试文件系统,调试文件系统用来替代/proc或/sys,用于调试*/ return 0; …… /*省略出错处理*/ }


中断处理例程

pxa27x设备控制器通过寄存器由软件读取设备状态或控制设备,其中,UDC控制寄存器控制UDC设备的功能,中断控制器打开或关闭中断,中断状态寄存器说明引起中断的原因,端口控制寄存器控制端口的功能。详细寄存器说明请参考厂商的pxa27x设备手册。pxa27x设备控制器的寄存器定义列出如下(在driver/usb/gadget/pxa27x_udc.h中):

/* 寄存器地址偏移*/ #define UDCCR 0x0000 /* UDC Control Register */ #define UDCICR0 0x0004 /* UDC Interrupt Control Register0 */ #define UDCICR1 0x0008 /* UDC Interrupt Control Register1 */ #define UDCISR0 0x000C /* UDC Interrupt Status Register 0 */ #define UDCISR1 0x0010 /* UDC Interrupt Status Register 1 */ #define UDCFNR 0x0014 /* UDC Frame Number Register */ #define UDCOTGICR 0x0018 /* UDC On-The-Go interrupt control */ #define UP2OCR 0x0020 /* USB Port 2 Output Control register */ #define UP3OCR 0x0024 /* USB Port 3 Output Control register */ #define UDCCSRn(x) (0x0100 + /* UDC Control/Status register */ #define UDCBCRn(x) (0x0200 + /* UDC Byte Count Register */ #define UDCDRn(x) (0x0300 + /* UDC Data Register */ #define UDCCRn(x) (0x0400 + /* UDC Control Register */


pxa27x设备控制器上的数据传输、电源管理等都通过中断处理例程来实现的,中断处理例程的主函数pxa_udc_irq的调用层次图如图5所示。下面按该图分析各个函数。


Linux kernel usb gadget driver 04.gif
图5 函数pxa_udc_irq的调用层次图

函数pxa_udc_irq是中断处理例程的主函数,处理所有USB控制设备的中断,参数irq为中断号,参数_dev为设备对象。它从中断状态寄存器中读取中断请求,根据中断请求调用相应的功能函数。如:IN端点数据寄存器发送完数据后,发出中断请求:要求从USB请求中写数据包到端点。OUT端点FIFO数据装满时,发出中断请求:要求将FIFO中数据读出到USB请求中。

函数pxa_udc_irq列出如下:

static irqreturn_t pxa_udc_irq(int irq, void *_dev) { struct pxa_udc *udc = _dev; u32 udcisr0 = udc_readl(udc, UDCISR0); /*读取中断状态寄存器UDCISR0*/ u32 udcisr1 = udc_readl(udc, UDCISR1); /*读取中断状态寄存器UDCISR1*/ u32 udccr = udc_readl(udc, UDCCR); /*读取控制寄存器UDCCR*/ u32 udcisr1_spec;   udcisr1_spec = udcisr1 & 0xf8000000; if (unlikely(udcisr1_spec & UDCISR1_IRSU)) /*中断请求为挂起电源(Suspend)*/ irq_udc_suspend(udc); /*调用驱动程序的挂起函数udc->driver->suspend(&udc->gadget)*/ if (unlikely(udcisr1_spec & UDCISR1_IRRU)) /*中断请求为恢复电源(Resume)*/ irq_udc_resume(udc); /*调用驱动程序的恢复函数udc->driver->resume(&udc->gadget)*/ if (unlikely(udcisr1_spec & UDCISR1_IRCC))/*中断请求为配置发生变化(Configure Change)*/ irq_udc_reconfig(udc); /*切换到新的配置*/ if (unlikely(udcisr1_spec & UDCISR1_IRRS)) /*中断请求为复位(Reset)*/ irq_udc_reset(udc);   if ((udcisr0 & UDCCISR0_EP_MASK) | (udcisr1 & UDCCISR1_EP_MASK)) irq_handle_data(irq, udc); /*处理端点的数据传输*/   return IRQ_HANDLED; }

函数irq_handle_data从请求队列到端点传输数据或者从端点到请求队列传输数据。其列出如下:

/*端点FIFO出错中断或包完成中断*/ #define UDCISR_INT_MASK (UDCICR_FIFOERR | UDCICR_PKTCOMPL) static void irq_handle_data(int irq, struct pxa_udc *udc) { int i; struct pxa_ep *ep; u32 udcisr0 = udc_readl(udc, UDCISR0) & UDCCISR0_EP_MASK; u32 udcisr1 = udc_readl(udc, UDCISR1) & UDCCISR1_EP_MASK;   if (udcisr0 & UDCISR_INT_MASK) { udc->pxa_ep[0].stats.irqs++; /*将掩码值写中寄存器UDCISR0,屏蔽其他中断,包只能被一个端点处理*/ udc_writel(udc, UDCISR0, UDCISR_INT(0, UDCISR_INT_MASK)); handle_ep0(udc, !!(udcisr0 & UDCICR_FIFOERR), !!(udcisr0 & UDCICR_PKTCOMPL)); }   /*处理各个端点的数据传输*/ udcisr0 >>= 2; for (i = 1; udcisr0 != 0 && i < 16; udcisr0 >>= 2, i++) { if (!(udcisr0 & UDCISR_INT_MASK)) continue;   udc_writel(udc, UDCISR0, UDCISR_INT(i, UDCISR_INT_MASK)); ep = &udc->pxa_ep[i]; ep->stats.irqs++; handle_ep(ep); }   for (i = 16; udcisr1 != 0 && i < 24; udcisr1 >>= 2, i++) { udc_writel(udc, UDCISR1, UDCISR_INT(i - 16, UDCISR_INT_MASK)); if (!(udcisr1 & UDCISR_INT_MASK)) continue;   ep = &udc->pxa_ep[i]; ep->stats.irqs++; handle_ep(ep); }   }

函数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》也不兼容。状态机的要点说明如下:

  • 在每个setup(建立)特征上,位UDCCSR0_SA被设置并保持直到被软件清除。
  • 在每个接收到的OUT包上,UDCCSR0_OPC被设置并保持直到被软件清除。
  • 清除UDCCSR0_OPC时,总是刷新ep0。如果在setup阶段,在读取ep0之前,从不这样做。
  • 当UDCCSR0_OPC还没被设置(此时间差可达100ms)时,在"包完成"事件上,中断可被调用。
  • 当在中断正处理中UDCCSR0_SA被激活,并且清除时UDCCSR0_OPC将刷新setup数据时,通常从不清除UDCCSR0_OPC:实际上从不读取IN数据阶段的"状态段(STATUS STAGE)"包。
  • 硬件上没有STATUS STAGE的概念,它仅处理SETUP STAGE和DATA STAGE。驱动程序增加STATUS STAGE,用来发送在OUT_STATUS_STAGE的最后的0长度包。
  • IN_STATUS_STAGE需要注意的是:如果一个包完成时检测到事件,用户决定"状态段",不用通知包,这样,不会有丢失潜在SETUP包的风险。

函数handle_ep0列出如下:

static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) { u32 udccsr0; struct pxa_ep *ep = &udc->pxa_ep[0]; struct pxa27x_request *req = NULL; int completed = 0;   udccsr0 = udc_ep_readl(ep, UDCCSR); /*读取控制状态寄存器UDCCSR*/   /*从请求队列中获取请求*/ if (!list_empty(&ep->queue)) req = list_entry(ep->queue.next, struct pxa27x_request, queue);   if (udccsr0 & UDCCSR0_SST) { /*延迟发送(Sent Stall)*/ ep_dbg(ep, "clearing stall status\n"); /*从队列中取下一个端点的所有请求,逐个发送完成通知,送回管道错误(-EPIPE)的请求状态。副作用是:将关闭该端点上的中断,因为不再有请求*/ nuke(ep, -EPIPE); udc_ep_writel(ep, UDCCSR, UDCCSR0_SST); /*写寄存器UDCCSR*/ ep0_idle(udc); /*将端点0置为空闲*/ }   if (udccsr0 & UDCCSR0_SA) { /* SETUP_STAGE阶段激活(Setup Active)*/ nuke(ep, 0); set_ep0state(udc, SETUP_STAGE); }   /*用状态机控制端点0的功能切换,状态机的状态值存放在udc->ep0state中*/ switch (udc->ep0state) { /*根据端点0的不同状态进行处理*/ case WAIT_FOR_SETUP:   break; case SETUP_STAGE: udccsr0 &= UDCCSR0_CTRL_REQ_MASK; /*输出包完成或Setup激活或接收FIFO不为空*/ if (likely(udccsr0 == UDCCSR0_CTRL_REQ_MASK)) handle_ep0_ctrl_req(udc, req); /*处理控制端点的控制请求*/ break; case IN_DATA_STAGE: /* GET_DESCRIPTOR */ if (epout_has_pkt(ep)) /*如果OUT端点有可用的包,返回1*/ udc_ep_writel(ep, UDCCSR, UDCCSR0_OPC);/*写寄存器,UDCCSR0_OPC表示输出包完成*/ if (req && !ep_is_full(ep)) /*如果有请求且端点非满,发送一个请求到端点0*/ completed = write_ep0_fifo(ep, req); /*结束控制端点输入请求,设置状态机状态为IN_STATUS_STAGE,输入状态计数加1, 调用函数req->req.complete()进行操作完成处理*/ if (completed) ep0_end_in_req(ep, req); break; case OUT_DATA_STAGE: /* SET_DESCRIPTOR */ if (epout_has_pkt(ep) && req) /*从数据寄存器(即FIFO)中传输所有包到req中*/ completed = read_ep0_fifo(ep, req); /*结束控制端点输出请求,设置状态机状态为OUT_STATUS_STAGE,输出状态计数加1, 调用函数req->req.complete()进行操作完成处理*/ if (completed) ep0_end_out_req(ep, req); break; case STALL: udc_ep_writel(ep, UDCCSR, UDCCSR0_FST); /*写控制状态寄存器UDCCSR:强制延迟*/ break; case IN_STATUS_STAGE:   if (opc_irq) ep0_idle(udc); break; case OUT_STATUS_STAGE: case WAIT_ACK_SET_CONF_INTERF: ep_warn(ep, "should never get in %s state here!!!\n", EP0_STNAME(ep->dev)); ep0_idle(udc); break; } }


函数handle_ep0_ctrl_req从端点0的数据FIFO中读取SETUP包,不作处理。根据控制端点的控制请求,设置状态机状态,并将请求交给Gadget驱动程序,处理硬件不支持的请求。参数udc为USB设备控制器对象,参数req为端点控制请求对象。

函数handle_ep0_ctrl_req列出如下:

static void handle_ep0_ctrl_req(struct pxa_udc *udc, struct pxa27x_request *req) { struct pxa_ep *ep = &udc->pxa_ep[0]; union { struct usb_ctrlrequest r; u32 word[2]; } u; int i; int have_extrabytes = 0;   /*从队列中取下一个端点的所有请求,逐个发送完成通知,送回协议错误(-EPROTO)的请求状态。副作用是:将关闭该端点上的中断,因为不再有请求*/ nuke(ep, -EPROTO);   /* 读取SETUP包 */ for (i = 0; i < 2; i++) { if (unlikely(ep_is_empty(ep))) /*如果端点为空,进入延迟处理*/ goto stall; /*读取寄存器UDCDR(UDC Endpoint Data Register,UDC端点数据寄存器,即数据FIFO*/ u.word[i] = udc_ep_readl(ep, UDCDR); }   /*此时,端点非空,表示有附加字节,但在Setup阶段,不应有附加字节,打印错误信息*/ have_extrabytes = !ep_is_empty(ep); while (!ep_is_empty(ep)) { i = udc_ep_readl(ep, UDCDR); ep_err(ep, "wrong to have extra bytes for setup : 0x%08x\n", i); }   if (unlikely(have_extrabytes)) goto stall;   if (u.r.bRequestType & USB_DIR_IN) set_ep0state(udc, IN_DATA_STAGE); /*将状态机设置为输入数据阶段*/ else set_ep0state(udc, OUT_DATA_STAGE); /*将状态机设置为输出数据阶段*/   /*写控制状态寄存器,告诉UDC进入数据阶段, UDCCSR0_SA|UDCCSR0_OPC表示Setup激活或输出包完成*/ udc_ep_writel(ep, UDCCSR, UDCCSR0_SA | UDCCSR0_OPC);   /*UDC硬件不能处理的控制请求,交给Gadget驱动程序处理*/ i = udc->driver->setup(&udc->gadget, &u.r); if (i < 0) goto stall; out: return; stall: /* UDCCSR0_FST为强制延迟(Force Stall),UDCCSR0_FT为刷新传输FIFO(Flush Transmit FIFO)*/ udc_ep_writel(ep, UDCCSR, UDCCSR0_FST | UDCCSR0_FTF); set_ep0state(udc, STALL); /*设置状态机状态*/ goto out; }


函数write_ep0_fifo 发送一个请求(或请求的一部分)到控制端点(ep0 IN)。如果请求大小不合适,剩余部分将从中断发送。请求满足下面任一条件时被认为完全写入:

  • 上一次写传送的所有剩余字节,但FIFO还没有装满。
  • 上一次写操作是0长度的写。

如果请求完全写入,函数返回1,如果仅部分发送,返回0.

函数write_ep0_fifo列出如下:

static int write_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) { unsigned count; int is_last, is_short;   /*从请求中提到一个包,写入端点,EP0_FIFO_SIZE 定义为16*/ count = write_packet(ep, req, EP0_FIFO_SIZE); inc_ep_stats_bytes(ep, count, USB_DIR_IN); /*发送或接收的字节计数*/   is_short = (count < EP0_FIFO_SIZE); is_last = ((count == 0) || (count < EP0_FIFO_SIZE));   /* 发送短包或一个0长度包*/ if (unlikely(is_short)) /*写控制状态寄存器UDCCSR,UDCCSR0_IPR表示输入包准备好(In Package Ready)*/ udc_ep_writel(ep, UDCCSR, UDCCSR0_IPR);   return is_last; }


函数write_packet从请求中提取1个包到一个IN端点,参数ep为物理端点对象,参数req为请求对象,参数max为适配于端点的最大字节数。函数write_packet从USB请求中提取字节,通过写数据寄存器传送数据到物理端点。如果没有字节传输,不写任何字节到物理端点。函数返回实际传输的字节字节数。

函数write_packet列出如下:

static int write_packet(struct pxa_ep *ep, struct pxa27x_request *req, unsigned int max) { int length, count, remain, i; u32 *buf; u8 *buf_8;   buf = (u32 *)(req->req.buf + req->req.actual); prefetch(buf); /*将地址buf所在内存预装进到CPU L1 Cache*/   length = min(req->req.length - req->req.actual, max); /*长度不能超过max*/ req->req.actual += length; /*用于请求字节的传送定位*/   remain = length & 0x3; /*在双字对齐内的剩余字节数*/ count = length & ~(0x3); /*以双字为单位的长度计数*/ for (i = count; i > 0 ; i -= 4) udc_ep_writel(ep, UDCDR, *buf++);/*将buf数据以双字为单位写入数据寄存器(即FIFO)*/   buf_8 = (u8 *)buf; for (i = remain; i > 0; i--) /*将buf数据以字节为单位写入数据寄存器(即FIFO)*/ udc_ep_writeb(ep, UDCDR, *buf_8++);   return length; }


函数handle_ep尝试传输所有挂起的请求数据到端点或传输端点中所有挂起的数据到USB请求中。传输数据的方法是通过读/写数据寄存器(或FIFO)实现。

函数handle_ep列出如下:

static void handle_ep(struct pxa_ep *ep) { struct pxa27x_request *req; int completed; u32 udccsr; int is_in = ep->dir_in; int loop = 0;   do { completed = 0; udccsr = udc_ep_readl(ep, UDCCSR); /*读控制状态寄存器UDCCSR*/ if (likely(!list_empty(&ep->queue))) /*如果请求队列非空,从队列中取下请求*/ req = list_entry(ep->queue.next, struct pxa27x_request, queue); else req = NULL;       /* UDCCSR_SST为延迟发送(Sent Stall),UDCCSR_TRN为接收/发送没有确认(Rx/Tx NAK)*/ if (unlikely(udccsr & (UDCCSR_SST | UDCCSR_TRN))) udc_ep_writel(ep, UDCCSR, udccsr & (UDCCSR_SST | UDCCSR_TRN)); /*写寄存器UDCCSR*/ if (!req) break;   if (unlikely(is_in)) { if (likely(!ep_is_full(ep))) completed = write_fifo(ep, req); /*从请求中传输数据包到IN端点*/ if (completed) /*传送完成*/ /*设置状态机状态为USB_DIR_OUT,输入状态计数加1, 调用函数req->req.complete()进行操作完成处理*/ ep_end_in_req(ep, req); } else { if (likely(epout_has_pkt(ep))) completed = read_fifo(ep, req); /*从OUT端点读出数据包到请求中*/ if (completed) /*设置状态机状态为USB_DIR_IN,输出状态计数加1, 调用函数req->req.complete()进行操作完成处理*/ ep_end_out_req(ep, req); } } while (completed); }


Gadget文件系统gadgetfs

文件系统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中):

struct dev_data { spinlock_t lock; atomic_t count; enum ep0_state state;   //包括控制请求、事件类型(断开、连接、挂起、控制建立)、速度类型 struct usb_gadgetfs_event event [N_EVENT]; unsigned ev_next; struct fasync_struct *fasync; u8 current_config; //当前配置   //驱动程序读ep0必须处理控制请求(SETUP),否则主机将超时 unsigned usermode_setup : 1, setup_in : 1, setup_can_stall : 1, setup_out_ready : 1, setup_out_error : 1, setup_abort : 1;   /* 其余的域基本上只写一次*/ struct usb_config_descriptor *config, *hs_config; //配置描述子 struct usb_device_descriptor *dev; //设备描述子 struct usb_request *req; //USB请求 struct usb_gadget *gadget; struct list_head epfiles; //每个端点用一个文件表示,端点文件链表 void *buf; wait_queue_head_t wait; struct super_block *sb;  //超级块 struct dentry *dentry;   /* 用于ep0的擦琯I/O buffer,可多次写*/ u8 rbuf [256]; };


(2)端点对象结构ep_data

结构ep_data代表了一个端点对象,由于在gadgetfs文件系统中,端点用一个文件描述,因而,结构ep_data除了包含端点相关的域(如:请求、状态等)外,还包含了与文件相关的域(如:dentry、inode等)。

结构ep_data列出如下:

struct ep_data { struct semaphore lock; enum ep_state state; atomic_t count; struct dev_data *dev; /*在访问端点或请求前,必须持有锁dev->lock */ struct usb_ep *ep; struct usb_request *req; ssize_t status; char name [16]; struct usb_endpoint_descriptor desc, hs_desc; /*端点描述子*/ struct list_head epfiles; /*端点文件链表*/ wait_queue_head_t wait; struct dentry *dentry; struct inode *inode; };


文件系统gadgetfs初始化

函数init注册了文件系统类型gadgetfs_type,函数列出如下:

static int __init init (void) { int status;   //注册文件系统类型gadgetfs_type status = register_filesystem (&gadgetfs_type); if (status == 0) pr_info ("%s: %s, version " DRIVER_VERSION "\n", shortname, driver_desc); return status; }


文件系统类型实例gadgetfs_type列出如下:

static const char shortname [] = "gadgetfs"; static struct file_system_type gadgetfs_type = { .owner = THIS_MODULE, .name = shortname, .get_sb = gadgetfs_get_sb, .kill_sb = gadgetfs_kill_sb, };


当文件系统挂接时,命令mount会通过系统调用来调用函数gadgetfs_get_sb获取超级块对象,该函数列出如下:

/* 挂接文件系统方法 为:mount -t gadgetfs path /dev/gadget */ static struct super_block *gadgetfs_get_sb (struct file_system_type *t, int flags, const char *path, void *opts) { return get_sb_single (t, flags, opts, gadgetfs_fill_super); }


文件系统gadgetfs继承了Gadget驱动模型基类和文件系统基类,因此,在初始化超级块函数gadgetfs_fill_super中,一方面应按文件系统模型初始化超级块、dentry、inode等文件系统对象,另一方面,还应按Gadget驱动模型初始化驱动程序、设备等对象。

函数gadgetfs_fill_super初始化超级块,创建根节点,创建名为CHIP的文件,它实际上是ep0的控制文件,在设备控制器被配置之前,这个文件的写操作函数就是对设备控制器进行配置,在设备控制器被配置之后,对这个函数的操作就是对设备控制器进行控制。

函数gadgetfs_fill_super列出如下(在driver/usb/gadget/inode.c中):

static int gadgetfs_fill_super (struct super_block *sb, void *opts, int silent) { struct inode *inode; struct dentry *d; struct dev_data *dev;   if (the_device) return -ESRCH;   /* 注册Gadget驱动程序实例probe_driver */ (void) usb_gadget_register_driver (&probe_driver); if (!CHIP) return -ENODEV;   /* 初始化超级块 */ sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = GADGETFS_MAGIC; sb->s_op = &gadget_fs_operations; //只提供简单的操作函数 sb->s_time_gran = 1;   /* 根节点 */   //创建根节点并初始化,赋上简单的文件操作函数 inode = gadgetfs_make_inode (sb, NULL, &simple_dir_operations, S_IFDIR | S_IRUGO | S_IXUGO); if (!inode) return -ENOMEM; inode->i_op = &simple_dir_inode_operations; //简单节点操作函数 if (!(d = d_alloc_root (inode))) {//分配根的dentry iput (inode); return -ENOMEM; } sb->s_root = d;   // ep0文件在以我们期望的控制器命名,用户模式能用它来做检查  dev = dev_new (); //创建dev_data结构并初始化 if (!dev) return -ENOMEM;   dev->sb = sb; //超级块 /*创建名为CHIP的文件,并创建对应的节点及dentry,并初始化,赋上dev和文件操作函数集dev_init_operations*/ if (!(inode = gadgetfs_create_file (sb, CHIP, dev, &dev_init_operations, &dev->dentry))) { put_dev(dev); return -ENOMEM; }   // 其他节点文件在硬件建立后通过绑定到控制器变为可用的  the_device = dev; return 0; }


由于Gadget驱动程序的功能实现上由文件系统实现,因此,初始化时,Gadget驱动程序对象基本上不作任何操作。该对象实例probe_driver列出如下:

static struct usb_gadget_driver probe_driver = { .speed = USB_SPEED_HIGH, .bind = gadgetfs_probe, /*探测决定$CHIP名,即CHIP = gadget->name*/ .unbind = gadgetfs_nop, /*空函数*/ .setup = (void *)gadgetfs_nop, .disconnect = gadgetfs_nop, .driver = { .name = "nop", }, };


ep0文件对应的操作函数集实例列出如下:

static struct file_operations dev_init_operations = { .owner = THIS_MODULE, .llseek = no_llseek,   .open = dev_open, //打开设备dev_data, .write = dev_config,//配置注册Gadget .fasync = ep0_fasync, //字符设备建立fasync队列 .ioctl = dev_ioctl, //调用gadget->ops->ioctl .release = dev_release,//释放设备 };


USB设备配置操作

USB设备在使用之前必须进行配置,用户空间应用程序通过文件系统gadgetfs进入内核,调用函数dev_config完成设备配置操作。

函数dev_config从用户空间得到设备描述子和配置描述子,对设备及端点进行配置。它还注册gadgetfs_driver驱动程序,从驱动程序的角度看,gadgetfs是一个虚拟的Gadget驱动程序。

函数dev_config列出如下:

static ssize_t dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) { struct dev_data *dev = fd->private_data; ssize_t value = len, length = len; unsigned total; u32 tag; char *kbuf;   if (dev->state != STATE_OPENED) return -EEXIST; //配置描述子+设备描述子+tag标签大小 if (len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4)) return -EINVAL;   if (copy_from_user (&tag, buf, 4)) //从buf中得到tag标签 return -EFAULT; if (tag != 0) return -EINVAL; buf += 4; length -= 4;   kbuf = kmalloc (length, SLAB_KERNEL); if (!kbuf) return -ENOMEM; if (copy_from_user (kbuf, buf, length)) { kfree (kbuf); return -EFAULT; }   spin_lock_irq (&dev->lock); value = -EINVAL; if (dev->buf) goto fail; dev->buf = kbuf;   /* 全速或低速配置 */ dev->config = (void *) kbuf; total = le16_to_cpup (&dev->config->wTotalLength); if (!is_valid_config (dev->config) || total >= length) goto fail; kbuf += total; length -= total;   /* 可选的高速配置 */ if (kbuf [1] == USB_DT_CONFIG) { dev->hs_config = (void *) kbuf; total = le16_to_cpup (&dev->hs_config->wTotalLength); if (!is_valid_config (dev->hs_config) || total >= length) goto fail; kbuf += total; length -= total; }   /* 设备描述子*/ if (length != USB_DT_DEVICE_SIZE) goto fail; dev->dev = (void *)kbuf; if (dev->dev->bLength != USB_DT_DEVICE_SIZE || dev->dev->bDescriptorType != USB_DT_DEVICE || dev->dev->bNumConfigurations != 1) goto fail; dev->dev->bNumConfigurations = 1; dev->dev->bcdUSB = __constant_cpu_to_le16 (0x0200);   /*触发adgetfs_bind(),接着能设备枚举 */ spin_unlock_irq (&dev->lock); /*注册Gadget驱动程序并触发adgetfs_bind(),在sysfs中创建设备属性文件,激活从设备控制器。*/ value = usb_gadget_register_driver (&gadgetfs_driver); if (value != 0) { kfree (dev->buf); dev->buf = NULL; } else { /* 在这点“好的”硬件首次被USB主机看中,如果用户插/拔,将清除所有的错误状态。*/ fd->f_op = &ep0_io_operations; value = len; } return value;   fail: spin_unlock_irq (&dev->lock); pr_debug ("%s: %s fail %Zd, %p\n", shortname, __FUNCTION__, value, dev); kfree (dev->buf); dev->buf = NULL; return value; }


驱动程序实例gadgetfs_driver

虚拟的Gadget设备驱动程序实例gadgetfs_driver列出如下:

static struct usb_gadget_driver gadgetfs_driver = { #ifdef HIGHSPEED .speed = USB_SPEED_HIGH, #else .speed = USB_SPEED_FULL, #endif .function = (char *) driver_desc, .bind = gadgetfs_bind, //将gadget绑定到此gadgetfs设备 .unbind = gadgetfs_unbind,   //通过ep0得到或设置各种描述子,事件处理 .setup = gadgetfs_setup,    //设置dev->state = STATE_UNCONNECTED .disconnect = gadgetfs_disconnect,   //挂起事件处理  .suspend = gadgetfs_suspend,    .driver = { .name = (char *) shortname, }, };


下面仅分析驱动程序实例中的函数bind。

函数gadgetfs_bind将gadget绑定到此gadgetfs设备,初始化设备对象,激活端点文件。其列出如下:

static int gadgetfs_bind (struct usb_gadget *gadget) { struct dev_data *dev = the_device; /*获取设备对象*/   if (!dev) return -ESRCH; //判定是否期望的控制器 if (0 != strcmp (CHIP, gadget->name)) { printk (KERN_ERR "%s expected %s controller not %s\n", shortname, CHIP, gadget->name); return -ENODEV; }   /*初始化dev*/   //设置gadget->dev->driver_data = dev set_gadget_data (gadget, dev); dev->gadget = gadget; gadget->ep0->driver_data = dev; dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket;     /*初始化请求*/ // 预分配控制反应的请求和请求buffer dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); if (!dev->req) goto enomem; dev->req->context = NULL; dev->req->complete = epio_complete;   if (activate_ep_files (dev) < 0) //激活端点文件 goto enomem;   INFO (dev, "bound to %s driver\n", gadget->name); dev->state = STATE_UNCONNECTED; get_dev (dev); //设备引用计数加1 return 0;   enomem: gadgetfs_unbind (gadget); return -ENOMEM; }


函数activate_ep_files给每个端点创建请求,并创建文件,赋上文件操作函数集实例,通过端点文件,在没有配置端点前,可以通过写函数来配置端点,配置好端点后,可以通过读写函数来发送和接收数据。

函数activate_ep_files分析如下:

static int activate_ep_files (struct dev_data *dev) { struct usb_ep *ep;      //遍历链表中所有的端点 gadget_for_each_ep (ep, dev->gadget) { struct ep_data *data;       //分配结构ep_data对象空间,接着初始化它 data = kmalloc (sizeof *data, GFP_KERNEL); if (!data) goto enomem; memset (data, 0, sizeof data); data->state = STATE_EP_DISABLED; init_MUTEX (&data->lock); init_waitqueue_head (&data->wait);   strncpy (data->name, ep->name, sizeof (data->name) - 1); atomic_set (&data->count, 1); data->dev = dev; get_dev (dev); //设备引用计数加1   data->ep = ep; ep->driver_data = data;     //分配请求及请求的buffer data->req = usb_ep_alloc_request (ep, GFP_KERNEL); if (!data->req) goto enomem;     //创建并初始化dentry和inode,赋上文件操作函数集 data->inode = gadgetfs_create_file (dev->sb, data->name, data, &ep_config_operations, &data->dentry); if (!data->inode) { kfree (data); goto enomem; } list_add_tail (&data->epfiles, &dev->epfiles); } return 0;   enomem: DBG (dev, "%s enomem\n", __FUNCTION__); destroy_ep_files (dev); return -ENOMEM; }


文件操作函数集结构实例

在文件系统中,端点以文件的形式存在,用户空间应用程序与端点之间传输数据变成了对端点文件的读写操作。这些文件的读写操作在文件系统的底层I/O接口函数中转换为调用控制器的读写寄存器操作,从而实现数据的传输或控制。

在设备配置后ep0使用的文件操作函数集ep0_io_operations列出如下:

static struct file_operations ep0_io_operations = { .owner = THIS_MODULE, .llseek = no_llseek,     //从ep0端点中输出数据到请求的buf中,再拷贝到用户空间 .read = ep0_read,  .write = ep0_write, //将用户空间数据写入到ep0端点 .fasync = ep0_fasync, // .poll = ep0_poll, .ioctl = dev_ioctl, //调用gadget->ops->ioctl .release = dev_release, //释放Gadget驱动程序 };


在端点配置前,端点配置文件使用文件操作函数集实例ep_config_operations对端点进行配置,其列出如下:

static struct file_operations ep_config_operations = { .owner = THIS_MODULE, .llseek = no_llseek,   .open = ep_open, .write = ep_config, //配置并初始化端点 .release = ep_release, };


在端点配置后,端点文件实现对端点的控制和传输数据操作,端点文件的文件操作函数集实例ep_io_operations列出如下:

static struct file_operations ep_io_operations = { .owner = THIS_MODULE, .llseek = no_llseek,   .read = ep_read, //从端点读出数据拷贝到用户空间 .write = ep_write, //从用户空间拷贝数据写入端点 .ioctl = ep_ioctl, //端点的各种控制 .release = ep_release, //释放ep_data结构   .aio_read = ep_aio_read, .aio_write = ep_aio_write, };


下面仅分析配置文件的文件操作函数集实例中的写函数(配置函数)和端点文件的写操作函数。

配置文件的文件操作函数集实例中的写函数ep_config用来配置初始化端点,在用户空间,端点初始化的方法如下:

fd = open ("/dev/gadget/$ENDPOINT", O_RDWR) status = write (fd, descriptors, sizeof descriptors)


写操作通过函数ep_config建立端点配置,使控制器端点有合适的最大包尺寸等配置,以便处理批量、中断或同步传输。描述子是消息类型1,内容是主机u32字节序。它包括2个描述子,描述子的次序是:全速/低速描述子,接着是可选的高速描述子。

函数ep_config列出如下:

static ssize_t ep_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) { struct ep_data *data = fd->private_data; struct usb_ep *ep; u32 tag; int value;    /*可中断的down操作,尝试获得一个信号量,若获得,返回0,如果被中断,返回-EINTR。*/ if ((value = down_interruptible (&data->lock)) < 0) return value;   if (data->state != STATE_EP_READY) { //端点没准备好 value = -EL2HLT; goto fail; }   value = len; if (len < USB_DT_ENDPOINT_SIZE + 4) //小于(端点描述子的大小+4) goto fail0;   /*判断tag标签是否有效*/ if (copy_from_user (&tag, buf, 4)) { //拷贝tag标签到内核空间 goto fail1; } if (tag != 1) { DBG(data->dev, "config %s, bad tag %d\n", data->name, tag); goto fail0; } buf += 4; len -= 4;   /* 全速/低速描述子,接着是高速描述子*/ //从用户空间拷贝全速/低速描述子到内核空间 if (copy_from_user (&data->desc, buf, USB_DT_ENDPOINT_SIZE)) { goto fail1; }   //判断描述子是否有效 if (data->desc.bLength != USB_DT_ENDPOINT_SIZE || data->desc.bDescriptorType != USB_DT_ENDPOINT) goto fail0; if (len != USB_DT_ENDPOINT_SIZE) { if (len != 2 * USB_DT_ENDPOINT_SIZE) goto fail0;     //从用户空间拷贝高速描述子到内核空间 if (copy_from_user (&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE, USB_DT_ENDPOINT_SIZE)) { goto fail1; }     //判断描述子是否有效 if (data->hs_desc.bLength != USB_DT_ENDPOINT_SIZE || data->hs_desc.bDescriptorType != USB_DT_ENDPOINT) { DBG(data->dev, "config %s, bad hs length or type\n", data->name); goto fail0; } } value = len;   spin_lock_irq (&data->dev->lock); if (data->dev->state == STATE_DEV_UNBOUND) {//设备还绑定 value = -ENOENT; goto gone; } else if ((ep = data->ep) == NULL) { //端点为空 value = -ENODEV; goto gone; } switch (data->dev->gadget->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: value = usb_ep_enable (ep, &data->desc); //配置激活端点 if (value == 0) data->state = STATE_EP_ENABLED; break; #ifdef HIGHSPEED case USB_SPEED_HIGH: /* fails if caller didn't provide that descriptor... */ value = usb_ep_enable (ep, &data->hs_desc); //配置激活端点 if (value == 0) data->state = STATE_EP_ENABLED; break; #endif default: DBG (data->dev, "unconnected, %s init deferred\n", data->name); data->state = STATE_EP_DEFER_ENABLE; } if (value == 0)     //配置好端点好,赋上配置后的文件操作函数集。 fd->f_op = &ep_io_operations;  …… }


在端点配置后,端点文件的文件操作函数集用来读写数据,这里只分析了写操作函数ep_write,读操作函数ep_read有相似机制,只是数据方向相反。

函数ep_write处理一个同步的IN bulk/intr/iso传输,函数列出如下:

static ssize_t ep_write (struct file *fd, const char __user *buf,                  size_t len, loff_t *ptr) { struct ep_data *data = fd->private_data; void *kbuf; ssize_t value;   //端点是否准备好 if ((value = get_ready_ep (fd->f_flags, data)) < 0) return value;   //停止任何做“错误方向”I/O的端点   if (!(data->desc.bEndpointAddress & USB_DIR_IN)) { if ((data->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC) return -EINVAL;   spin_lock_irq (&data->dev->lock); if (likely (data->ep != NULL)) usb_ep_set_halt (data->ep); //停止端点 spin_unlock_irq (&data->dev->lock); up (&data->lock); return -EBADMSG; }   //分配内核buffer,从用户空间拷贝数据到buffer中 value = -ENOMEM; kbuf = kmalloc (len, SLAB_KERNEL); if (!kbuf) goto free1; if (copy_from_user (kbuf, buf, len)) { value = -EFAULT; goto free1; }   value = ep_io (data, kbuf, len);   free1: up (&data->lock); kfree (kbuf); return value; }


函数ep_io通过调用Gadget API(即具体设备控制器的端点操作函数)将读写请求在端点排队,在端点上进行数据的发送与接收处理。

函数ep_io分析如下:

static ssize_t ep_io (struct ep_data *epdata, void *buf, unsigned len) { DECLARE_COMPLETION (done); int value;   spin_lock_irq (&epdata->dev->lock); if (likely (epdata->ep != NULL)) { struct usb_request *req = epdata->req; //初始化请求 req->context = &done; req->complete = epio_complete; req->buf = buf; req->length = len; value = usb_ep_queue (epdata->ep, req, GFP_ATOMIC); //请求排队 } else value = -ENODEV; spin_unlock_irq (&epdata->dev->lock);   if (likely (value == 0)) { value = wait_event_interruptible (done.wait, done.done); if (value != 0) { spin_lock_irq (&epdata->dev->lock); if (likely (epdata->ep != NULL)) { DBG (epdata->dev, "%s i/o interrupted\n", epdata->name); usb_ep_dequeue (epdata->ep, epdata->req); spin_unlock_irq (&epdata->dev->lock);   wait_event (done.wait, done.done); if (epdata->status == -ECONNRESET) epdata->status = -EINTR; } else { spin_unlock_irq (&epdata->dev->lock);   DBG (epdata->dev, "endpoint gone\n"); epdata->status = -ENODEV; } } return epdata->status; } return value; }


Gadget Mass Storage设备驱动程序
驱动程序原理介绍

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=0xRRRRUSB发布号(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子目录下的"ro"和"file"文件属性里。如果"removable"选项被设置,对这些文件的写将模拟弹出/装上媒体(写一个空行意味着弹出)操作。当媒体被装载时,"ro"的设置不允许改变。

本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_dev代表了一个大存储设备,字符"fsg"是File Storage的缩写。它继承了USB的基类结构usb_gadget、usb_ep、usb_request,包括了传输请求、传输缓冲区、SCSI命令数据块和线程任务等。结构fsg_dev列出如下:

struct fsg_dev {   spinlock_t lock; struct usb_gadget *gadget;   //大存储设备文件使用的文件信号量保护  struct rw_semaphore filesem;   struct usb_ep *ep0; // gadget->ep0的拷贝 struct usb_request *ep0req; // 控制请求 volatile unsigned int ep0_req_tag; //控制请求的标签,标识请求的过期 const char *ep0req_name; //控制请求名   struct usb_request *intreq; //中断请求 volatile int intreq_busy; //中断请求忙 struct fsg_buffhd *intr_buffhd; //中断请求的缓冲区头   unsigned int bulk_out_maxpacket; // bulk_out包的最大尺寸 enum fsg_state state; //为异常处理设置的状态 unsigned int exception_req_tag; //异常请求标签,标识请求的过期   u8 config, new_config;   unsigned int running : 1; unsigned int bulk_in_enabled : 1; unsigned int bulk_out_enabled : 1; unsigned int intr_in_enabled : 1; unsigned int phase_error : 1; unsigned int short_packet_received : 1; unsigned int bad_lun_okay : 1;   unsigned long atomic_bitflags; #define REGISTERED 0 #define CLEAR_BULK_HALTS 1 #define SUSPENDED 2     //使用的端点 struct usb_ep *bulk_in; struct usb_ep *bulk_out; struct usb_ep *intr_in;   struct fsg_buffhd *next_buffhd_to_fill; //下一个填充的buffer struct fsg_buffhd *next_buffhd_to_drain; //下一个清空的buffer struct fsg_buffhd buffhds[NUM_BUFFERS]; //使用2个buffer   wait_queue_head_t thread_wqh; int thread_wakeup_needed;  //需要唤醒线程 struct completion thread_notifier; //线程通知器 int thread_pid; struct task_struct *thread_task; //线程任务 sigset_t thread_signal_mask; //线程信号掩码   int cmnd_size; u8 cmnd[MAX_COMMAND_SIZE]; //SCSI命令数据块,长度为16 enum data_direction data_dir; //数据方向 u32 data_size; u32 data_size_from_cmnd; u32 tag; unsigned int lun; u32 residue; u32 usb_amount_left;   /* CB协议没有提供方法给主机来知道何时一个命令已完成。这样下个命令可能较早到达并且我们还得处理它。由于这个原因,当使用CB(或CBI,它也不会迫使主机等待命令完成)时,我们需要一个buffer来存储新的命令。*/ int cbbuf_cmnd_size; u8 cbbuf_cmnd[MAX_COMMAND_SIZE];// 长度为16的命令缓冲区   unsigned int nluns; struct lun *luns; struct lun *curlun; };


结构fsg_buffhd是缓冲区头结构,它管理着缓冲区的状态及使用缓冲区的USB请求。该结构列出如下:

struct fsg_buffhd { void *buf; //缓冲区指针 dma_addr_t dma;  //DMA地址 volatile enum fsg_buffer_state state; //状态为满、空、忙。 struct fsg_buffhd *next;   /*如果我们不提交任何短的bulk-out读请求,那么,NetChip 2280控制器较快,并且较好地处理一些协议错误。因此,我们在这里将记录期望的请求长度。*/ unsigned int bulk_out_intended_length;   struct usb_request *inreq; //输入请求 volatile int inreq_busy; struct usb_request *outreq; //输出请求 volatile int outreq_busy; };


结构lun代表了一个逻辑单元,结构lun列出如下:

struct lun { struct file *filp;     loff_t file_length; //文件长度 loff_t num_sectors; //扇区数   unsigned int ro : 1; unsigned int prevent_medium_removal : 1; unsigned int registered : 1;   u32 sense_data; u32 sense_data_info; u32 unit_attention_data;   struct device dev; };


驱动程序初始化

函数fsg_init注册驱动程序结构实例fsg_driver,并且激活了设备控制器。函数分析如下:

static struct fsg_dev *the_fsg; static struct usb_gadget_driver fsg_driver; static int __init fsg_init(void) { int rc; struct fsg_dev *fsg;   if ((rc = fsg_alloc()) != 0) return rc; fsg = the_fsg; //注册驱动程序并激活设备 if ((rc = usb_gadget_register_driver(&fsg_driver)) != 0) kref_put(&fsg->ref, fsg_release); return rc; }


驱动程序结构实例fsg_driver列出如下:

static struct usb_gadget_driver fsg_driver = { #ifdef CONFIG_USB_GADGET_DUALSPEED .speed = USB_SPEED_HIGH, #else .speed = USB_SPEED_FULL, #endif .function = (char *) longname, .bind = fsg_bind, //Gadget驱动程序绑定操作,在注册过程中调用。 .unbind = fsg_unbind, .disconnect = fsg_disconnect, //断开处理函数 .setup = fsg_setup,  //处理ep0请求,在ep0端点排队处理请求 .suspend = fsg_suspend, //设置SUSPENDED标识,由线程来处理 .resume = fsg_resume,  //清除SUSPENDED标识   .driver = { .name = (char *) shortname,   }, };


驱动程序绑定函数fsg_bind分析

函数fsg_bind注册LUN,分配与端点配置相适应的端点,分配数据buffer,创建Gadget设备控制用的主线程fsg_main_thread。

函数fsg_bind分析如下:

static int __init fsg_bind(struct usb_gadget *gadget) { struct fsg_dev *fsg = the_fsg; int rc; int i; struct lun *curlun; struct usb_ep *ep; struct usb_request *req; char *pathbuf, *p;   fsg->gadget = gadget; //设置gadget->dev->driver_data = fsg set_gadget_data(gadget, fsg); fsg->ep0 = gadget->ep0; fsg->ep0->driver_data = fsg;     //存储检查模块选项到结构实例mod_data中 if ((rc = check_parameters(fsg)) != 0) goto out;   //如果是可移去的设备 if (mod_data.removable) { // Enable the store_xxx attributes dev_attr_ro.attr.mode = dev_attr_file.attr.mode = 0644; dev_attr_ro.store = store_ro; //赋上属性存储函数 dev_attr_file.store = store_file; }   //查找有多少个LUN i = mod_data.nluns; if (i == 0) i = max(mod_data.num_filenames, 1); if (i > MAX_LUNS) { ERROR(fsg, "invalid number of LUNs: %d\n", i); rc = -EINVAL; goto out; }   //给逻辑单元LUN分配lun结构对象空间 fsg->luns = kmalloc(i * sizeof(struct lun), GFP_KERNEL); if (!fsg->luns) { rc = -ENOMEM; goto out; } memset(fsg->luns, 0, i * sizeof(struct lun)); //空间清0 fsg->nluns = i;     //初始化、注册各个LUN,并打开LUN设备文件 for (i = 0; i < fsg->nluns; ++i) { curlun = &fsg->luns[i]; curlun->ro = ro[i]; curlun->dev.parent = &gadget->dev; curlun->dev.driver = &fsg_driver.driver; dev_set_drvdata(&curlun->dev, fsg); snprintf(curlun->dev.bus_id, BUS_ID_SIZE, "%s-lun%d", gadget->dev.bus_id, i);      //注册LUN设备到sysfs文件系统中 if ((rc = device_register(&curlun->dev)) != 0) INFO(fsg, "failed to register LUN%d: %d\n", i, rc); else {//注册成功,在sysfs文件系统中创建属性文件 curlun->registered = 1; curlun->dev.release = lun_release; device_create_file(&curlun->dev, &dev_attr_ro); device_create_file(&curlun->dev, &dev_attr_file); }       //打开各个LUN设备文件,初始化lun结构中与设备相关的扇区数及长度等 if (file[i] && *file[i]) { if ((rc = open_backing_file(curlun, file[i])) != 0) goto out; } else if (!mod_data.removable) { ERROR(fsg, "no file given for LUN%d\n", i); rc = -EINVAL; goto out; } }   /*查找所有我们将使用的端点 */   //各个端点复位,即: ep->driver_data = NULL usb_ep_autoconfig_reset(gadget);   //选择匹配端点描述子fs_bulk_in_desc的端点 ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint fsg->bulk_in = ep;     //选择匹配端点描述子fs_bulk_out_desc的端点 ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint fsg->bulk_out = ep;   if (transport_is_cbi()) { //是CBI传输 //选择匹配端点描述子fs_intr_in_desc的端点 ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint fsg->intr_in = ep; }   /* 用模块选项设置设备描述子 */ device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket; device_desc.idVendor = cpu_to_le16(mod_data.vendor); device_desc.idProduct = cpu_to_le16(mod_data.product); device_desc.bcdDevice = cpu_to_le16(mod_data.release);     //用模块选项设置接口描述子 i = (transport_is_cbi() ? 3 : 2); // Number of endpoints intf_desc.bNumEndpoints = i; intf_desc.bInterfaceSubClass = mod_data.protocol_type; intf_desc.bInterfaceProtocol = mod_data.transport_type; fs_function[i + FS_FUNCTION_PRE_EP_ENTRIES] = NULL;   #ifdef CONFIG_USB_GADGET_DUALSPEED //支持双速 hs_function[i + HS_FUNCTION_PRE_EP_ENTRIES] = NULL;   //假定ep0在双速下使用同样的maxpacket值 dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket;   //假定所有的端点在双速下使用同样的maxpacket值 hs_bulk_in_desc.bEndpointAddress = fs_bulk_in_desc.bEndpointAddress; hs_bulk_out_desc.bEndpointAddress = fs_bulk_out_desc.bEndpointAddress; hs_intr_in_desc.bEndpointAddress = fs_intr_in_desc.bEndpointAddress; #endif   if (gadget->is_otg) { //支持OTG协议 otg_desc.bmAttributes |= USB_OTG_HNP, config_desc.bmAttributes |= USB_CONFIG_ATT_WAKEUP; }   rc = -ENOMEM;   /*调用设备控制器端点的分配函数给ep0分配请求和buffer */ fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL); if (!req) goto out; req->buf = usb_ep_alloc_buffer(fsg->ep0, EP0_BUFSIZE, &req->dma, GFP_KERNEL); if (!req->buf) goto out;   //赋上回调函数,函数打印信息并在请求取消时刷新FIFO中数据。 req->complete = ep0_complete;    /*调用设备控制器端点的分配函数分配数据buffer */ for (i = 0; i < NUM_BUFFERS; ++i) { // NUM_BUFFERS为2 struct fsg_buffhd *bh = &fsg->buffhds[i];   bh->buf = usb_ep_alloc_buffer(fsg->bulk_in, mod_data.buflen, &bh->dma, GFP_KERNEL); if (!bh->buf) goto out; bh->next = bh + 1; }   //2个buffer组成环形缓冲区 fsg->buffhds[NUM_BUFFERS - 1].next = &fsg->buffhds[0];   /* This should reflect the actual gadget power source */ //将SELF_POWERED_STATUS标识写设备控制器控制寄存器中 usb_gadget_set_selfpowered(gadget);     //写系统信息到manufacturer中 snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", system_utsname.sysname, system_utsname.release, gadget->name);   /* 在一个真实设备上,serial[]将从永久存储中装载,我们仅从驱动程序版本字符串对它编码*/ for (i = 0; i < sizeof(serial) - 2; i += 2) { unsigned char c = DRIVER_VERSION[i / 2];   if (!c) break; sprintf(&serial[i], "%02X", c); }     //创建线程fsg_main_thread,它完成Gadget的大部分操作 if ((rc = kernel_thread(fsg_main_thread, fsg, (CLONE_VM | CLONE_FS | CLONE_FILES))) < 0) goto out; fsg->thread_pid = rc; //线程的pid …… return 0; …… }


Gadget工作线程

线程函数fsg_main_thread是Gadget设备操作的主处理函数,它处理了设备的各种操作及事件。线程函数fsg_main_thread调用层次图如图2所示。


Linux kernel usb gadget driver 02.gif
图2 线程函数fsg_main_thread调用层次图

函数fsg_main_thread列出如下(在drivers/usb/gadget/file_storage.c中):

static int fsg_main_thread(void *fsg_) { struct fsg_dev *fsg = fsg_;   /*允许接收信号INT, TERM, KILL和USR1,忽略其他信号*/ allow_signal(SIGINT); allow_signal(SIGTERM); allow_signal(SIGKILL); allow_signal(SIGUSR1);   /* 允许线程冻僵*/ set_freezable();   /* 用户空间引用解释作内核指针,这样,就能传递内核指针到一个期望有正常工作__user指针的例程*/ set_fs(get_ds());   /*主要的循环 */ while (fsg->state != FSG_STATE_TERMINATED) {//非结束状态     //FSG设备异常或进程挂起 if (exception_in_progress(fsg) || signal_pending(current)) { handle_exception(fsg); //进行异常处理 continue; }   if (!fsg->running) { //FSG设备没运行 sleep_thread(fsg);//等待直到fsg->thread_wakeup_needed时来唤醒 continue; }   if (get_next_command(fsg)) //得到下一个SCSI命令放在缓存区中 continue;   spin_lock_irq(&fsg->lock); if (!exception_in_progress(fsg)) //异常处理 fsg->state = FSG_STATE_DATA_PHASE; spin_unlock_irq(&fsg->lock);      /*执行SCSI命令相应的操作,操作完成后,调用函数finish_reply进行发送短包、停止端点、出错等处理。*/ if (do_scsi_command(fsg) || finish_reply(fsg)) continue;   spin_lock_irq(&fsg->lock); if (!exception_in_progress(fsg)) //异常处理 fsg->state = FSG_STATE_STATUS_PHASE; spin_unlock_irq(&fsg->lock);   if (send_status(fsg)) //发送CSW continue;   spin_lock_irq(&fsg->lock); if (!exception_in_progress(fsg)) //异常处理 fsg->state = FSG_STATE_IDLE; spin_unlock_irq(&fsg->lock); }   fsg->thread_task = NULL; flush_signals(current); //当前信号刷新   // 因为一个信号退出时,注销Gadget驱动程序并且关闭设备文件  if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) { usb_gadget_unregister_driver(&fsg_driver); close_all_backing_files(fsg); } }

函数get_next_command从端点中得到CBW,并将CBW拷贝到fsg->cmnd中留待后面分析。函数get_next_command列出如下(在drivers/usb/gadget/file_storage.c中):

static int get_next_command(struct fsg_dev *fsg) { struct fsg_buffhd *bh; int rc = 0;   if (transport_is_bbb()) {//Bulk-Only传输时   /*等待下一个buffer变成可用的*/ bh = fsg->next_buffhd_to_fill; while (bh->state != BUF_STATE_EMPTY) {//buffer不为空时 if ((rc = sleep_thread(fsg)) != 0) //睡眠直到条件满足时唤醒 return rc; }   /* 排队请求来读一个Bulk-only CBW */     //使bulk-out请求能被maxpacket大小分割 set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN);     //使用函数usb_ep_queue在端点上进行请求排队, //设备控制器端点将数据从FIFO中拷贝到请求req的buffer中。 start_transfer(fsg, fsg->bulk_out, bh->outreq, &bh->outreq_busy, &bh->state);   //我们将在软件里将buffer清除,这样它可在下一次填充中再使用。   //等待CBW到来 while (bh->state != BUF_STATE_FULL) { //buffer没装满就睡眠等待 if ((rc = sleep_thread(fsg)) != 0) return rc; }     //检查CBW是否有效,并将CBW拷贝到fsg->cmnd中,     //根据CBW设置传输方向fsg->data_dir、fsg->lun和fsg->tag rc = received_cbw(fsg, bh); bh->state = BUF_STATE_EMPTY; //设置buffer状态为空   } else { // USB_PR_CB或USB_PR_CBI传输时   /* 等待下一个命令的到来 */ while (fsg->cbbuf_cmnd_size == 0) { if ((rc = sleep_thread(fsg)) != 0) return rc; }   /* 前面的中断请求状态是否还是忙?主机被许可跳过读这个状态,因此我们必须取消它。*/ if (fsg->intreq_busy) usb_ep_dequeue(fsg->intr_in, fsg->intreq);   /* 拷贝命令并标识buffer为空*/ fsg->data_dir = DATA_DIR_UNKNOWN; spin_lock_irq(&fsg->lock); fsg->cmnd_size = fsg->cbbuf_cmnd_size; memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size); fsg->cbbuf_cmnd_size = 0; spin_unlock_irq(&fsg->lock); } return rc; }

函数do_scsi_command分析SCSI命令,并进行相应的操作。函数列出如下:

static int do_scsi_command(struct fsg_dev *fsg) { struct fsg_buffhd *bh; int rc; int reply = -EINVAL; int i; static char unknown[16];   dump_cdb(fsg);   /* 等待下一个buffer的数据或状态变成可用的*/ bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill; while (bh->state != BUF_STATE_EMPTY) { //buffer不为空 if ((rc = sleep_thread(fsg)) != 0) //线程睡眠出错,返回 return rc; } fsg->phase_error = 0; fsg->short_packet_received = 0;   down_read(&fsg->filesem); // We're using the backing file switch (fsg->cmnd[0]) {//根据不同的SCSI命令进行不同的操作 …… case SC_READ_10:     //从buffer中数据移位得到命令 fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9;     //检查SCSI命令和LUN的有效性 if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (1<<1) | (0xf<<2) | (3<<7), 1, "READ(10)")) == 0) /*调用文件系统vfs_read函数将设备文件中数据读出放入buffer中,然后,通过bulk-in端点请求排队将数据传给主机。*/ reply = do_read(fsg);  break; …… case SC_SYNCHRONIZE_CACHE: fsg->data_size_from_cmnd = 0; if ((reply = check_command(fsg, 10, DATA_DIR_NONE, (0xf<<2) | (3<<7), 1, "SYNCHRONIZE CACHE")) == 0)       //调用函数fsync_sub将数据从cache中同步到块设备中 reply = do_synchronize_cache(fsg);  break;   ……   case SC_WRITE_10: fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, (1<<1) | (0xf<<2) | (3<<7), 1, "WRITE(10)")) == 0) reply = do_write(fsg); break;   …… } up_read(&fsg->filesem); …… return 0; }

  函数do_read与函数do_write有相似的机制,这里只分析函数do_write。

函数do_write通过对2个缓冲区组成的环形缓存进行操作,实现了一方面bulk-out端点向其中一个缓冲区写数据,另一方面,函数vfs_write将另一个缓冲区的数据输出写入到LUN设备文件中。这是操作系统中的典型的读者-写者问题。

函数do_write列出如下(在drivers/usb/gadget/file_storage.c中):

static int do_write(struct fsg_dev *fsg) { struct lun *curlun = fsg->curlun; u32 lba; struct fsg_buffhd *bh; int get_some_more; u32 amount_left_to_req, amount_left_to_write; loff_t usb_offset, file_offset, file_offset_tmp; unsigned int amount; unsigned int partial_page; ssize_t nwritten; int rc;   if (curlun->ro) { curlun->sense_data = SS_WRITE_PROTECTED; return -EINVAL; } curlun->filp->f_flags &= ~O_SYNC; // Default is not to wait   /*得到开始的逻辑块地址并检查它不是太大*/ if (fsg->cmnd[0] == SC_WRITE_6) lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); else { lba = get_be32(&fsg->cmnd[2]);   /* 允许DPO (Disable Page Out = 不存数据在cache中) 和 FUA (Force Unit Access = 直接对媒体写)。我们不执行DPO,我们通过执行同步输出来使用FUA */ if ((fsg->cmnd[1] & ~0x18) != 0) {  curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } if (fsg->cmnd[1] & 0x08) // FUA curlun->filp->f_flags |= O_SYNC; //同步写入 } if (lba >= curlun->num_sectors) { //如果逻辑块地址超过LUN的扇区数 curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return -EINVAL; }   /*执行文件写*/ get_some_more = 1; file_offset = usb_offset = ((loff_t) lba) << 9; //转换成字节位置   //命令中要求写入的数据大小 amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd;   while (amount_left_to_write > 0) {   /* 为了从主机中得到更多数据而排队请求*/ bh = fsg->next_buffhd_to_fill; //如果buffer是空的,通过端点请求排队得到数据 if (bh->state == BUF_STATE_EMPTY && get_some_more) {   /* 指出我们想得到多少数据: 尝试得到剩下的数据量,但不能超过buffer的大小。不能超越设备文件的结尾。如果不在一页的边界,不能超越下一页。如果得到0,那么我们被请求写过去的数据到文件的结尾。最后,停在一个块的边界上。*/         //需要写的数据量不能超过buffer的大小 amount = min(amount_left_to_req, mod_data.buflen);       //需要写的数据量不能超过设备文件的大小 amount = min((loff_t) amount, curlun->file_length - usb_offset); // usb_offset是文件写的起始位置       //文件写的起始位置是否页对齐 partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); if (partial_page > 0) amount = min(amount,            (unsigned int) PAGE_CACHE_SIZE - partial_page);   if (amount == 0) {//传输的数据量为0时 get_some_more = 0; curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; curlun->sense_data_info = usb_offset >> 9; continue; }       //检查是否小于1个扇区 amount -= (amount & 511); if (amount == 0) {//小于1个扇区 // 被请求传输一个部分块是不合理的 get_some_more = 0; continue; }   /*得到下一个buffer */ usb_offset += amount; fsg->usb_amount_left -= amount; amount_left_to_req -= amount; if (amount_left_to_req == 0) get_some_more = 0;   //amount总是可被512整除的, 也是bulk-out最大包尺寸的整数倍 bh->outreq->length = bh->bulk_out_intended_length = amount; //开始在端点排队请求,并传输数据到请求的buffer中 start_transfer(fsg, fsg->bulk_out, bh->outreq, &bh->outreq_busy, &bh->state); fsg->next_buffhd_to_fill = bh->next; continue; }   /* 写接收到的数据到设备文件中 */ bh = fsg->next_buffhd_to_drain;     //如果buffer是空的且不想得到数据时跳出循环。 if (bh->state == BUF_STATE_EMPTY && !get_some_more) break; // We stopped early if (bh->state == BUF_STATE_FULL) { //buffer是满的 fsg->next_buffhd_to_drain = bh->next; bh->state = BUF_STATE_EMPTY;   //传输出现错误时跳出循环 if (bh->outreq->status != 0) { curlun->sense_data = SS_COMMUNICATION_FAILURE; curlun->sense_data_info = file_offset >> 9; break; }   amount = bh->outreq->actual;       //超出设备文件末尾 if (curlun->file_length - file_offset < amount) { //amount为到设备文件末尾时的数据量 amount = curlun->file_length - file_offset; }   /* 执行写操作*/ file_offset_tmp = file_offset; nwritten = vfs_write(curlun->filp, (char __user *) bh->buf, amount, &file_offset_tmp);   if (signal_pending(current)) //如果信号挂起,中断返回错误 return -EINTR; // Interrupted!   if (nwritten < 0) {//文件写出现错误 nwritten = 0; } else if (nwritten < amount) { nwritten -= (nwritten & 511); // Round down to a block } file_offset += nwritten; amount_left_to_write -= nwritten; fsg->residue -= nwritten;   /* 如果一个错误发生,报告它及它的位置*/ if (nwritten < amount) { curlun->sense_data = SS_WRITE_ERROR; curlun->sense_data_info = file_offset >> 9; break; }   /* 主机是否决定提前停止? */ if (bh->outreq->actual != bh->outreq->length) { fsg->short_packet_received = 1; break; } continue; }   /* 睡眠等待 */ if ((rc = sleep_thread(fsg)) != 0) return rc; }   return -EIO; // No default reply }


文章转载至:
阅读(1558) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~