hdparm、smartctl、ATA/ATAPI command set、scsi command set、SAT、ATACB
1:为什么smartctl对usb设备会失败?能否有解决的方法?
2:hdparm对usb失败后,采取的方法能否对smartctl是否有效?
因为是用smartctl工具对usb硬盘失效的,所以就使用strace调试smartctl到底使用哪些ioctl。
ioctl(3, SG_IO, {'S', SG_DXFER_FROM_DEV, cmd[16]=[85, 08, 0e, 00, 00, 00
查找内核代码发现,0x85是ATA_16是个ata pass through命令。
sata硬盘将scsi命令转换成ata命令的函数是:ata_scsi_queuecmd--->__ata_scsi_queuecmd
该函数遵循的标准是SAT(scsi to ata translate spec),即如何将scsi命令转成ata命令。
在这个函数中发现:真正转成ata命令后,发送到sata控制器是使用:ata_scsi_translate;
而使用已有的数据来模拟scsi命令的是ata_scsi_simulate。
这里必须说明一点的是,这两个函数都是SAT标准的一部分,只是一些scsi命令的结果不需要再向sata 控制器发送,只要根据已知数据就可以填充返回值。
其功能就是根据scsi命令找到合适的转换函数。即ata_get_xlat_func根据scsi命令返回合适的函数。
static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd)
{
switch (cmd) {
case READ_6:
case READ_10:
case READ_16:
case WRITE_6:
case WRITE_10:
case WRITE_16:
return ata_scsi_rw_xlat;
case SYNCHRONIZE_CACHE:
if (ata_try_flush_cache(dev))
return ata_scsi_flush_xlat;
break;
case VERIFY:
case VERIFY_16:
return ata_scsi_verify_xlat;
case ATA_12:
case ATA_16:
return ata_scsi_pass_thru;
case START_STOP:/*power management*/
return ata_scsi_start_stop_xlat;
}
return NULL;
}
通过这个函数可以看出,真正装换ata命令的基本都是读写函数。
但是ata command set命令很多,比如:smart相关、power management、hpa相关。但是上面的函数均没有处理。那么这些函数该如何处理的?这就迁出了SAT spec中最重要的两个命令:ATA_12、ATA_16.
这两个命令就是告诉SATL,scsi命令集cdb中包含不是scsi command,而是真正的ata 命令,SATL只需要将其完整的发送ATA 设备即可。转换的函数是ata_scsi_pass_thru。
以上就是一个简单的sata硬盘接收命令的过程。
这里又有一个问题:如果用户程序例如smartctl发送的scsi命令不是ATA_12/16,而是直接的smart相关ata命令,那么是不是SATL就无法处理?是的如果仍然使用SG_IO,发送的是标准ATA command这是错误。使用SG_IO,只能发送SAT spec支持的命令.否则就会被丢弃。
但是我使用strace hdparm -y /dev/sda时发现hdparm使用的不是SG_IO,命令是标准的ATA命令。那么它们是如何实现的?
ioctl(3, 0x31f, 0x7fffadc97e10) = 0
查内核代码0x31f=HDIO_DRIVE_CMD。ioctl的执行情况:sd_ioctl--》scsi_cmd_ioctl---》scsi_ioctl--》sdev->host->hostt->ioctl
而scsi host的ioctl是ata_scsi_ioctl---》ata_cmd_ioctl。该函数的功能是将标准ATA命令,按照SAT SPEC转换成ATA_16命令后,生成request插入request 队列,回到了ata_scsi_queuecmd。
这样虽然两个工具的ioctl不同,但是在内核中最终走到一起。
我们这里先解决问题2,即hdparm对usb设备失败的原因:usb存储的scsi host template是:struct scsi_host_template usb_stor_host_template。这个数据没有ioctl,所以0x31f命令是没有办法执行的,所以在scsi_ioctl函数中找不到任何可以执行的函数,就只能return -EINVAL;
如果我们将hdparm发送的命令改成SG_IO方式。就不会出错了。下面是改后的代码:
int dm_start_hdardisk_standby(const char * disk_name)
{
int fd = 0;
struct sg_io_hdr io_hdr;
unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0}; /*cdb*/
unsigned char sense_b[SENSE_BUFF_LEN];
int time_secs;
if (disk_name == NULL){
printf("%s(%d):parameter error:%s/n", __FUNCTION__, __LINE__, disk_name);
return DM_ERR_PARAM;
}
fd = open(disk_name, O_RDONLY);
if (fd < 0){
printf("%s(%d): open %s error(%s)l/n", __FUNCTION__, __LINE__, disk_name, strerror(errno));
return DM_EOPEN;
}
if ( system("sync") != 0 )
{
printf("%s(%d): system(sync): fail(%s)/n", __FUNCTION__, __LINE__,strerror(errno));
}
ssuBlk[4] = 0;/*from active to standby*/
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
io_hdr.interface_id = 'S';
io_hdr.dxfer_direction = SG_DXFER_NONE;
io_hdr.cmdp = ssuBlk;
io_hdr.cmd_len = START_STOP_CMDLEN;
memset(sense_b, 0, SENSE_BUFF_LEN);
io_hdr.sbp = sense_b;
io_hdr.mx_sb_len = SENSE_BUFF_LEN;
io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
if (ioctl(fd, SG_IO, &io_hdr) < 0){
printf("ioctl error(%s)/n", strerror(errno));
close(fd);
return DM_EIOCTL;
}
使用SG_IO命令,command是标准的scsi命令,在ata_get_xlat_func中有对START_STOP的处理。
但是这仅仅是对正常的sata硬盘而言,那么对usb硬盘会这样吗?
usb storage spec中说明了usb存储中的command set。
static int get_protocol(struct us_data *us)
{
switch (us->subclass) {
case US_SC_RBC:
us->protocol_name = "Reduced Block Commands (RBC)";
us->proto_handler = usb_stor_transparent_scsi_command;
break;
case US_SC_8020:
us->protocol_name = "8020i";
us->proto_handler = usb_stor_ATAPI_command;
us->max_lun = 0;
break;
case US_SC_QIC:
us->protocol_name = "QIC-157";
us->proto_handler = usb_stor_qic157_command;
us->max_lun = 0;
break;
case US_SC_8070:
us->protocol_name = "8070i";
us->proto_handler = usb_stor_ATAPI_command;
us->max_lun = 0;
break;
case US_SC_SCSI:
us->protocol_name = "Transparent SCSI";
us->proto_handler = usb_stor_transparent_scsi_command;
break;
case US_SC_UFI:
us->protocol_name = "Uniform Floppy Interface (UFI)";
us->proto_handler = usb_stor_ufi_command;
break;
这是根据usb 控制器芯片的subclass来选择合适的命令集,我们一般见到的都是US_SC_SCSI。至少我没有见过其他类型的。这说明命令集是scsi,即在驱动中并不转成ata command,这个转换是在usb芯片中完成的。
但是我们使用smartctl时是失败,那么这是什么原因?查看smartctl发现有下面的内容:
To access USB storage devices, the operating system sends SCSI commands through the USB transport to the device. If the USB device is actually a PATA or SATA disk in an USB enclosure, the firmware of its USB bridge chip translates these commands into the corresponding ATA commands. This works straightforward for read and write commands, but not for SMART commands.
To access SMART functionality, smartmontools must be able to send ATA commands directly to the disk. For USB devices, at least the following conditions must be met:
Some recent USB bridges already support the vendor independent SAT (SCSI/ATA Translation, ANSI INCITS 431-2007) standard. Other USB bridges provide vendor specific ATA pass-through commands. The current version of smartmontools supports the following pass-through commands and USB bridges:
Command | USB bridges | smartctl option | 48-bit ATAsupport | Comment |
SAT ATApass-through 12 and 16 | various (Initio, Oxford, ...) | -d sat[,16]; -d sat,12 | requires '-d sat[,16]' | Older Linux kernels may require '-d sat,12' |
Cypress ATACB | Cypress CY7C68300B/C (AT2LP), CY7C68310 (ISD-300LP) | -d usbcypress[,CMD] | No | CY7C68300A (AT2) may not work. |
JMicron ATApass-through | JMicron JM20329, JM20335-39 | -d usbjmicron[,x][,PORT] | No:JM20327, Yes:JM20336/9 | '-d usbjmicron,x' enables 48-bit support |
Sunplus ATApass-through | Sunplus SPIF215/6, SPIF225/6 | -d usbsunplus | Yes |
Smartmontools was successfully tested with many USB devices on several Platforms. This will never work on MacOS X unless someone adds SCSI pass-through support to this OS. If the USB ID can be obtained from the operating system, smartmontools also supports auto-detection of (the already tested) USB devices. Then it is not necessary to specify the '-d' option. See the following table for details:
Platform | ...has SCSIpass-through | smartmontools supports SCSI | ... supports USB | ... auto-detection | ... in smartd DEVICESCAN | Comment |
Linux | Yes | Yes | YES | YES | YES | See comment about SAT in above table |
MacOS X | No | No | No | No | No | USB works with e.g. XP in a VM |
FreeBSD | Yes | Yes | YES | YES | YES | |
NetBSD | Yes | Yes | YES | No | No | |
OpenBSD | Yes | Yes | YES | No | No | |
Solaris | Yes | Yes | YES | No | No | |
QNX | ? | No | No | No | No | |
OS/2 | ? | No | No | No | No | |
Windows NT/2K/XP/Vista | Yes | Yes | YES | YES | No | Auto-detection is slow due to the use of 'wmic.exe' |
Windows 9x/ME | Yes | Yes | YES | No | No | Requires ASPI driver |