2016年4月19日 星期二

Linux SCSI Pass Through Interface

其實不難, 只是專案必需要知道每一個實體 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
 

  參考下面這幾篇
   T10 Working Drafts
   這個 T10很多檔案都已經改為付費購買. 其實只要只知道檔案名稱, 還是在Google搜尋下載得到.

   Seagate SCSI Commands Reference Manual
   Seagate SCSI Commands Reference Manual April 2010
   wiki SCSI command


   http://www.tldp.org/HOWTO/SCSI-Generic-HOWTO/
   Appendix C. Programming 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
  Linux LSI SAS 9211-8i Utility
  Source Code中一個 doScsiIo(), doMptCommand()  的函式
  利用 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

//===========================================



沒有留言:

張貼留言