Linux那些事儿之我是U盘(50)跟着感觉走(二)

回到usb_stor_invoke_transport()中来,540,还是老套路,又问是不是命令被放弃了,放弃了当然下面的就别执行了.goto Handle_Abort.
  
  
546,如果有错误,注意正如前面所说,USB_STOR_TRANSPORT_ERROR表示传输本身就是有问题的,比如管道堵塞.USB_STOR_TRANSPORT_FAILED则只是说明命令传输是没有问题的,就比如你作为场外观众给非常六加一发短信了,然后李咏随机抽到你,给你打电话,电话通了,让你砸金蛋,砸出了金花你就能获得自己想要的奖品,但是问题是你没有砸中,这样你就失去了机会,这属于FAILED,但是另一种更惨的情况是,咏哥给你打电话还赶上你小子把手机关了,那你就只好认倒霉了,这就属于ERROR.对于这种疑似管道堵塞的问题,我们会调用自己写的一个函数us->transport_reset(us),us->transport_reset()其实也是一个指针,我们也是很早以前和us->transport()一起赋的值,对于U,我们赋的值是usb_stor_Bulk_reset().所谓reset,就相当于我们重起计算机,每次遇到些什么乱七八糟的问题,我们二话不说,重起机器通常就会发现一切都好了.关于设备reset的冬冬,我们讲完命令的执行这一块之后再来专门讲.暂且不表.对于这种情况,当然我们会设置srb->resultDID_ERROR.然后返回.
  
  
554,看到了吧,这里就判断是不是USB_STOR_TRANSPORT_NO_SENSE.如果是的,那么返回给scsi的结果是SAM_STAT_CHECK_CONDITION.返回这个值,scsi核心层那边就知道会去读srb->sense_buffer里边的东西.SAM_STAT_CHECK_CONDITIONscsi那边定义的宏,scsi协议规定,scsi总线上有若干个阶段,比如命令阶段,比如数据阶段,比如状态阶段,这三个阶段其实咱们Bulk-Only spec里边也有.不过scsi协议里还规定了更多的一些阶段,scsi协议里边称一个阶段为一个phase.除了这三个phase以外,还可以有bus free phase,selection phase,message phase等等.而状态阶段就是要求目标设备返回给主机一个状态码(status code).关于这些状态码,scsi的规范里边定义得很清楚.include/scsi/scsi.h中也有相关的宏定义.
  
  
    117 /*
  
  
    118  *  SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft
  
  
    119  *  T10/1561-D Revision 4 Draft dated 7th November 2002.
  
  
    120  */
  
  
    121 #define SAM_STAT_GOOD            0x00
  
  
    122 #define SAM_STAT_CHECK_CONDITION 0x02
  
  
    123 #define SAM_STAT_CONDITION_MET   0x04
  
  
    124 #define SAM_STAT_BUSY            0x08
  
  
    125 #define SAM_STAT_INTERMEDIATE    0x10
  
  
    126 #define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14
  
  
    127 #define SAM_STAT_RESERVATION_CONFLICT 0x18
  
  
    128 #define SAM_STAT_COMMAND_TERMINATED 0x22        /* obsolete in SAM-3 */
  
  
    129 #define SAM_STAT_TASK_SET_FULL   0x28
  
  
    130 #define SAM_STAT_ACA_ACTIVE      0x30
  
  
    131 #define SAM_STAT_TASK_ABORTED    0x40
  
  
