接上一篇: usb-skeleton.c到 usb core层的分析
对于存储设备的USB,Linux源码中有关于USB MASS Storage 的驱动程序(/drivers/usb/storage下),其中/drivers/usb/storage/usb.c 实现了驱动初始化,和usb-skeleton.c 例子以一样,调用 retval = usb_register(&usb_storage_driver); 注册usb设备,
static struct usb_driver usb_storage_driver = {
.name = "usb-storage",
.probe = storage_probe,
.disconnect = storage_disconnect,
#ifdef CONFIG_PM
.suspend = storage_suspend,
.resume = storage_resume,
.reset_resume = storage_reset_resume,
#endif
.pre_reset = storage_pre_reset,
.post_reset = storage_post_reset,
.id_table = storage_usb_ids,
.soft_unbind = 1,
};
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
.....................
/*
* Ask the SCSI layer to allocate a host structure, with extra
* space at the end for our private us_data structure.
*/
host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us));
if (!host) {
printk(KERN_WARNING USB_STORAGE
"Unable to allocate the scsi host\n");
return -ENOMEM;
}
result = get_device_info(us, id);
if (result)
goto BadDevice;
result = get_transport(us); //获得usb设备端的的数据,并进行相应设置,见下面
if (result)
goto BadDevice;
result = get_protocol(us); //获得usb设备端协议的数据,并进行相应设置,见下面
if (result)
goto BadDevice;
result = get_pipes(us); //获得endpoint管道的数据,并进行相应设置,见下面
if (result)
goto BadDevice;
.........................
result = usb_stor_acquire_resources(us); //获得资源,开启了一个控制内核的线程static int usb_stor_control_thread(void * __us),用于接收上层命令,该线程会在有命令的时候(休眠等待),调用struct us_data(在storage中定义的数据)中的proto_handler方法,从而调用transport,向usbcore发送命令。非常重要,见下面代码
if (result)
goto BadDevice;
result = scsi_add_host(host, &intf->dev); 增加到SCSI host
if (result) {
printk(KERN_WARNING USB_STORAGE
"Unable to add the scsi host\n");
goto BadDevice;
}
/* Start up the thread for delayed SCSI-device scanning */
th = kthread_create(usb_stor_scan_thread, us, "usb-stor-scan");
}
以下是进行usb设备端信息获取,同时设置us参数的函数get_transport()、get_protocal()、getPipes(),对应着上图中的Protocal Layer 和 Transfer Layer,分别在/drivers/usb/storage/protocal.c、transport.c中实现。初始化完成了交给SCSI层的接口后就由上层处理。
static int get_transport(struct us_data *us)
{
switch (us->protocol) {
case US_PR_CB:
us->transport_name = "Control/Bulk";
us->transport = usb_stor_CB_transport;
us->transport_reset = usb_stor_CB_reset; //在transport.c中定义 usb_stor_CB_reset
us->max_lun = 7;
break;
case US_PR_CBI:
us->transport_name = "Control/Bulk/Interrupt";
us->transport = usb_stor_CBI_transport;
us->transport_reset = usb_stor_CB_reset; //同上
us->max_lun = 7;
break;
case US_PR_BULK:
us->transport_name = "Bulk";
us->transport = usb_stor_Bulk_transport;
us->transport_reset = usb_stor_Bulk_reset;//同上
break;
#ifdef CONFIG_USB_STORAGE_USBAT
case US_PR_USBAT:
us->transport_name = "Shuttle USBAT";
us->transport = usbat_transport;
us->transport_reset = usb_stor_CB_reset;//同上
us->max_lun = 1;
break;
#endif
#ifdef CONFIG_USB_STORAGE_SDDR09
case US_PR_EUSB_SDDR09:
us->transport_name = "EUSB/SDDR09";
us->transport = sddr09_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 0;
break;
#endif
#ifdef CONFIG_USB_STORAGE_SDDR55
case US_PR_SDDR55:
us->transport_name = "SDDR55";
us->transport = sddr55_transport;
us->transport_reset = sddr55_reset;
us->max_lun = 0;
break;
#endif
#ifdef CONFIG_USB_STORAGE_DPCM
case US_PR_DPCM_USB:
us->transport_name = "Control/Bulk-EUSB/SDDR09";
us->transport = dpcm_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 1;
break;
#endif
#ifdef CONFIG_USB_STORAGE_FREECOM
case US_PR_FREECOM:
us->transport_name = "Freecom";
us->transport = freecom_transport;
us->transport_reset = usb_stor_freecom_reset;
us->max_lun = 0;
break;
#endif
#ifdef CONFIG_USB_STORAGE_DATAFAB
case US_PR_DATAFAB:
us->transport_name = "Datafab Bulk-Only";
us->transport = datafab_transport;
us->transport_reset = usb_stor_Bulk_reset;
us->max_lun = 1;
break;
#endif
#ifdef CONFIG_USB_STORAGE_JUMPSHOT
case US_PR_JUMPSHOT:
us->transport_name = "Lexar Jumpshot Control/Bulk";
us->transport = jumpshot_transport;
us->transport_reset = usb_stor_Bulk_reset;
us->max_lun = 1;
break;
#endif
#ifdef CONFIG_USB_STORAGE_ALAUDA
case US_PR_ALAUDA:
us->transport_name = "Alauda Control/Bulk";
us->transport = alauda_transport;
us->transport_reset = usb_stor_Bulk_reset;
us->max_lun = 1;
break;
#endif
#ifdef CONFIG_USB_STORAGE_KARMA
case US_PR_KARMA:
us->transport_name = "Rio Karma/Bulk";
us->transport = rio_karma_transport;
us->transport_reset = usb_stor_Bulk_reset;
break;
#endif
default:
return -EIO;
}
US_DEBUGP("Transport: %s\n", us->transport_name);
/* fix for single-lun devices */
if (us->fflags & US_FL_SINGLE_LUN)
us->max_lun = 0;
return 0;
}
static int get_protocol(struct us_data *us)
{
switch (us->subclass) {
case US_SC_RBC:
us->protocol_name = "Reduced Block Commands (RBC)";
us->proto_handler = usb_stor_transparent_scsi_command; //在protocal.c中定义
break;
case US_SC_8020:
us->protocol_name = "8020i";
us->proto_handler = usb_stor_ATAPI_command; //同上
us->max_lun = 0;
break;
case US_SC_QIC:
us->protocol_name = "QIC-157";
us->proto_handler = usb_stor_qic157_command; //同上
us->max_lun = 0;
break;
case US_SC_8070:
us->protocol_name = "8070i";
us->proto_handler = usb_stor_ATAPI_command;
us->max_lun = 0;
break;
case US_SC_SCSI:
us->protocol_name = "Transparent SCSI";
us->proto_handler = usb_stor_transparent_scsi_command;
break;
case US_SC_UFI:
us->protocol_name = "Uniform Floppy Interface (UFI)";
us->proto_handler = usb_stor_ufi_command;
break;
#ifdef CONFIG_USB_STORAGE_ISD200
case US_SC_ISD200:
us->protocol_name = "ISD200 ATA/ATAPI";
us->proto_handler = isd200_ata_command;
break;
#endif
#ifdef CONFIG_USB_STORAGE_CYPRESS_ATACB
case US_SC_CYP_ATACB:
us->protocol_name = "Transparent SCSI with Cypress ATACB";
us->proto_handler = cypress_atacb_passthrough;
break;
#endif
default:
return -EIO;
}
US_DEBUGP("Protocol: %s\n", us->protocol_name);
return 0;
}
/* Get the pipe settings */
static int get_pipes(struct us_data *us)
{
struct usb_host_interface *altsetting =
us->pusb_intf->cur_altsetting;
int i;
struct usb_endpoint_descriptor *ep;
struct usb_endpoint_descriptor *ep_in = NULL;
struct usb_endpoint_descriptor *ep_out = NULL;
struct usb_endpoint_descriptor *ep_int = NULL;
/*
* Find the first endpoint of each type we need.
* We are expecting a minimum of 2 endpoints - in and out (bulk).
* An optional interrupt-in is OK (necessary for CBI protocol).
* We will ignore any others.
*/
for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {
ep = &altsetting->endpoint[i].desc;
if (usb_endpoint_xfer_bulk(ep)) {
if (usb_endpoint_dir_in(ep)) {
if (!ep_in)
ep_in = ep;
} else {
if (!ep_out)
ep_out = ep;
}
}
else if (usb_endpoint_is_int_in(ep)) {
if (!ep_int)
ep_int = ep;
}
}
if (!ep_in || !ep_out || (us->protocol == US_PR_CBI && !ep_int)) {
US_DEBUGP("Endpoint sanity check failed! Rejecting dev.\n");
return -EIO;
}
/* Calculate and store the pipe values */
us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0);
us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0);
us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev,
ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev,
ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
if (ep_int) {
us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev,
ep_int->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
us->ep_bInterval = ep_int->bInterval;
}
return 0;
}
static int usb_stor_control_thread(void * __us) //drivers/usb/storage/usb.c中
{
struct us_data *us = (struct us_data *)__us;
struct Scsi_Host *host = us_to_host(us);
for(;;) {
US_DEBUGP("*** thread sleeping.\n");
if (wait_for_completion_interruptible(&us->cmnd_ready)) // 加入等待队列,cmnd_ready表示有命令到来,那什么时候通知呢,下面解释
break;
US_DEBUGP("*** thread awakened.\n");
/* lock the device pointers */
mutex_lock(&(us->dev_mutex));
/* lock access to the state */
scsi_lock(host);
/* When we are called with no command pending, we're done */
if (us->srb == NULL) {
scsi_unlock(host);
mutex_unlock(&us->dev_mutex);
US_DEBUGP("-- exiting\n");
break;
}
/* has the command timed out *already* ? */
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {
us->srb->result = DID_ABORT << 16;
goto SkipForAbort;
}
scsi_unlock(host);
/* reject the command if the direction indicator
* is UNKNOWN
*/
if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) {
US_DEBUGP("UNKNOWN data direction\n");
us->srb->result = DID_ERROR << 16;
}
/* reject if target != 0 or if LUN is higher than
* the maximum known LUN
*/
else if (us->srb->device->id &&
!(us->fflags & US_FL_SCM_MULT_TARG)) {
US_DEBUGP("Bad target number (%d:%d)\n",
us->srb->device->id, us->srb->device->lun);
us->srb->result = DID_BAD_TARGET << 16;
}
else if (us->srb->device->lun > us->max_lun) {
US_DEBUGP("Bad LUN (%d:%d)\n",
us->srb->device->id, us->srb->device->lun);
us->srb->result = DID_BAD_TARGET << 16;
}
/* Handle those devices which need us to fake
* their inquiry data */
else if ((us->srb->cmnd[0] == INQUIRY) &&
(us->fflags & US_FL_FIX_INQUIRY)) {
unsigned char data_ptr[36] = {
0x00, 0x80, 0x02, 0x02,
0x1F, 0x00, 0x00, 0x00};
US_DEBUGP("Faking INQUIRY command\n");
fill_inquiry_response(us, data_ptr, 36);
us->srb->result = SAM_STAT_GOOD;
}
/* we've got a command, let's do it! */
else {
US_DEBUG(usb_stor_show_command(us->srb));
us->proto_handler(us->srb, us);
}
/* lock access to the state */
scsi_lock(host);
/* indicate that the command is done */
if (us->srb->result != DID_ABORT << 16) {
US_DEBUGP("scsi cmd done, result=0x%x\n",
us->srb->result);
us->srb->scsi_done(us->srb);
} else {
SkipForAbort:
US_DEBUGP("scsi command aborted\n");
}
/* If an abort request was received we need to signal that
* the abort has finished. The proper test for this is
* the TIMED_OUT flag, not srb->result == DID_ABORT, because
* the timeout might have occurred after the command had
* already completed with a different result code. */
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {
complete(&(us->notify));
/* Allow USB transfers to resume */
clear_bit(US_FLIDX_ABORTING, &us->dflags);
clear_bit(US_FLIDX_TIMED_OUT, &us->dflags);
}
/* finished working on this command */
us->srb = NULL;
scsi_unlock(host);
/* unlock the device pointers */
mutex_unlock(&us->dev_mutex);
} /* for (;;) */
/* Wait until we are told to stop */
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (kthread_should_stop())
break;
schedule();
}
__set_current_state(TASK_RUNNING);
return 0;
}
跟踪以下cmnd_ready在哪里被通知有命令到来,发现了在scsiglue.c中的queuecommand函数(该函数实现了获取命令和唤醒usb_stor_control_thread内核线程)调用了它,那谁又调用了queuecommand函数呢,发现了在scsiglue.c中有一个结构体在很多地方都用到了:
struct scsi_host_template usb_stor_host_template = {
。。。。。。。。。。。。。
/* command interface -- queued only */
.queuecommand = queuecommand, //注册quequecommand函数
。。。。。。。。。。
}
usb_stor_host_template在/drivers/usb/storage/usb.c的probe函数中storage_probe的
host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us)); 语句被加到了Scsi_Host中,后来通过scsi_add_host注册到了SCSI层,因此,调用的顺序为上层的SCSI 的scsi_dispatch_cmd(structscsi_cmnd*cmd) -> queuecommand->complete(cmnd_ready)唤醒usb_stor_control_thread处理SCSI Command set。
USB Mass Storage层与SCSI层沟通结构体:Struct Scsi_Host、scsi_host_template, 这两个结构体一般通过SCSI层的scsi_host_alloc函数与Mass Storage的struct us_data结构体关联,通过scsi_add_host注册到SCSI层,scsi_host_template中除了queuecommand函数外,还有其他类似函数被注册到SCSI层。
针对Storage层自己实现的机制多线程,将命令接收下来进入队列,只是为了提高效率而已,也可将usb_stor_control_thread线程 与queuecommand函数合并处理(不考虑效率)。scsi_host_template的完整定义为:
struct scsi_host_template usb_stor_host_template = {
/* basic userland interface stuff */
.name = "usb-storage",
.proc_name = "usb-storage",
.proc_info = proc_info,
.info = host_info,
/* command interface -- queued only */
.queuecommand = queuecommand,
/* error and abort handlers */
.eh_abort_handler = command_abort,
.eh_device_reset_handler = device_reset,
.eh_bus_reset_handler = bus_reset,
/* queue commands only, only one command per LUN */
.can_queue = 1,
.cmd_per_lun = 1,
/* unknown initiator id */
.this_id = -1,
.slave_alloc = slave_alloc,
.slave_configure = slave_configure,
/* lots of sg segments can be handled */
.sg_tablesize = SG_ALL,
/* limit the total size of a transfer to 120 KB */
.max_sectors = 240,
/* merge commands... this seems to help performance, but
* periodically someone should test to see which setting is more
* optimal.
*/
.use_clustering = 1,
/* emulated HBA */
.emulated = 1,
/* we do our own delay after a device or bus reset */
.skip_settle_delay = 1,
/* sysfs device attributes */
.sdev_attrs = sysfs_device_attr_list,
/* module management */
.module = THIS_MODULE
};