Linux那些事儿之我是SCSI硬盘(5)三座大山(二)

来源:互联网 发布:mac dock栏 编辑:程序博客网 时间:2024/05/16 09:42

第二座大山,sd_read_write_protect_flag.

   1327 /*

   1328  * read write protect setting, if possible - called only in sd_revalidate_disk()

   1329  * called with buffer of length SD_BUF_SIZE

   1330  */

   1331 static void

   1332 sd_read_write_protect_flag(struct scsi_disk *sdkp, unsigned char *buffer)

   1333 {

   1334         int res;

   1335         struct scsi_device *sdp = sdkp->device;

   1336         struct scsi_mode_data data;

   1337

   1338         set_disk_ro(sdkp->disk, 0);

   1339         if (sdp->skip_ms_page_3f) {

   1340                 sd_printk(KERN_NOTICE, sdkp, "Assuming Write Enabled/n");

   1341                 return;

   1342         }

   1343

   1344         if (sdp->use_192_bytes_for_3f) {

   1345                 res = sd_do_mode_sense(sdp, 0, 0x3F, buffer, 192, &data, NULL);

   1346         } else {

   1347                 /*

   1348                  * First attempt: ask for all pages (0x3F), but only 4 bytes.

   1349                  * We have to start carefully: some devices hang if we ask

   1350                  * for more than is available.

   1351                  */

   1352                 res = sd_do_mode_sense(sdp, 0, 0x3F, buffer, 4, &data, NULL);

   1353

   1354                 /*

   1355                  * Second attempt: ask for page 0 When only page 0 is

   1356                  * implemented, a request for page 3F may return Sense Key

   1357                  * 5: Illegal Request, Sense Code 24: Invalid field in

   1358                  * CDB.

   1359                  */

   1360                 if (!scsi_status_is_good(res))

   1361                         res = sd_do_mode_sense(sdp, 0, 0, buffer, 4, &data, NULL);

   1362

   1363                 /*

   1364                  * Third attempt: ask 255 bytes, as we did earlier.

   1365                  */

   1366                 if (!scsi_status_is_good(res))

   1367                         res = sd_do_mode_sense(sdp, 0, 0x3F, buffer, 255,

   1368                                                &data, NULL);

   1369         }

   1370

   1371         if (!scsi_status_is_good(res)) {

   1372                 sd_printk(KERN_WARNING, sdkp,

   1373                           "Test WP failed, assume Write Enabled/n");

   1374         } else {

   1375                 sdkp->write_prot = ((data.device_specific & 0x80) != 0);

   1376                 set_disk_ro(sdkp->disk, sdkp->write_prot);

   1377                 sd_printk(KERN_NOTICE, sdkp, "Write Protect is %s/n",

   1378                           sdkp->write_prot ? "on" : "off");

   1379                 sd_printk(KERN_DEBUG, sdkp,

   1380                           "Mode Sense: %02x %02x %02x %02x/n",

   1381                           buffer[0], buffer[1], buffer[2], buffer[3]);

   1382         }

   1383 }

这个函数看似很长,其实有意义的就是一行,那就是1376,调用set_disk_ro()从而确定本磁盘是否是写保护的.

1338,set_disk_ro就是设置磁盘只读,0就是可读可写,1才是设置为只读.但是咱们这只是软件意义上的作个记录而已,硬件上还得听磁盘自己的.所以我们通过下面一大段代码最终得到这一信息,最终在1376行再次设置.

那么如何得知写保护是否设置了呢?发送命令给设备,这个命令就是MODE SENSE.MODE SENSE这个命令的目的在于获得设备内部很多潜在的信息,这其中包括设备是否设置了写保护,当然还有更多SCSI特有的信息.只不过我们此时此刻只关注写保护设了没有.这些特性就像设备的天性一样,在它出生的时候就设置好了,当然有些天性也是可以改变的,就比如范冰冰,可能她生下来的时候长相平平,但是经过整容,变成了美女.又比如何丽秀,原本是男人,后来却变成了女人.而对于SCSI设备来说,很多特性可以改变,但是有些特性就不可以改变了,比如medium type,即它属于哪种类型的设备,对于SCSI Block设备,其内部保存MEDIUM TYPE的这个byte一定是00h.