其中,SAM_STAT_CHECK_CONDITION就是对应scsi协议中的CHECK CONDITION,这一状态表明有sense data被放置在相应的buffer,于是scsi core那边就会去读sense buffer.而我们这里遇到这种情况,当然就可以返回了.
  
  
559,要是没别的啥意外的话,到了这里我们就可以设置srb->resultSAM_STAT_GOOD,说明一切都是ok.当然,对于之后会出现的REQUEST SENSE的执行失败,我们会再次修改srb->result.
  
  
下面566,出现了一个叫做need_auto_sense的变量,这是我们定义的临时变量,这里赋初值为0.574588,两个if语句,need_auot_sense赋了值,赋值为1.我们首先很容易理解第二个if,正如我们前面介绍的那样,REQUEST SENSE这个艰巨的任务被下放给了我们底层驱动,那么我们就勇敢的去承担它,USB_STOR_TRANSPORT_FAILED的情况下,我们就去发送一个REQUEST SENSE命令.设了这个flag稍后我们就会看到我们会因此而执行REQUEST SENSE.那么第一个if?相信那些注释已经说得很清楚了吧,如果你对自己的智商还有一丁点儿信心的话,应该就不需要我解释了.我只说一句,对于那些遵守CB或者DPCM协议的设备它们没有自己没有办法决定状态,所以scsi核心层当然就不知道去读它们的sense buffer,但是不读sense buffer我们连这个命令执行成功与否都不知道,那怎么行?而设备对于大多数读操作的错误也不需要使用sense buffer,因为它们对于读操作的错误通常会停止掉bulk-in pipe,这已经是一个很明显的信号了,不需要再检测sense buffer,因为检测sense buffer或者说检测sense data的目的无非就是出错了以后想知道出错的原因,而这种情况下原因已经清楚了嘛不是.至于你问为什么读操作会有这种特点,那我只能说两个字,经验.写设备驱动靠的就是经验.
  
  
594,srb->resid大于0,说明有问题,希望传输n个字节,结果汇报上来说只传递了n-m个字节.对于这里列出的这五个命令,少传几个字节倒是无所谓,比如INQUIRY,我就想知道设备的基本信息,那你说你姓甚名谁,生辰八字,学历如何,婚否,等等这些信息,你多说两句就多说两句,少说两句就少说两句,无所谓,没什么影响,但是有些命令就不能少传输了,比如我要传一个pdf文档,你传到一半就不传了,那肯定不行,直接导致我可能打不开这个pdf文档.驱动程序要是写成这样,那人家不跟你急就怪了.这里咱们就是调用US_DEBUGP打印一句调试语句,也就不退出了,咱们忍了.
  
  
接下来的一些行,604行开始,一直到713,我们就是为need_auto_sense的情况发送REQUEST SENSE命令了.其实和之前一样,我们还是等于再进行一次Bulk传输,还是三个阶段,不过我们有了之前的经验,现在再看Bulk传输就简单多了,无非就是准备一个struct scsi_cmnd,调用us->transport(us->srb,us),然后结束了之后检查结果.这正是这一百多行所做的事情.不过我们偷了点懒,没有另外申请一个struct scsi_cmnd,而是直接利用之前的那个srb,只是调用us->transport之前先把一些关键的东西备份起来,然后执行完us->transport之后再恢复过来.所以接下来我们看到如下事件:
  
  
old_cmnd保存了srb->cmnd;
  
  
old_cmd_len保存了srb->cmd_len;
  
  
先把srb->cmnd清零,然后对它重新赋值,REQUEST SENSE的意思来赋值.
  
  
不同的命令集里边REQUEST SENSE的长度也不同,对于RBC或者咱们的SCSI,长度为6,而对于别的命令集,其长度为12.
  
  
然后用old_sc_data_direction保存了srb->sc_data_direction,而把srb->sc_data_direction设置为REQUEST SENSE的要求,DMA_FROM_DEVICE,很显然,REQUEST SENSE是向设备要sense data,那么当然数据传输方向是从设备到主机.
  
  
然后用old_request_buffer来保存srb->request_buffer,而将srb->request_buffer设置为srb->sense_buffer,同时用old_request_bufflen来备份srb->request_bufflen,同时把srb->request_bufflen设置为18.
  
  
old_sg来备份srb->use_sg,而把srb->use_sg设置为0,传这么点数据也就别用那麻烦的scatter-gather机制了.
  
  
然后用old_serial_number来备份srb->serial_number,并把srb->serial_number的最高位取反.
  
  
最后用old_resid来备份srb->resid,而把srb->resid再次初始化为0.
  
  
这时候就可以调用us->transport(us->srb,us).并且用一个临时变量temp_result来保存这个结果.
  
  
这次命令完了之后,我们659666,就把刚才备份的那些变量给恢复原来的值.
  
  
668,再一次判断是不是被放弃了,如果是又goto Handle_Abort.
  
  
672,然后判断temp_result,如果这个result说明这次传输还有问题,那就说明连REQUEST SENSEfail.于是我们会设置srb->result=DID_ERROR<<16.在这之前我们还会调用us->transport_reset(us)把设备reset,因为REQUEST SENSE都出错本身就说明很不正常,这种情况下我们不得不来点狠的了,男人嘛,就是要对自己狠一点!当然这里有一个条件,我们判断的是US_FL_SCM_MULT_TARG这个flag没有设置,因为设了这个flag的设备有多个target,这种情况下咱们就不好胡乱给人全reset,因为REQUEST SENSE这个命令虽然是一个基本的命令,但是毕竟执行成功与否无所谓,我们只是出于好奇才想知道一个命令执行出错的原因.即使不知道也没有太大的关系.没必要非得大张旗鼓的把一个多个target的设备给人家reset,不该管的事情不要管.
  
  
然后685696行无非就是把temp_result的值打印出来,sense_buffer里的值打印出来.这些都是调试信息.对调试设备驱动非常管用.
  
  
继续讲之前,我们先看一下,692,usb_stor_show_sense().这个函数,是第一次出现,曾经我们见过一个类似的函数,名叫usb_stor_show_command().她们是老乡,都来自drivers/usb/storage/debug.c:
  
  

    159 void usb_stor_show_sense(
    160                 unsigned char key,
    161                 unsigned char asc,
    162                 unsigned char ascq) {
    163
    164         const char *what, *keystr;
    165
    166         keystr = scsi_sense_key_string(key);
    167         what = scsi_extd_sense_format(asc, ascq);
    168
    169         if (keystr == NULL)
    170                 keystr = "(Unknown Key)";
    171         if (what == NULL)
    172                 what = "(unknown ASC/ASCQ)";
    173
    174         US_DEBUGP("%s: ", keystr);
    175         US_DEBUGPX(what, ascq);
    176         US_DEBUGPX("/n");
    177 }

