这一节我们来分析一个在很多企业的产品中都存在的bug.写设备驱动是一件很实在的事情,你得根据实实在在的硬件来编写你的代码,如果你的硬件存在某种bug,那么你就要去fix它.如果你希望成为通用的驱动程序,那么你就要兼顾各家企业,兼顾各种可能存在的bug.也许一百家企业的产品都可以很好的被你的程序所支持,但是如果地一百零一家的产品有问题,你就得尽量解决.usb-storage正是这样一个模块.所以它的代码里会涉及到很多不同的企业.当然我相信,有一个更重要的原因,那就是,没有企业的支持,Linux不可能像今天这样火.所以Linux内核代码中支持诸多企业的设备也是必然的,就像厉娜在给把票投给许飞而不是给我们复旦的尚雯婕的时候说的那句:于情于理于公于私,都应该这样.
很多年前,《商业周刊》断言:“自由软件业的开发者大部分水平不高,不可能制造高端的企业级产品”. 这是事实, 出身卑微的Linux真正有出人头地的机会, 的确是在各大名企大规模介入之后. 而任何一家企业支持Linux的目的都只是为了赚钱.IBM干嘛支持Linux?老板思想境界高?想为全人类服务?我告诉你,IBM自从2000年开始展开Linux战略,2002年他们家就从Linux市场上赚取了10亿美元.所有的公司支持Linux的目的无非就是想瓜分那些曾经属于微软的财富. 正如洪波所说的那样,大企业只不过是花钱为这次抢劫置办一件迷人的外衣,让所有人认同这样一个观点,那就是,张君拿着武器抢劫运钞车,是死罪,有钱人拿着Linux瓜分微软,是正义. (注:张君,我的老乡,也是我当年的偶像)更滑稽的是,时间长了,每一个学习Linux的人都有这样一种感觉,觉得自己正做着一件伟大的正义的,追求自由的大事.(郑重声明一下,我是例外,从未觉得Linux跟伟大有什么关系,学习Linux只是混口饭而已.)
Ok,下面让我们来仔细看看这段属于苹果,属于诺基亚,属于摩托罗拉,属于索尼爱立信的代码吧.US_FL_FIX_CAPACITY这个flag的设置,意味着这种设备存在这么一个bug.在scsi众多命令中,有一个命令叫做READ CAPACITY.它的作用很简单,就是读取磁盘的总容量.先来点直观的印象吧,还记得当初哥们给你推荐的那个工具sg_utils3么,当初用它执行了INQUIRY命令.现在咱们用它执行READ CAPACITY,具体命令名字叫做sg_readcap.你要是装了SUSE Linux的话,里边的rpm包可能不叫sg_utils,而是叫scsi-xx-xx,比如偶的就是scsi-1.7_2.36_1.19_0.17_0.97-12.4.这个包包含很多执行scsi命令的工具.而且都有man文档,不会用看看man就知道了.
先来个硬盘的,比如偶的scsi硬盘:
myhost: # sg_readcap -b /dev/sda
0x11040000 0x200
myhost: # fdisk -l /dev/sda
Disk /dev/sda: 146.1 GB, 146163105792 bytes
255 heads, 63 sectors/track, 17769 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Device Boot Start End Blocks Id System
/dev/sda1 1 266 2136613+ 83 Linux
/dev/sda2 2879 17769 119611957+ 83 Linux
/dev/sda3 * 267 1572 10490445 83 Linux
/dev/sda4 1573 2878 10490445 82 Linux swap / Solaris
myhost: #
看出sg_readcap读出来的信息了吗?我们可以传递不同的参数,如果像我们这里-b参数,那么将获得block数以及每个block的字节数.我们来计算一下,我们这里返回的两个值分别是0x11040000和0x200,0x11040000对应于十进制285474816,0x200就是十进制的512,N多设备的block大小都是512.这两个相乘就是我们的容量,相乘的结果是146163105792,看到了吗?和fdisk命令显示出来的一模一样,呵呵,其实fdisk就是这么干的.不一样就见鬼了.Ok,有了直观的印象可以继续往下看了.刚才我们知道对于有设置US_FL_FIX_CAPACITY这个flag的设备,就会执行fix_read_capacity(),这个函数定义于drivers/usb/storage/protocol.c中,所谓的这个bug在这个注释里说得很清楚.明明容量是N,偏偏要汇报说自己是N+1,你说这不是找抽么?当然这也很简单,我们处理起来不难.
60 /*
61 * Fix-up the return data from a READ CAPACITY command. My Feiya reader
62 * returns a value that is 1 too large.
63 */
64 static void fix_read_capacity(struct scsi_cmnd *srb)
65 {
66 unsigned int index, offset;
67 __be32 c;
68 unsigned long capacity;
69
70 /* verify that it's a READ CAPACITY command */
71 if (srb->cmnd[0] != READ_CAPACITY)
72 return;
73
74 index = offset = 0;
75 if (usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
76 &index, &offset, FROM_XFER_BUF) != 4)
77 return;
78
79 capacity = be32_to_cpu(c);
80 US_DEBUGP("US: Fixing capacity: from %ld to %ld/n",
81 capacity+1, capacity);
82 c = cpu_to_be32(capacity - 1);
83
84 index = offset = 0;
85 usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
86 &index, &offset, TO_XFER_BUF);
87 }
应该说看过前面我们如果处理INQRUIY命令那个bug的代码的同志们应该能够很容易看懂眼前这段代码.其中调用的最关键的函数就是usb_stor_access_xfer_buf(),对usb_stor_access_xfer_buf()函数陌生的同志们可以回过去看看当时咱们是如何分析的.这里75行的作用就是从request_buffer里边把数据copy到c里边去,而85行的作用就是反过来把c里边的数据copy到request_buffer里边去.而在这之间,最重要的一步自然是79行和82行,capacity被赋为c,而c再被赋为capacity-1.这样简简单单的几行代码就fix了这么一个bug.
当然,虽然我不喜欢Linux,但是我还是有必要为你解释一些事情.
你是不是看见好些次这样一系列函数了: be32_to_cpu/cpu_to_be32(),cpu_to_le16(),cpu_to_le32(),此前我们一直没有讲,所以让我们现在一并来讲吧.反正整个故事的大致走向你已经很清楚了,从现在开始的故事基本上就属于一些小打小闹小修小补式的细枝末节了.le叫做Little Endian,be叫做Big Endian,这是两种字节序.le就表示地址地位存储值的低位,地址高位存储值的高位.be就表示地址低位存储值的高位,地址高位存储值的低位.我们就以这里这个临时变量c为例.假设c是这样被存储在内存地址0x0000开始的地方:
0x0000 0x12
0x0001 0x34
0x0002 0xab
0x0003 0xcd
如果你是采用le的字节序,那么读出来的值就是0xcdab3412,反之,如果你采用的是be的字节序,那么读出来的值就是0x1234abcd.同样的,如果你把0x1234abcd写入0x0000开始的内存中,那么结果就是:
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
为什么这几个函数名字里面都一个”cpu”?谈到字节序不谈cpu那就好比神采飞扬的谈起超级女声却对张靓颖是何许人也茫然不知.不同的cpu采用不同的字节序.看生产商自己喜欢了.其中,big endian以Motolora的PowerPC系列cpu为代表,而little endian则以我家Intel的x86系列cpu为代表.所以这几个函数名字里边都会有cpu的字样,那么毫无疑问对于不同的cpu,这几个函数执行的代码是不一样的.但是,凡是xx_to_cpu就说明函数的结果是给cpu使用的,反之如果是cpu_to_xx就说明是从cpu的字节序转换成目标字节序.
那么目标字节序应该是什么样子?我们先来看usb的情况.usb spec 2.0第八章,白纸黑字的规定了这么一句,usb总线上使用的是little-endian的字节序.所以,当初我们在处理bcs/bcb的时候一直在调用lexx_to_cpu/cpu_to_lexx()这样的函数,或者准确地说,这样的宏.(usb spec 2.0, Chapter 8, 8.1 Byte/Bit Ordering:
Bits are sent out onto the bus least-significant bit (LSb) first, followed by the next LSb, through to the most significant bit (MSb) last. In the following diagrams, packets are displayed such that both individual bits and fields are represented (in a left to right reading order) as they would move across the bus. Multiple byte fields in standard descriptors, requests, and responses are interpreted as and moved over the bus in little-endian order, i.e., LSB to MSB.)
而与此同时,在灯火阑珊处,我们也依稀记得,在那份名为SCSI Primary Commands-4的规范,即那份在江湖上有着SCSI葵花宝典之美誉的SPC-4中,第三章,3.5, Bit and byte ordering那一段,是这般描述的: If a field consists of more than one byte and contains a single value, the byte containing the MSB is stored at the lowest address and the byte containing the LSB is stored at the highest address (i.e., big-endian byte ordering).所以request_buffer回来的数据是采用be的字节序,因此我们这里的c要通过be32_to_cpu()转换才能变成cpu使用的结果.反过来,当我们再次copy回request_buffer中的时候,要再使用cpu_to_be32()给转回去.
最后如果你还要问,为什么要采用两种字节序?多麻烦啊?那我没什么可说的,你问上帝问真主问释迦牟尼去吧,也许他们能告诉你,不管白老鼠黑老鼠,只要不给猫逮住的就是好老鼠.