在咱们的驱动中为了发送这个命令,还作了两次包装,先调用sd_do_mode_sense().

   1316 /* called with buffer of length 512 */

   1317 static inline int

   1318 sd_do_mode_sense(struct scsi_device *sdp, int dbd, int modepage,

   1319                  unsigned char *buffer, int len, struct scsi_mode_data *data,

   1320                  struct scsi_sense_hdr *sshdr)

   1321 {

   1322         return scsi_mode_sense(sdp, dbd, modepage, buffer, len,

   1323                                SD_TIMEOUT, SD_MAX_RETRIES, data,

   1324                                sshdr);

   1325 }

sd_do_mode_sense调用来自scsi核心层统一提供的scsi_mode_sense(),关于后者我们就不详细介绍了,总之执行之后,结果就是保存在了data,datastruct scsi_mode_data结构体变量.

     16 struct scsi_mode_data {

     17         __u32   length;

     18         __u16   block_descriptor_length;

     19         __u8    medium_type;

     20         __u8    device_specific;

     21         __u8    header_length;

     22         __u8    longlba:1;

     23 };

这里每一个成员都在scsi协议中能够找到对应物.就比如刚才说得medium_type,对于SCSI磁盘,它一定是00h.这是没得商量的.

我们最终是在1375行作的判断,1375,为啥判断device_specific0x80相与呢?SBC-2中有一幅图描述了这个Device Specific的玩意儿.

这里bit7叫做WP,Write Protect,写保护位.N年前当咱们刚开始用软盘的时候就听说了写保护,所以对这个概念我们并不陌生.如果这一位为1就说明设置了写保护,反之则是没有设置.如果没有设置写保护,那么在日志文件里我们就能看到类似下面这行的一句话:

Dec  6 08:47:05 localhost kernel: sdb: Write Protect is off

因此, sd_read_write_protect_flag这一个函数的流程就是:

1.      软件问:磁盘磁盘你设置了写保护吗?

2.      如果磁盘说:是的我设置了.

3.      软件打印: sdb: Write Protect is on

4.      如果磁盘说:,我没有设置.

5.      软件打印: sdb: Write Protect is off

最后说一下,1344,判断有没有设置use_192_bytes_for_3f,这是因为实践表明,很多磁盘只能接受MODE SENSEpage=0x3f时传输长度为192bytes,所以咱们在定义struct scsi_device的时候为这些设备准备了这么一个flag,scsi总线扫描设备初始化的时候就可以设置这么一个flag.相应的我们发送命令的时候就设置好192.

另一个1339,skip_ms_page_3f,这也是一个类似的flag,MODE SENSE命令有一个参数page,同样是实践表明,某些愚蠢的设备在page=0x3f的时候会出错.所以写代码的做出让步,又准备了一个flag.

如果你还不是很明白这个page是啥意思,那么让我们来看一下SPC-4MODE SENSE命令的格式是如何的.

首先是6字节的.

然后是10字节的.

这其中,PAGE CODE就是我们上面说的page,很明显,它一共占6bits.因此它的取值范围就是00h3Fh(11 1111).而我们上面说到3fh,就是说当你发送MODE SENSE命令的时候,设置PAGE CODE3fh的时候,因为3fh是最后一个page,很多设备都会有一些莫名其妙的错误,搞得我们很没面子,于是我们需要设置种种flag来处理这些情况.

当然,你可能还想知道为什么需要PAGE这么一个概念.Ok,其实这样来的,众所周知,开源社区有很多寂寞男,但是很少有女人,毕竟亚里士多德曾经说过:”女人做程序,既毁了女人,也毁了程序.”SCSI设计者作为同样是IT工作者,他们对开源社区的兄弟们也很同情,所以他们在设计SCSI的时候一直希望把对开源社区兄弟们美好的祝愿寄托在设备中,他们想,开源社区缺女人,而我们经常说,女人就象一本书,(当然了,胖女人就象一本辞海.除了必要时,没有愿意去翻她.),于是他们在设备内部保存了一本书,这本书就是设备的<<我的自白书>>,或者用更加时尚的话说,这本书就是设备的性感写真集,而你要阅读这本书,你就必须发送MODE SENSE命令,但是就像你读别的书一样,你必须一页一页的读,所以你需要给定一个PAGE CODE,或者说页码,同时我们看到Byte3叫做SUBPAGE CODE,这就是子页号码,你索性就理解为一页中某一个段落好了,即设备允许你一页一页的读,也允许你一段一段的读.很显然,由于SUBPAGE CODE8bits,因此其最大值就是255.即一个page可以有最多255subpage.