这里面又调用了其她函数,scsi_sense_key_stringscsi_extd_sense_format,这两个函数来自driver/scsi/constants.c,暂且不表.先来看对usb_stor_show_sense这个函数的调用.传递给她的实参是srb->sense_buffer中的几个元素,对比咱们前面贴出来的那个sense data的格式,可知sense_buffer[2]的低四位被称为Sense Key,sense_buffer[12]Additional sense code,也称ASC,sense_buffer[13]Additional sense code qulifier,也称ASCQ.这三个冬冬联手为mid level提供了需要的信息,主要也就是错误信息或者异常信息.为什么要三个冬冬呢?实际上就是一个分层的描述方法,比如要描述某个房间就要说某城市某街道某门牌号.这三个冬冬也是起着这么一个作用,Sense Key是第一层,ASC则是对她的补充,ASCQ则又是对ASC的补充,或者说解释.这样我们再来看看usb_stor_show_sense就很清楚了,咱们传递进来的是三个char变量,而实际的信息就像某种编码一样被融入在了这些char变量中,而调用的两个来自scsi核心层的函数scsi_sense_key_stringscsi_extd_sense_format就是起着翻译的作用,也叫解码.解码了就可以打印出来了.Yeah!

  699,srb->result设置为SAM_STAT_CHECK_CONDITION.为什么?不为什么,Request Sense执行完之后,scsi规范告诉我们应该把srb->result设为SAM_STAT_CHECK_CONDITION,酱紫mid level就知道去检查sense data.这也是为什么在554,555行会令srb->result也为这个值,只不过那次sense data是咱们自己手工准备的,不是通过命令获得的.

  704这个if这一小段,首先咱们需要明白,need_auot_sense这个flag被设为1实际上是有两种可能的,它本身是在usb_stor_invoke_transport()中第一行所定义的一个局部变量,并且在这个函数中特意把它初始化为0. 第一处设置为1的位置是574行当时check us->protocolUS_PR_CB或者US_PR_DPCM_USB,对于这种设备,(如果您只关心u,那么就甭理这种设备了.)第二处设置这个flag的就是我们确实遇到了failure,585,result如果等于USB_STOR_TRANSPORT_FAILED,这种情况当然要设置need_auto_sense.704行这里判断result是否等于USB_STOR_TRANSPORT_GOOD,那么很显然,如果result等于USB_STOR_TRANSPORT_FAILED,那么它就不可能等于USB_STOR_TRANSPORT_GOOD,因此,这里这个判断一定是针对第一种need_auto_sense的情况,正如我们曾经说过的,遵守US_PR_CB/US_PR_DPCM_USB协议的设备是不会自己返回命令执行之后的Status,所以我们不管它执行到底成功与否,我们都会对它来一次REQUEST SENSE,就是为了尽可能多的获取一些信息,这样一旦出了问题,我们至少能多一些辅助信息来帮我们判断问题出在哪.那么对于USB_STOR_TRANSPORT_GOOD的情况,首先这说明命令执行是没有问题的了,我们仔细看一下这个if语句,除了这个条件以外还判断了另外三个条件,(srb->sense_buffer[2]&0xaf)结果为0,那么说明srb->sense_buffer[2]bit0~bit3都为0,bit50,bit7也为0,bit4bit6是什么我们无所谓.(如果这个你还要问为什么那么我只能说你没救了.没办法,这个世界上只有10种人,一种是懂二进制的,一种是不懂二进制的.)虽然我们没有兴趣熟悉每一个SCSI命令的细节,但我们毕竟是共产主义接班人,应该对社会主义建设的方方面面都有所了解,所以让我们来仔细看看这个sense_buffer[2].对照sense data的格式那张图,sense data的第二个字节,bit0~bit3sense key,bit4Reserved,即保留的,不使用的.bit5ILI,全称incorrect length indicator,bit6EOM,全称End of Medium,bit7Filemark,伟大的不朽的金山词霸告诉我们这个词叫做卷标.关于sense key,Scsi协议是这么规定的,如果sense key0h,那么这种情况表示NO SENSE.这种情况通常对应于命令的成功执行或者就是Filemark/EOM/ILI bits中的任一个被设置为了1.需要注意的是,scsi协议里边定义了四样东西,Filemark/EOM/ILI/Sense Key,它们都是为了提供错误信息的,只是前三者只要一个bit就能表达明确的意思了,而最后一个包含很多信息,所以需要用4bits,并且还在后面附有很多额外信息,sense_buffer[12]sense_buffer[13],这里也要求它们为0,即所谓的ASCASCQ都为0,scsi协议里面,这种情况称之为NO ADDITIONAL SENSE INFORMATION.关于这一点scsi协议是这么说:”The REQUEST SENSE command requests that the target transfer sense data to the initiator. If the target has no other sense data available to return, it shall return a sense key of NO SENSE and an additional sense code of NO ADDITIONAL SENSE INFORMATION.”而这正是我们这里的代码所表达的意思.(什么?你要我翻译这段话?有没有搞错啊,难道你没上过新东方,没听过老罗的课?那么我代表人民代表党义正严辞的告诉你,同志,你真的落伍了耶!)

