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

上帝给了每个人一支书写人生的铅笔,却未曾给我们橡皮擦.但计算机的世界却并非如此,电脑用着用着觉得不正常了,按一下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,其格式如下图所示:

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 268.2pt; HEIGHT: 48.6pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/JASON_~1/LOCALS~1/Temp/msohtml1/01/clip_image001.emz"></imagedata></shape>

其中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,保证设备的端点没有停止.就这些,这就够了.

你可能感兴趣的:(linux)