Linux那些事儿之我是U盘(52)有多少爱可以胡来?(一)

来源:互联网 发布:php is numeric 编辑:程序博客网 时间:2024/06/08 07:49

上帝给了每个人一支书写人生的铅笔,却未曾给我们橡皮擦.但计算机的世界却并非如此,电脑用着用着觉得不正常了,按一下reset键就一切ok. (当然你要是中了熊猫烧香啊中了冲击波啥的病毒那就另当别论了,,别打岔行不行,我们讲正事呢.)如果人生也可以这样,那么星爷的那段经典的妇孺皆知的人世间最悲哀的……假如……”的对白恐怕就没有意义了.

在驱动程序中,一个非常非常重要的概念就是错误处理.生活不是林黛玉,不会因为忧伤而风情万种,写代码不是写小说,不会因为作者的构思完美而天衣无缝.所以我们来看看在usb-storage,我们是如何来进行错误处理的.

一切都得从那个结构体变量struct scsi_host_template usb_stor_host_template开始说起.其实这个结构体正是我们和scsi核心层最最关键的接口.我们知道这个结构体有很多内容,我们为usb_stor_host_template的许多成员赋了值,但是显然很多我们都没有讲.那么现在是时候去讲了.这其中,我们不妨把与错误处理相关的三个成员给揪出来.这些函数都是我们自己定义的,提供给scsi core那边去调用,就好像queuecommand()一样.

   430         /* error and abort handlers */
    431         .eh_abort_handler =             command_abort,
    432         .eh_device_reset_handler =      device_reset,
    433         .eh_bus_reset_handler =         bus_reset,

,让我们一个一个来看.先看两个与reset相关的函数,网友要挑熟女问我为什么有两个reset函数,很简单,当你的电脑有点小毛病了之后,你可以有两种选择,一种是注销就可以了,一种是重起才可以.device_reset在这里对应的就是注销,bus_reset对应的就是重起.当然这样说并不严谨,只能说,一般来说轻微一点,device_reset就够了,如果严重一点,眼看着设备病入膏肓了,那么可能就要bus_reset().

Ok,我们让代码来告诉你.

首先看到的是device_reset().来自drivers/usb/storage/scsiglue.c,

239 /* This invokes the transport reset mechanism to reset the state of the
    240  * device */
    241 /* This is always called with scsi_lock(srb->host) held */
    242 static int device_reset(struct scsi_cmnd *srb)
    243 {
    244         struct us_data *us = (struct us_data *)srb->device->host->hostdata[0];
    245         int result;
    246
    247         US_DEBUGP("%s called/n", __FUNCTION__);
    248
    249         scsi_unlock(srb->device->host);
    250
    251         /* lock the device pointers and do the reset */
    252         down(&(us->dev_semaphore));
    253         if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
    254                 result = FAILED;
    255                 US_DEBUGP("No reset during disconnect/n");
    256         } else
    257                 result = us->transport_reset(us);
    258         up(&(us->dev_semaphore));
    259
    260         /* lock the host for the return */
    261         scsi_lock(srb->device->host);
    262         return result;
    263 }

244,没啥好说的,星星还是那颗星星哟,月亮还是那个月亮,山也还是那座山哟,梁也还是那道梁,碾子是碾子,缸是缸哟,爹是爹来娘是娘,麻油灯啊,还吱吱地响,点的还那么丁点亮,喔哦...喔哦...只有那篱笆墙影子咋那么长,只有那篱笆墙影子咋那么长,还有那看家的狗叫的叫的叫的叫的咋就这么狂...80后不可能没有听过毛阿敏的歌,不可能没有听过这首毛阿敏姐姐的成名作.20多年过去,星星不再像那颗星星,月亮也不象那个月亮,河也不是那条河哟,房也不是那座房.然而,us还是那个us.就像那篱笆墙,影子还那么长.

253,首先看US_FLIDX_DISCONNECTING flag设了没有.显然,disconnecting了就没有必要再reset.关于disconnect,你别急,讲完reset部分就该讲它了.

  否则,会调用us->transport_reset,我们前面已经说过了,很久很久以前,我们曾经为us->transport_reset赋值为usb_stor_Bulk_reset,所以这里也就是函数usb_stor_Bulk_reset()会被调用,usb_stor_Bulk_reset定义于drivers/usb/storage/transport.c,

1184 /* This issues a Bulk-only Reset to the device in question, including
   1185  * clearing the subsequent endpoint halts that may occur.
   1186  */
   1187 int usb_stor_Bulk_reset(struct us_data *us)
   1188 {
   1189         US_DEBUGP("%s called/n", __FUNCTION__);
   1190
   1191         return usb_stor_reset_common(us, US_BULK_RESET_REQUEST,
   1192                                  USB_TYPE_CLASS | USB_RECIP_INTERFACE,
   1193                                  0, us->ifnum, NULL, 0);
   1194 }