(filemarkeom都是针对磁带设备的,跟磁盘设备无关.也就是说跟咱们无关.)

最后,满足了这四个条件的情况就表示刚才这次scsi命令的传输确实是圆满完成了.应该说这次检测还是蛮严格的,毕竟开源社区的同志们觉得写代码不像我们开会,每次看新闻,发现凡是会议必然是圆满成功的.这里人家检查了这么多条件都满足然后就设置srb->resultSAM_STAT_GOOD,并且把srb->sense_buffer[0]也置为0.sense databyte 0由两部分组成,ValidError code,如果置为0,首先就说明这整个sense data是无效的, scsi标准的说法叫invalid, 所以scsi core自然没法识别这么一个sense data,而我们既然认定这个命令是成功执行的,当然就没有必然让scsi core 再去理睬这么一个sense data.

以上花了大量笔墨就讲了704712这个if语句段.需要重新强调一点,正如我们已经说过的,对于U,这段代码根本就不可能执行,理由我们已经说过了.但是既然它出现在我们眼前了,我们又有什么理由去逃避呢?写代码,尤其是写这种通用的设备驱动程序,必然要考虑各种情况,不是完全跟着感觉走,也不是纯粹的追求华丽的算法和数据结构,更应该接近实际,华丽的代码堆砌的东西缺乏骨质感.

  这样,关于need_auto_sense设置了的这一段就结束了.最后还想重复一点,说起来,REQUEST SENSE这种命令应该由mid level来发,不应该由底层驱动来发,不过通常mid-level并不愿意发这个命令,因为实际上很多SCSI主机适配卡(SCSI host adapter)会自动request the sense.所以为了让事情变得简单,设计上要求底层驱动去对付这个问题.所以要么SCSi host adapters自动获得sense data,要么就是咱们LLD(底层驱动程序)去发送这个命令,对于咱们这个模拟的scsi系统,当然只能是用软件去实现,即咱们必须在LLD中用代码来发送request sense.

  再然后,716,如果经过了这么一番折腾,srb->result仍然等于SAM_STAT_GOOD,(我们在559,即进行autosense之前把srb->result设置成了SAM_STAT_GOOD.)那么说明真金不怕火炼,我们再判断最后一个条件,即我们要求传输的数据长度是srb->request_bufflen,而实际上还剩下srb->resid个字节没有被传送,这种情况本身没什么,但是struct scsi_cmnd中有一个成员叫做underflow,其意思是如果传输的数据连这个值都没有达到的话,不管其它条件如何,必须向上层反映,出错了.换句话说,有些scsi命令有一个底线,你至少得达到我这个底线,否则我跟你急!所以这里就是判断这么一个条件是否满足,如果传输的长度小于srb->underflow,那么不用废话,即便你其它条件判断下来都觉得这个命令是成功的,我还是要汇报说你这个命令执行有误.而关于这种情况,我们反馈给scsi coreresultDID_ERROR<<16或上SUGGEST_RETRY<<24.DID_ERROR被定义为0x07,SUGGEST_RETRY0x10.其定义都在include/scsi/scsi.h.所以这里srb->result就最终被设置为0x10070000.还记不记得当初我们贴出来的那个关于US_FL_IGNORE_RESIDUE关于MP3的调试信息了?回过去看一下,没错,当时的result就是0x10070000,也就是这里赋的值.而当时之所以导致执行了这段代码,原因正是设备报虚警,明明读写正常,它偏要瞎写一个residue到状态字节里去.导致我们的代码在这里判断出读写出了错.这个疑案只有到了这里我们才能真正明白,哈哈!

  至此,我们的这个故事也快接近尾声了.故事总有结束的时候,Linux所反映的那种人们对自由的追求却是永无止境的.天长地久有时尽,此恨绵绵无绝期!--开源社区追求自由的战士白居易.

