Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1944292
  • 博文数量: 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)

分类: 服务器与存储

2015-05-21 15:21:02

驱动程序原理介绍

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子目录下的"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 }
Linux%E5%86%85%E6%A0%B8USB%E4%BB%8E%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F
阅读(2233) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~