如何调用到sd_spinup_disk接口,请查看前面文章【深入浅出SCSI子系统(六)SCSI 磁盘驱动】
链接:深入浅出SCSI子系统(六)SCSI 磁盘驱动_sinat_37817094的博客-CSDN博客
函数sd_spinup_disk()代码(摘自文件drivers/scsi/sd.c)
sd_spinup_disk函数尝试等到SCSI磁盘已经准备好,必要时使磁盘转起来。前者发送TEST UNITREADY命令,后者发送START UNIT命令。
/*
* spinup disk - called only in sd_revalidate_disk()
*/
static void
sd_spinup_disk(struct scsi_disk *sdkp)
{
unsigned char cmd[10];
unsigned long spintime_expire = 0;
int retries, spintime;
unsigned int the_result;
struct scsi_sense_hdr sshdr;
int sense_valid = 0;
spintime = 0;
/* Spin up drives, as required. Only do this at boot time */
/* Spinup needs to be done for module loads too. */
do {
retries = 0;
do {
cmd[0] = TEST_UNIT_READY;
memset((void *) &cmd[1], 0, 9);
the_result = scsi_execute_req(sdkp->device, cmd,
DMA_NONE, NULL, 0,
&sshdr, SD_TIMEOUT,
SD_MAX_RETRIES, NULL);
/*
* If the drive has indicated to us that it
* doesn't have any media in it, don't bother
* with any more polling.
*/
if (media_not_present(sdkp, &sshdr))
return;
if (the_result)
sense_valid = scsi_sense_valid(&sshdr);
retries++;
} while (retries < 3 &&
(!scsi_status_is_good(the_result) ||
((driver_byte(the_result) & DRIVER_SENSE) &&
sense_valid && sshdr.sense_key == UNIT_ATTENTION)));
if ((driver_byte(the_result) & DRIVER_SENSE) == 0) {
/* no sense, TUR either succeeded or failed
* with a status error */
if(!spintime && !scsi_status_is_good(the_result)) {
sd_printk(KERN_NOTICE, sdkp, "Unit Not Ready\n");
sd_print_result(sdkp, the_result);
}
break;
}
/*
* The device does not want the automatic start to be issued.
*/
if (sdkp->device->no_start_on_add)
break;
if (sense_valid && sshdr.sense_key == NOT_READY) {
if (sshdr.asc == 4 && sshdr.ascq == 3)
break; /* manual intervention required */
if (sshdr.asc == 4 && sshdr.ascq == 0xb)
break; /* standby */
if (sshdr.asc == 4 && sshdr.ascq == 0xc)
break; /* unavailable */
/*
* Issue command to spin up drive when not ready
*/
if (!spintime) {
sd_printk(KERN_NOTICE, sdkp, "Spinning up disk...");
cmd[0] = START_STOP;
cmd[1] = 1; /* Return immediately */
memset((void *) &cmd[2], 0, 8);
cmd[4] = 1; /* Start spin cycle */
if (sdkp->device->start_stop_pwr_cond)
cmd[4] |= 1 << 4;
scsi_execute_req(sdkp->device, cmd, DMA_NONE,
NULL, 0, &sshdr,
SD_TIMEOUT, SD_MAX_RETRIES,
NULL);
spintime_expire = jiffies + 100 * HZ;
spintime = 1;
}
/* Wait 1 second for next try */
msleep(1000);
printk(".");
/*
* Wait for USB flash devices with slow firmware.
* Yes, this sense key/ASC combination shouldn't
* occur here. It's characteristic of these devices.
*/
} else if (sense_valid &&
sshdr.sense_key == UNIT_ATTENTION &&
sshdr.asc == 0x28) {
if (!spintime) {
spintime_expire = jiffies + 5 * HZ;
spintime = 1;
}
/* Wait 1 second for next try */
msleep(1000);
} else {
/* we don't understand the sense code, so it's
* probably pointless to loop */
if(!spintime) {
sd_printk(KERN_NOTICE, sdkp, "Unit Not Ready\n");
sd_print_sense_hdr(sdkp, &sshdr);
}
break;
}
} while (spintime && time_before_eq(jiffies, spintime_expire));
if (spintime) {
if (scsi_status_is_good(the_result))
printk("ready\n");
else
printk("not responding...\n");
}
}
这里看到两层循环,外层循环为下面代码,循环的目的为让SCSI磁盘“准备好”给予足够的等待时间。并不是所有情况都需要这样的循环,代码给了两种情况:
一种是在发送START_STOP命令之后,毕竟SCSI磁盘“转动起来”需要一段时间;
另一种针对某些USB闪存设备,它们的固件运行速度很慢。所以用到了局部变量spintime,初始值为0,在遇到上述两种情况下,设置为1。
//外循环
do
{
}} while (spintime && time_before_eq(jiffies, spintime_expire));
内层循环下面代码,发送TEST_UNIT_READY命令,如果已知磁盘介质不存在,情况就简单了,可以直接返回。否则给它三次机会以忽略Unit Attention。
do {
cmd[0] = TEST_UNIT_READY;
memset((void *) &cmd[1], 0, 9);
the_result = scsi_execute_req(sdkp->device, cmd,
DMA_NONE, NULL, 0,
&sshdr, SD_TIMEOUT,
SD_MAX_RETRIES, NULL);
/*
* If the drive has indicated to us that it
* doesn't have any media in it, don't bother
* with any more polling.
*/
if (media_not_present(sdkp, &sshdr))
return;
if (the_result)
sense_valid = scsi_sense_valid(&sshdr);
} while (retries < 3 &&
(!scsi_status_is_good(the_result) ||
((driver_byte(the_result) & DRIVER_SENSE) &&
sense_valid && sshdr.sense_key == UNIT_ATTENTION)));
内层循环退出,根据其结果,有三种情况可以直接退出外层循环:
(1)TEST_UNIT_READY成功,这是显而易见的;其他情况都表示设备还没有准备好,需要在外层循环进一步处理。
(2)因为处理必须给予感测数据,但我们拿不到有效的感测数据,前两种情况对应下面代码;
if ((driver_byte(the_result) & DRIVER_SENSE) == 0) {
/* no sense, TUR either succeeded or failedwith a status error */
if(!spintime && !scsi_status_is_good(the_result)) {
sd_printk(KERN_NOTICE, sdkp, "Unit Not Ready\n");
sd_print_result(sdkp, the_result);
}
break;
}
(3)所谓处理,是发送START_STOP命令,等待磁盘转动起来,但SCSI设备却指定了no_start_on_add域,不希望发送自动启动命令。
/*
* The device does not want the automatic start to be issued.
*/
if (sdkp->device->no_start_on_add)
break;
外层循环的剩余代码都是处理设备未准备好的情况的,又有以下三个分支:
• 如果感测数据为NOT_READY,向SCSI磁盘发送STARTUNIT命令,在此之前需要排除三种情况,这些都是通过附加感测码判断的:(1)需要手工介入,(2)磁盘正待机,(3)磁盘不可用。相应代码如下;
1 if (sense_valid && sshdr.sense_key == NOT_READY) {
2 if (sshdr.asc == 4 && sshdr.ascq == 3)
3 break; /* manual intervention required 需要手工介入*/
4 if (sshdr.asc == 4 && sshdr.ascq == 0xb)
5 break; /* standby 磁盘正待机*/
6 if (sshdr.asc == 4 && sshdr.ascq == 0xc)
7 break; /* unavailable 磁盘不可用*/
8 /*
9 * Issue command to spin up drive when not ready
10 */
11 if (!spintime) {
12 sd_printk(KERN_NOTICE, sdkp, "Spinning up disk...");
13 cmd[0] = START_STOP;
14 cmd[1] = 1; /* Return immediately */
15 memset((void *) &cmd[2], 0, 8);
16 cmd[4] = 1; /* Start spin cycle */
17 if (sdkp->device->start_stop_pwr_cond)
18 cmd[4] |= 1 << 4;
19 scsi_execute_req(sdkp->device, cmd, DMA_NONE,
20 NULL, 0, &sshdr,
21 SD_TIMEOUT, SD_MAX_RETRIES,
22 NULL);
23 spintime_expire = jiffies + 100 * HZ;
24 spintime = 1;
25 }
26 /* Wait 1 second for next try */
27 msleep(1000);
28 printk(".");
29
30 }
• 如果感测数据为UNIT_ATTENTION,并且附加感测码为0x28,说明可能是USB闪存设备的情况。它和上一种情况一样,都在spintime为0的情况下设置它,开始或继续外层循环。相应代码如下;
1 /*
2 * Wait for USB flash devices with slow firmware.
3 * Yes, this sense key/ASC combination shouldn't
4 * occur here. It's characteristic of these devices.
5 */
6 } else if (sense_valid &&
7 sshdr.sense_key == UNIT_ATTENTION &&
8 sshdr.asc == 0x28) {
9 if (!spintime) {
10 spintime_expire = jiffies + 5 * HZ;
11 spintime = 1;
12 }
13 /* Wait 1 second for next try */
14 msleep(1000);
15 }
• 其他的感测数据都是不支持的,退出外层循环。相应代码如下。
1 else {
2 /* we don't understand the sense code, so it's
3 * * probably pointless to loop */
4 if(!spintime) {
5 sd_printk(KERN_NOTICE, sdkp, "Unit Not Ready\n");
6 sd_print_sense_hdr(sdkp, &sshdr);
7 }
8 break;
9 }
执行scsi命令。调用scsi_execute_req()这个函数算是scsi核心层提供的,在include/scsi/scsi_device.h中能找到它的声明:
static inline int scsi_execute_req(struct scsi_device *sdev,
const unsigned char *cmd, int data_direction, void *buffer,
unsigned bufflen, struct scsi_sense_hdr *sshdr, int timeout,
int retries, int *resid)
{
return scsi_execute_req_flags(sdev, cmd, data_direction, buffer,
bufflen, sshdr, timeout, retries, resid, 0);
}
unsigned char cmd[10];
unsigned int the_result;
do {
cmd[0] = TEST_UNIT_READY;
memset((void *) &cmd[1], 0, 9);
the_result = scsi_execute_req(sdkp->device, cmd,
DMA_NONE, NULL, 0,
&sshdr, SD_TIMEOUT,
SD_MAX_RETRIES, NULL);
/*
* If the drive has indicated to us that it
* doesn't have any media in it, don't bother
* with any more polling.
*/
if (media_not_present(sdkp, &sshdr))
return;
if (the_result)
sense_valid = scsi_sense_valid(&sshdr);
retries++;
} while (retries < 3 &&
(!scsi_status_is_good(the_result) ||
((driver_byte(the_result) & DRIVER_SENSE) &&
sense_valid && sshdr.sense_key == UNIT_ATTENTION)));
执行一个scsi命令,第一个参数struct scsi_device的结构体指针,第二个参数则命令cmd,就是command.申请的一个unsigned char类型的数组,总共10个元素,赋了值为TEST_UNIT_READY.Test Unit Ready就是一个很基本的SCSI命令.DMA_NONE代表传输方向,buffer和bufflen咱们用不上,因为这个命令就是测试设备准备好了没有,不需要传递什么数据。调用scsi_execute_req()以执行这个Test Unit Ready命令,返回的结果基本上都是好的,除非设备真的有毛病。
sg_turs这个命令就是用来手工发送Test Unit Ready。安装sg3_utils系列软件包。rpm -qa | grep sg3_utils
unsigned int the_result
执行完命令之后,我们用the_result记录下结果,调用scsi_status_is_good()来判断结果.关于scsi_status_is_good()以及和它相关的一些宏定义于include/scsi/scsi.h文件中:
/*
* SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft
* T10/1561-D Revision 4 Draft dated 7th November 2002.
*/
#define SAM_STAT_GOOD 0x00
#define SAM_STAT_CHECK_CONDITION 0x02
#define SAM_STAT_CONDITION_MET 0x04
#define SAM_STAT_BUSY 0x08
#define SAM_STAT_INTERMEDIATE 0x10
#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14
#define SAM_STAT_RESERVATION_CONFLICT 0x18
#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */
#define SAM_STAT_TASK_SET_FULL 0x28
#define SAM_STAT_ACA_ACTIVE 0x30
#define SAM_STAT_TASK_ABORTED 0x40
/** scsi_status_is_good - check the status return.
*
* @status: the status passed up from the driver (including host and
* driver components)
*
* This returns true for known good conditions that may be treated as
* command completed normally
*/
static inline int scsi_status_is_good(int status)
{
/*
* FIXME: bit0 is listed as reserved in SCSI-2, but is
* significant in SCSI-3. For now, we follow the SCSI-2
* behaviour and ignore reserved bits.
*/
status &= 0xfe;
return ((status == SAM_STAT_GOOD) ||
(status == SAM_STAT_INTERMEDIATE) ||
(status == SAM_STAT_INTERMEDIATE_CONDITION_MET) ||
/* FIXME: this is obsolete in SAM-3 */
(status == SAM_STAT_COMMAND_TERMINATED));
}
the_result和状态码还是有区别的,毕竟状态码只有那么多,用8位来表示足够了,而the_result我们看到是unsigned int,显然它不只是8位,于是我们就充分利用资源,因此就有了下面这些宏:
/*
* Use these to separate status msg and our bytes
*
* These are set by:
*
* status byte = set from target device
* msg_byte = return status from host adapter itself.
* host_byte = set by low-level driver to indicate status.
* driver_byte = set by mid-level.
*/
#define status_byte(result) (((result) >> 1) & 0x7f)
#define msg_byte(result) (((result) >> 8) & 0xff)
#define host_byte(result) (((result) >> 16) & 0xff)
#define driver_byte(result) (((result) >> 24) & 0xff)
#define sense_class(sense) (((sense) >> 4) & 0x7)
#define sense_error(sense) ((sense) & 0xf)
#define sense_valid(sense) ((sense) & 0x80)
/*
* These indicate the error that occurred, and what is available.
*/
#define DRIVER_BUSY 0x01
#define DRIVER_SOFT 0x02
#define DRIVER_MEDIA 0x03
#define DRIVER_ERROR 0x04
#define DRIVER_INVALID 0x05
#define DRIVER_TIMEOUT 0x06
#define DRIVER_HARD 0x07
#define DRIVER_SENSE 0x08
最低的byte是作为status byte用,
driver_byte,即bit23到bit31,这8位被用来承载mid-level设置的信息.而这里用它和DRIVER_SENSE相与,则判断的是是否有sense data,scsi世界里的sense data就是错误信息.这里do-while循环就是如果不成功就最多重复三次,循环结束了之后,再次判断有没有sense data,如果没有,则说明也许成功了.
if ((driver_byte(the_result) & DRIVER_SENSE) == 0) {
/* no sense, TUR either succeeded or failed
* with a status error */
if(!spintime && !scsi_status_is_good(the_result)) {
sd_printk(KERN_NOTICE, sdkp, "Unit Not Ready\n");
sd_print_result(sdkp, the_result);
}
break;
}
Scsi子系统在于错误判断的代码特别的多.而针对sense data的处理则是错误判断的一部分
/*
* This is a slightly modified SCSI sense "descriptor" format header.
* The addition is to allow the 0x70 and 0x71 response codes. The idea
* is to place the salient data from either "fixed" or "descriptor" sense
* format into one structure to ease application processing.
*
* The original sense buffer should be kept around for those cases
* in which more information is required (e.g. the LBA of a MEDIUM ERROR).
*/
struct scsi_sense_hdr { /* See SPC-3 section 4.5 */
u8 response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */
u8 sense_key;
u8 asc;
u8 ascq;
u8 byte4;
u8 byte5;
u8 byte6;
u8 additional_length; /* always 0 for fixed sense format */
};
static inline int scsi_sense_valid(struct scsi_sense_hdr *sshdr)
{
if (!sshdr)
return 0;
return (sshdr->response_code & 0x70) == 0x70;
}
这里定义的struct scsi_sense_hdr就是被用来描述一个sense data.”hdr”就是header的意思,因为sense data可能长度比较长,但是其前8个bytes是最重要的,所以这部分被叫做header,或者说头部,大多数情况下只要理睬头部就够了.
我们看函数scsi_execute_req()中第六个参数是struct scsi_sense_hdr *sshdr,如果命令执行出错了,那么sense data就会通过这个参数返回.所以咱们定义了sshdr,然后咱们通过判断它和它的各个成员,来决定下一步.
在sense data中,最基本的一个元素叫做response_code,它相当于为一个sense data定了性,即它属于哪一个类别,因为sense data毕竟有很多种.response code总共就是8个bits,目前使用的值只有70h,71h,72h,73h,其它的像00h到6Fh以及74h到7Eh这些都是保留的,以备将来之用.所以这里判断的就是response code得0x70,0x71,0x72,0x73才是valid,否则就是invalid.这就是scsi_sense_valid()做的事情.
关于sense data,在SCSI Primary Commands(SPC-3)的4.5节Sense data,专门介绍Sense Data的.Sense data中最有意义的东西叫做sense key和sense code.这两个概念基本上确定了你这个错误究竟是什么错误.
do {
} while (retries < 3 &&
(!scsi_status_is_good(the_result) ||
((driver_byte(the_result) & DRIVER_SENSE) &&
sense_valid && sshdr.sense_key == UNIT_ATTENTION)));
我们判断sshdr的sense_key是不是等于UNIT_ATTENTION,这个信息表示这个设备可能被重置了或者可移动的介质发生了变化,或者更通俗一点说,只要设备发生了一些变化,然后它希望引起主机控制器的关注,比如说设备原本是on-line的,突然变成了off-line,或者反过来,设备从off-line回到了on-line.在正式读写设备之前,如果有UNIT_ATTENTION条件,必须把它给清除掉.而这(清除UNIT ATTENTION)也正是Test Unit Ready的工作之一.
if (sense_valid && sshdr.sense_key == NOT_READY) {
if (sshdr.asc == 4 && sshdr.ascq == 3)
break; /* manual intervention required */
if (sshdr.asc == 4 && sshdr.ascq == 0xb)
break; /* standby */
if (sshdr.asc == 4 && sshdr.ascq == 0xc)
break; /* unavailable */
}
而如果sense key等于NOT_READY,则表明这个logical unit不能被访问.(NOT READY: Indicates that the logical unit is not accessible.)而如果sense key等于NOT READY,而asc等于04h,ascq等于03h,这表明”Logical Unit Not Ready,Manual Intervention required”.(详见SPC-4,附录D部分)这说明需要人工干预.
unsigned char cmd[10];
if (sense_valid && sshdr.sense_key == NOT_READY) {
if (sshdr.asc == 4 && sshdr.ascq == 3)
break; /* manual intervention required */
if (sshdr.asc == 4 && sshdr.ascq == 0xb)
break; /* standby */
if (sshdr.asc == 4 && sshdr.ascq == 0xc)
break; /* unavailable */
/*
* Issue command to spin up drive when not ready
*/
if (!spintime) {
sd_printk(KERN_NOTICE, sdkp, "Spinning up disk...");
cmd[0] = START_STOP;
cmd[1] = 1; /* Return immediately */
memset((void *) &cmd[2], 0, 8);
cmd[4] = 1; /* Start spin cycle */
if (sdkp->device->start_stop_pwr_cond)
cmd[4] |= 1 << 4;
scsi_execute_req(sdkp->device, cmd, DMA_NONE,
NULL, 0, &sshdr,
SD_TIMEOUT, SD_MAX_RETRIES,
NULL);
spintime_expire = jiffies + 100 * HZ;
spintime = 1;
}
/* Wait 1 second for next try */
msleep(1000);
printk(".");
}
如果磁盘确实应该是NOT_READY,于是我们需要发送下一个命令,即START STOP,在SCSI Block Commands-2(SBC-2)的书中,5.17节专门介绍了START STOP UNIT这个命令.这个命令简而言之,就相当于电源开关,SBC-2中Table 48给出了这个命令的格式:
结合代码看,咱们把cmd[4]设置为1,实际上就等于是把这张图里的START位设置为1.而在SBC-2中,这个START位的含义如下:
很明显,这就是真正的电源开关.因此前面再次调用scsi_execute_req以执行START STOP UNIT命令,就是真正的让硬盘转起来。
do{
if (sense_valid && sshdr.sense_key == NOT_READY) {
if (!spintime) {
spintime_expire = jiffies + 100 * HZ;
spintime = 1;
}
/* Wait 1 second for next try */
msleep(1000);
printk(".");
}
} while (spintime && time_before_eq(jiffies, spintime_expire));
最外的循环,延时设置100s,如果在期间内,硬盘还是NOT_READY状态,会延时1s。最后还是转不起来就退出。
退出后
if (spintime) {
if (scsi_status_is_good(the_result))
printk("ready\n");
else
printk("not responding...\n");
}
printk打印not responding,没有把硬盘转起来