720, usb_stor_invoke_transport()函数终于返回了.如果之前有执行goto Handle_Abort,那么724行会被执行,实际上就是设置srb->resultDID_ABORT<<16,并且执行reset.关于reset,您别急我们马上就会讲.
  
  
usb_stor_invoke_transport()返回就回到了usb_stor_transparent_scsi_command(),这个函数实际上不干什么正经事,就是调用usb_stor_invoke_transport().然后它又判断了一个flag, US_FL_FIX_CAPACITY,又是对了对付某些硬件bug.我们可以看到,如果这个flag没有设置,那么usb_stor_transparent_scsi_command()函数就会就此返回了,返回到usb_stor_control_thread(),别忘了我们曾经是从375us->proto_handler(us->srb,us)进入usb_stor_transparent_scsi_command(),返回了375行之后怎么走我们之前就已经分析过了,usb_stor_control_thread()这个守护进程,它还将永垂不朽的循环下去,它会进入睡眠等待着下一个命令的到来,如果你不强行kill,或者卸载模块,那么它将守护到天长地久,到海枯石烂,到山峰无棱到河水不再流...
  
  
然后我们就可以去看US_FL_FIX_CAPACITY,这里又是一个硬件bug,前几年这种硬件bug出现得还不是很多,2.6.10的内核中只有松下等几家公司的产品有这么一个问题,不过随着改革开放的发展,很多事情都变了,猪肉涨价了,方便面涨价了,Apple公司的iPod,诺基亚 3250,E70,E60,N91,N80,E61,NIKONDSC D70,DSC D70s,DSC D80,索尼爱立信的P990i,M600i,摩托罗拉的RAZR V3x,RAZR V3i,等一大批产品都出现了这个问题.虽然偶自己的Nokia 6108没有这种问题,但是考虑到AppleiPod最近被评为最伟大的20IT产品之一,Nokia作为我们Intel多年来最重要的customer之一,我觉得这里我们还是有必要来看看这个bug.再说了,了解了这个,将来去Nokia面试,Apple面试,或者次一点,去摩托罗拉或者索尼爱立信面试,跟人家谈一下,,伙计,我听说你家的产品有这么这么一个bug,怎么回事啊?兄弟们是不是待遇不佳压力太大啊?人家一看你这么拽,连这种内幕都知道,能不要你么?
  
  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值