第二座大山,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中,而data是struct 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_specific和0x80相与呢?SBC-2中有一幅图描述了这个Device Specific的玩意儿.
<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_i1027" style="WIDTH: 415.5pt; HEIGHT: 63pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/JASON_~1/LOCALS~1/Temp/msohtml1/01/clip_image001.emz"></imagedata></shape>
这里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 SENSE在page=0x3f时传输长度为192bytes,所以咱们在定义struct scsi_device的时候为这些设备准备了这么一个flag,在scsi总线扫描设备初始化的时候就可以设置这么一个flag.相应的我们发送命令的时候就设置好192.
另一个1339行,skip_ms_page_3f,这也是一个类似的flag,MODE SENSE命令有一个参数page,同样是实践表明,某些愚蠢的设备在page=0x3f的时候会出错.所以写代码的做出让步,又准备了一个flag.
如果你还不是很明白这个page是啥意思,那么让我们来看一下SPC-4中MODE SENSE命令的格式是如何的.
首先是6字节的.
<shape id="_x0000_i1026" style="WIDTH: 414.75pt; HEIGHT: 132pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/JASON_~1/LOCALS~1/Temp/msohtml1/01/clip_image003.emz"></imagedata></shape>
然后是10字节的.
<shape id="_x0000_i1025" style="WIDTH: 414.75pt; HEIGHT: 177pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/JASON_~1/LOCALS~1/Temp/msohtml1/01/clip_image005.emz"></imagedata></shape>
这其中,PAGE CODE就是我们上面说的page,很明显,它一共占6个bits.因此它的取值范围就是00h到3Fh(即11 1111).而我们上面说到3fh,就是说当你发送MODE SENSE命令的时候,设置PAGE CODE为3fh的时候,因为3fh是最后一个page,很多设备都会有一些莫名其妙的错误,搞得我们很没面子,于是我们需要设置种种flag来处理这些情况.
当然,你可能还想知道为什么需要PAGE这么一个概念.Ok,其实这样来的,众所周知,开源社区有很多寂寞男,但是很少有女人,毕竟亚里士多德曾经说过:”女人做程序,既毁了女人,也毁了程序.”而SCSI设计者作为同样是IT工作者,他们对开源社区的兄弟们也很同情,所以他们在设计SCSI的时候一直希望把对开源社区兄弟们美好的祝愿寄托在设备中,他们想,开源社区缺女人,而我们经常说,女人就象一本书,(当然了,胖女人就象一本辞海.除了必要时,没有愿意去翻她.),于是他们在设备内部保存了一本书,这本书就是设备的<<我的自白书>>,或者用更加时尚的话说,这本书就是设备的性感写真集,而你要阅读这本书,你就必须发送MODE SENSE命令,但是就像你读别的书一样,你必须一页一页的读,所以你需要给定一个PAGE CODE,或者说页码,同时我们看到Byte3叫做SUBPAGE CODE,这就是子页号码,你索性就理解为一页中某一个段落好了,即设备允许你一页一页的读,也允许你一段一段的读.很显然,由于SUBPAGE CODE是8个bits,因此其最大值就是255.即一个page可以有最多255个subpage.