进入这个函数一看,很简单,也不干别的,就是调用usb_stor_reset_common().于是,咱们接着来到了这个来自driver/usb/storage/transport.c中的usb_stor_reset_common()函数.

   1101 /* This is the common part of the device reset code.
   1102  *
   1103  * It's handy that every transport mechanism uses the control endpoint for
   1104  * resets.
   1105  *
   1106  * Basically, we send a reset with a 20-second timeout, so we don't get
   1107  * jammed attempting to do the reset.
   1108  */
   1109 static int usb_stor_reset_common(struct us_data *us,
   1110                 u8 request, u8 requesttype,
   1111                 u16 value, u16 index, void *data, u16 size)
   1112 {
   1113         int result;
   1114         int result2;
   1115         int rc = FAILED;
   1116
   1117         /* Let the SCSI layer know we are doing a reset, set the
   1118          * RESETTING bit, and clear the ABORTING bit so that the reset
   1119          * may proceed.
   1120          */
   1121         scsi_lock(us->host);
   1122         usb_stor_report_device_reset(us);
   1123         set_bit(US_FLIDX_RESETTING, &us->flags);
   1124         clear_bit(US_FLIDX_ABORTING, &us->flags);
   1125         scsi_unlock(us->host);
   1126
   1127         /* A 20-second timeout may seem rather long, but a LaCie
   1128          * StudioDrive USB2 device takes 16+ seconds to get going
   1129          * following a powerup or USB attach event.
   1130          */
   1131         result = usb_stor_control_msg(us, us->send_ctrl_pipe,
   1132                         request, requesttype, value, index, data, size,
   1133                         20*HZ);
   1134         if (result < 0) {
   1135                 US_DEBUGP("Soft reset failed: %d/n", result);
   1136                 goto Done;
   1137         }
   1138
   1139         /* Give the device some time to recover from the reset,
   1140          * but don't delay disconnect processing. */
   1141         wait_event_interruptible_timeout(us->dev_reset_wait,
   1142                         test_bit(US_FLIDX_DISCONNECTING, &us->flags),
   1143                         HZ*6);
   1144         if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
   1145                 US_DEBUGP("Reset interrupted by disconnect/n");
   1146                 goto Done;
   1147         }
   1148
   1149         US_DEBUGP("Soft reset: clearing bulk-in endpoint halt/n");
   1150         result = usb_stor_clear_halt(us, us->recv_bulk_pipe);
   1151
   1152         US_DEBUGP("Soft reset: clearing bulk-out endpoint halt/n");
   1153         result2 = usb_stor_clear_halt(us, us->send_bulk_pipe);
   1154
   1155         /* return a result code based on the result of the control message */
   1156         if (result < 0 || result2 < 0) {
   1157                 US_DEBUGP("Soft reset failed/n");
   1158                 goto Done;
   1159         }
   1160         US_DEBUGP("Soft reset done/n");
   1161         rc = SUCCESS;
   1162
   1163   Done:
   1164         clear_bit(US_FLIDX_RESETTING, &us->flags);
   1165         return rc;
   1166 }

前面几行是赋值,然后usb_stor_report_device_reset()被调用. usb_stor_report_device_reset()定义于drivers/usb/storage/scsiglue.c,

    308 /* Report a driver-initiated device reset to the SCSI layer.
    309  * Calling this for a SCSI-initiated reset is unnecessary but harmless.
    310  * The caller must own the SCSI host lock. */
    311 void usb_stor_report_device_reset(struct us_data *us)
    312 {
    313         int i;
    314
    315         scsi_report_device_reset(us->host, 0, 0);
    316         if (us->flags & US_FL_SCM_MULT_TARG) {
    317                 for (i = 1; i < us->host->max_id; ++i)
    318                         scsi_report_device_reset(us->host, 0, i);
    319         }
    320 }

  315,scsi_report_device_reset(),drivers/scsi/scsi_error.c中定义的.这个函数scsi core那边要求我们调用的,我们身不由己.然而关于这个函数的细节,只能说,世界太大,我们只在乎我们需要在乎的冬冬,其她的我们无暇顾及.我们只想在960万平方千米的一个角落里,静静的为自己的理想打拼,为自己寻找一份荣耀.usb是我们care,scsi的核心,我们是不想去深究的,只有写scsi核心的同志们会在乎.江湖中流传这么一句话:女孩在乎的是下半生的幸福,男孩关注的是下半身的幸福.同样,Linux世界里,每一个人在乎的冬冬是不一样的……言归正传,咱们是不需要关系这个函数怎么定义的,但是咱们需要知道什么时候会调用她,调用她干嘛?需要传递什么参数?首先,要传递三个参数,第一个,Scsi_Host指针,一块u盘就有一个Scsi_Host,然后第二个参数,channel,然后第三个参数target,描述scsi设备位置的四个参数就三缺一了,缺的就是LUN,因为一个devicereset那么就不会管她上面有几个LUN,有几个都一起给她reset.那么调用这个函数的目的是什么?告诉scsi核心,俺观察到某个设备reset.至于scsi核心会如何处理呢,那咱管不着,也懒得去管.总而言之,言而总之,统而言之,言而统之,咱们的职责是在发现了一个设备reset之后立刻向上级汇报.

 US_FL_SCM_MULT_TARG这个flag,咱们也提过好几次,她代表的是支持多个target,这是设备本身的属性,不是咱们的代码愣给设备设的.对于这种设备,scsi_report_device_reset()就会被多调用几次,针对每一个targetreport一次.

  结束了scsi_report_device_reset(),自然又回到了usb_stor_reset_common(),1123,1124行设置一个flag,清除一个flag,设置的是US_FLIDX_RESETTING,清除的是US_FLIDX_ABORTING,关于这两个flag,一会咱们结合command_abort()来讲.

