最近使用smartctl對usb硬碟擷取smart資訊識別。所以就仔細查了看原因。同時又記起一起使用hdparm對硬碟休眠時,usb硬碟也是有問題,使用了什麼pass through方法?那時沒有深入的去瞭解,這次正好仔細看了看代碼和相關問題。關鍵字
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 控制器發送,只要根據已知資料就可以填充傳回值。
ata_scsi_translate
其功能就是根據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硬碟
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發現有下面的內容:
USB devices and smartmontools
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:
- The USB bridge provides an ATA pass-through command.
- This command is supported by smartmontools.
- The operating system provides a SCSI pass-through I/O-control which works through its USB-layer.
- SCSI support is implemented in the operating system interface of smartmontools.
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 |
usb晶片並不是將所有的scsi命令都按照SAT轉換ATA的。所以有些命令是不支援的。目前usb儲存晶片:1:支援標準SAT,使用ATA_12/162:支援自己的特殊命令如ATACB,類似於ATA_12/16,比如cypress公司的晶片。在linux-2.6.28中的cypress-atacb.c中就是第二中方案:Support for emulating SAT (ata pass through) on devices based on the Cypress USB/ATA bridge supporting ATACB. 完成該功能的函數是cypress_atacb_passthrough。 目前可以說hdparm和smartctl對usb硬碟不工作的原因是不同的。如果使用sdparm usb就會可以。但是smartctl是需要晶片支援的。所以在最新的smartctl中使用-d後面加參數。就是根據各自的晶片,填寫標準ATA_16或者vendor spec comand。 可以在smartctl的首頁中尋找支援哪些usb晶片。
usb晶片是否支援SAT或者特殊的如ATACB,需要看datasheet