storage R&D guy.
全部博文(1000)
分类: 服务器与存储
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
本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列出如下:
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注册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; …… }
线程函数fsg_main_thread是Gadget设备操作的主处理函数,它处理了设备的各种操作及事件。线程函数fsg_main_thread调用层次图如图2所示。
函数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