1131,usb_stor_control_msg()被调用,再一次看到这个函数想必大家已经不再陌生了吧.她就是发送一个控制命令,其实我们已经很久没有讲控制传输了.这里结合参数来看看传送的什么命令.首先,us还是那个us,不再多说.然后,pipeus->send_ctrl_pipe,就是发送控制管道.然后request,requesttype这些都是在调用usb_stor_reset_common()的时候传递进来的参数,usb_stor_Bulk_reset()中可以看到,requestUS_BULK_RESET_REQUEST,requesttypeUSB_TYPE_CLASS | USB_RECIP_INTERFACE.

  US_BULK_RESET_REQUESTdrivers/usb/storage/transport.h中被设置为0xff,这是和usb mass storage class-Bulk Only transport协议相对应的.该协议专门为Bulk-Only Mass Storage设备定义了一个请求,Reset.协议里说,this request is used to reset the mass storage device and its associated interface.协议中规定了,usb host要发送命令reset usb设备的时候,需要通过发送控制管道发送一个请求,即前面提过的ctrlrequest,其格式如下图所示:

其中bReques这一位须设置为255(FFh),wValue设置为0,wIndex设置为interface number,wLength设置为0.(而我们这里也确实这样做了,wIndex被赋值为us->ifnum,和上次咱们调用usb_stor_control_msg的时候传递的一样,显然interface还是那个interface.江山会变,四季会变,咱们心中的interface始终不变.)

  至于requesttype,和咱们在usb_stor_Bulk_max_lun()中讲的差不多,唯一的区别是控制数据传输方向,当时是device to host,现在是host to device,所以当时多了一个USB_DIR_IN,而现在没有写USB_DIR_OUT,原因很简单,USB_DIR_OUT被定义为0,所以或不或她无所谓.

  ,酱紫,就完成了向设备发送reset命令的任务.返回值小于0就是出错了.

  没出错那么就1141,wait_event_interruptible_timeout()被调用.us->dev_reset_wait咱们前面讲过,她是一个等待队列头,storage_probe()中被初始化,而以后在讲storage_disconnect()时会讲到,有这么一句,wake_up(&us->dev_reset_wait),她唤醒的正是这里进入睡眠的进程,这里1141,会进入睡眠,进入睡眠之前先判断US_FLIDX_DISCONNECTING这个flag有没有设置,要是设了就没有必要睡眠了,直接退出吧.1144,再判断一次,是真的设了这个flag那么直接goto Done,返回rc,rc就是初值FAILED.返回之前先清除US_FLIDX_RESETTING flag. 关于wait_event_interruptible_timeout()这个函数,我们当初在分析usb_stor_scan_thread()的时候已经详细的讲过了,所以这里不需要再浪费你我的青春去多讲了.

  当然,如果US_FLIDX_DISCONNECTING并没有设置,那么6s钟时间到了,睡到自然醒,11501153,usb_stor_clear_halt(),又是一个很亲切的函数,表忘了当前我们在讲GET MAX LUN的时候就专门介绍了这个函数,而且那时候我们就已经说过,有一种情况下我们要调用这个函数,说的正是我们这里的情况,即当设备reset之后,需要清楚halt这个feature,然后端点才能正常工作.对于我们的两个bulk端点,只要有一个清halt feature失败了,那么整个这个负责reset的函数usb_stor_reset_common就算失败了,并且因此会返回FAILED,而且在返回之前先把US_FLIDX_RESETTING这个flag给去掉.

然后到了这里,usb_stor_reset_commond该返回了,然后我们惊讶的发现, usb_stor_Bulk_reset()也该返回了,再然后我们又惊讶的发现,device_reset()也该返回了.就这样,我们走完了device_reset()这么一个函数.之所以简单是因为它的使命本身就很简单,其实就是给设备发送一个resetrequest,然后clearhalt feature,保证设备的端点没有停止.就这些,这就够了.

原创粉丝点击