其實不難, 只是專案必需要知道每一個實體 PHY Port 是連結到 Linux 中那一個 /dev/sgn 中
原來是直接用 /dev/sdN , 但發現這對以逅的專案會有影響, 所以就改用/dev/sgN 方式
而且習慣看 SCSI Sense Code, 除錯也比較容易.
過去十幾年來都在 Windows 和 NIOS-II 下對 Device直接下 SCSI Command
終於換到 Linux 平台來.
Some examples of storage device names of the supported operating systems
//===========================================
方式一:
一般都是利用 sg3-utils核心
# apt-get install sg3-utils
也就是開啟 "/dev/sgN" 方法
http://sg3-utils.sourcearchive.com/
sg3_utils-1.32
Example
ioctrl-using-scsi-pass-through
linux scsi generic howto學習筆記
typedef struct sg_io_hdr
{
int interface_id; /* [i] 'S' for SCSI generic (required) */
int dxfer_direction; /* [i] data transfer direction */
SG_DXFER_NONE /* e.g. a SCSI Test Unit Ready command */
SG_DXFER_TO_DEV /* e.g. a SCSI WRITE command */
SG_DXFER_FROM_DEV /* e.g. a SCSI READ command */
TestUnitReady => SG_DXFER_NONE
ReadCapacity10 => SG_DXFER_FROM_DEV
Read10 => SG_DXFER_FROM_DEV
Write10 => SG_DXFER_TO_DEV
unsigned char cmd_len; /* [i] SCSI command length ( <= 16 bytes) */
ReadCapacity10 => cmd_len = 10
ReadCapacity16 => cmd_len = 16
Read10 => cmd_len = 10
Write10 => cmd_len = 10
Write16 => cmd_len = 16
...
} sg_io_hdr_t;
//===========================================
使用 SG_IO 出現 ENOMEM
表示要求傳輸的記憶體太大
經測試只傳輸到 512byte * 9181sector
一呼叫 512byte * 9182sector 就會發生 ENOMEM
經查 Google 這是Linux的
核心限制
scsi sg ioctl要求向
blk-mq 取得一塊和要求傳輸大小的記憶體區塊失敗.
對 Storage一次使用大區塊讀取, 並不一定會比較穩定快速
因為不同的 Storage, 其晶片的特性不同
讀取太大塊, 速度反而會變慢, 變錯.
這個數字是多少, 就依賴經驗值, 必竟Storage的種類範圍太廣.
下面只是參考, 別太認真.
void ReadWrite_Test()
{
int nI, nJ;
TLBA nReadLba, nWriteLba;
DWORD nSectorSize, nReadLen, nWriteLen;
DWORD nReadCrc32, nWriteCrc32, nWriteSectorCrc32;
DWORD nReadBufLen, nWriteBufLen;
UCHAR *pReadBuf = new UCHAR[16*1024*1024];
UCHAR *pWriteBuf = new UCHAR[16*1024*1024];
nSectorSize = 512;
nWriteLba = 0;
nWriteLen = 8192;
memset( pWriteBuf, 0, 16*1024*1024);
/* 初始化 Writer Data Buffer */
for( nI =0; nI < nWriteLen; nI++)
{
/* 中間填入 序號Index */
for( nJ = 4; nJ < nSectorSize-sizeof(DWORD); nJ++)
pWriteBuf[nI*nSectorSize+nJ] = nJ%255;
/* 前4個Byte 是 Sector Index */
pWriteBuf[nI*nSectorSize] = (UCHAR)((nWriteLba+nI) >> 24); // MSB
pWriteBuf[nI*nSectorSize+1] = (UCHAR)((nWriteLba+nI) >> 16);
pWriteBuf[nI*nSectorSize+2] = (UCHAR)((nWriteLba+nI) >> 8);
pWriteBuf[nI*nSectorSize+3] = (UCHAR)((nWriteLba+nI)); // LSB
nWriteSectorCrc32 = Get_CRC32( pWriteBuf+(nI*nSectorSize), nSectorSize-sizeof(DWORD));
/* 後 2個Byte 是 Sector Index */
pWriteBuf[nI*nSectorSize+nSectorSize-sizeof(DWORD)] = (UCHAR)(nWriteSectorCrc32 >> 24); // MSB
pWriteBuf[nI*nSectorSize+nSectorSize-sizeof(DWORD)+1] = (UCHAR)(nWriteSectorCrc32 >> 16);
pWriteBuf[nI*nSectorSize+nSectorSize-sizeof(DWORD)+2] = (UCHAR)(nWriteSectorCrc32 >> 8);
pWriteBuf[nI*nSectorSize+nSectorSize-sizeof(DWORD)+3] = (UCHAR)(nWriteSectorCrc32); // LSB
}
nWriteBufLen = nSectorSize*nWriteLen;
/* 整個 Write Data Buffer 的 CRC-32 */
nWriteCrc32 = Get_CRC32( pWriteBuf, nWriteBufLen);
if( Write16( nWriteLba, nSectorSize, nWriteLen, pWriteBuf) == true)
{
memset( pReadBuf, 0, 4096);
memset( pReadBuf, 0, 16*1024*1024);
nReadLen = nWriteLen;
nReadLba = nWriteLba;
nReadBufLen = nSectorSize * nReadLen;
if( Read16( nReadLba, nSectorSize, nReadLen, pReadBuf))
{
/* 驗證讀回來的資料是否正確 */
nReadCrc32 = Get_CRC32( pReadBuf, nReadBufLen);
if( nReadCrc32 != nWriteCrc32)
nprintf( "Read/Write Test CRAC-32 Failed!");
if( memcmp( pReadBuf, pWriteBuf, nReadBufLen) == 0)
nprintf( "Read/Write Test Compare Ok!");
else
nprintf( "Read/Write Test Compare Failed!");
}
}
// Write12( nWriteLba, nSectorSize, nWriteLen, pWriteBuf);
// Write16( nWriteLba, nSectorSize, nWriteLen, pWriteBuf);
if( pReadBuf)
delete[] pReadBuf;
if( pWriteBuf)
delete[] pWriteBuf;
}
//===========================================
# lsscsi
[0:0:0:0] disk SEAGATE ST3500414SS 0006 /dev/sdb
[0:0:1:0] disk SEAGATE ST3500414SS 0006 /dev/sdc
[1:0:0:0] disk ATA ST1000DM003-9YN1 CC4B /dev/sda
# lsscsi --transport
[6:0:0:0] disk sas:0x5000c500418fac39 /dev/sdb
[6:0:1:0] disk sas:0x5000c50041ea5265 /dev/sdc
/sys/class/scsi_generic/sg2/device/block/sdc
/sys/class/scsi_generic/sg2/device/scsi_device/6:0:1:0
/sys/class/scsi_generic/sg2/device/scsi_device/6:0:1:0/sas_address
可以交叉驗證, 就可以知道 /dev/sg3 是那一顆HDD
要注意 Hot-Plug 的狀態下, Target 會改變, /sys/class/scsi_generic/sgN的目錄會自動消失, 也會自動出現.
所以用udevadm 來監控 (但監控這幾個目錄也是可以, 而且比較簡單)
How To Compiler udev
$ apt-get install sg3-utils
$ sg_scan -i -x
//===========================================
sg3_utils-1.32
Example
如果是 SATA HDD交叉驗證, 請看另一篇取得 SATA PHY Port Index 的相關文章
//===========================================
關於超過 2TB 容量的 Device
請使用 ReadCapacity16, Read16, Write16 這幾組指令
ReadCapacity10 有的會直接失敗, 有的 Device會回傳四個 0xFF
這時就轉換為 ReadCapacity16指令
ReadCapacity回傳的 Capacity是最後一個 LBA位置
所以實際容量為 Capacity + 1
還有一種情況是造假的
Capacity, 也就是回傳的 Capacity都是假的
這時要實際的去 Write & Read Last LBA, 進行資料驗證
記得有一次, 遇到一個
Device, 一次 Read/Write 16MB, 回傳都是正常
讀寫的資料只有前面 4MB 資料是正確, 後面 12MB資料都是晶片亂編出來.
其實最常見的是, 寫入時, Device回傳 Write OK, 但確沒有真正的寫入.
//===========================================
//===========================================
方式二:
使用
/dev/bsg/1:0:0:0
http://sg.danny.cz/sg/p/libsgutils2-2_1.42-0.1_i386.deb
http://www.spinics.net/lists/linux-scsi/msg53920.html
sg_tst_context
//===========================================
//===========================================
方式三:
這方法只適用 LSI 控制卡上的 Device
利用 LSI 驅動程式提供的 mpt_ioctl_command
直接對 LSI CHIP 下達 SCSI Command
LSI Util 原始程式是一個 SCSI Command就 Open Device Handle一次
速度會比較慢, 最好要改寫一些程式.
//===========================================
關於 Linux 下直接使用 ATA Command
Linux ioctl command for bypassing ATA command(Identify) -
Sample Code
這是直接使用
ioctl(device, HDIO_DRIVE_CMD, buf);
真是方便, 以前在 NIOS 中, 程式寫了一大堆程式才做出來.
但還是建議使用 Hdparm 的 sgio方式
另一個是請查詢 hdparm 的原始程式
https://sourceforge.net/projects/hdparm/
hdparm.c / sgio.c / sgio.h
//===========================================