天天看點

scsi裝置驅動體系架構

1.6.2 scsi裝置驅動體系架構

從這一層開始,整個檔案讀寫的中心将由request轉向scsi的指令結構scsi_cmnd。那麼這個指令結構到底是怎麼一回事呢,這還得從SCSI架構談起。SCSI 實作了一種客戶機/伺服器風格的通信架構,發起者向目标裝置發送指令請求。該目标處理此請求并向發起者傳回響應。發起者可以是托管計算機中的一個 SCSI 裝置,而 SCSI 目标則可以是一個磁盤、CD光牒和錄音帶裝置或特殊裝置(比如箱體裝置)。

這裡要提到一個概念——Lower Level Device(LDD):在最低層的是一組驅動器,稱為 SCSI 低層驅動器。它們是一些可與實體裝置(比如 HBA)連結的特定驅動器。LLD 提供了自公共中間層到特定于裝置的 HBA 的一種抽象。每個 LLD 都提供了到特定底層硬體的接口,但所使用的到中間層的接口卻是一組标準接口。

較低層包含大量代碼,原因是它要負責處理各種不同的 SCSI 擴充卡類型。例如,Fibre Channel 協定包含了針對 Emulex 和 QLogic 的各種擴充卡的 LLD。面向 Adaptec 和 LSI 的 SAS 擴充卡的 LLD 也包括在内。

與存儲相關的 SCSI 指令一般是在 SCSI Architecture Model (SAM)、SCSI Primary Commands (SPC) 和 SCSI Block Commands (SBC) 中定義的:

l  SAM:定義SCSI 系統模型、SCSI 标準集的功能性分區,以及适用于所有 SCSI 實作和實作标準的需求。

l  SPC:定義:對所有 SCSI 裝置模型通用的行為。

l  SBC:定義指令集擴充,以友善操作 SCSI 直接通路塊裝置。

每個 SCSI 指令都由 Command Descriptor Block (CDB) 描述,它定義 SCSI 裝置執行的操作。SCSI 指令涉及到用于向 SCSI 裝置傳輸資料(或從中輸出資料)的資料指令,以及用于設定 SCSI 裝置的配置參數的非資料指令。

典型的CDB格式如下圖所示:

位 7 位 6 位 5 位 4 位 3 位 2 位 1 位 0
位元組 0 Operation code = 12h
位元組 1 LUN Reserved EVPD
位元組 2 Page code
位元組 3 Reserved
位元組 4 Allocation length
位元組 5 Control

如果 EVPD 參數位(用于啟用關鍵産品資料)為 0 并且 Page Code 參數位元組為 0,那麼目标将傳回标準指令資料(如inquiry)。如果 EVPD 參數為 1,那麼目标将傳回對應 page code 字段的特定于供應商的資料。

scsi_cmnd結構中的cmnd[MAX_COMMAND_SIZE]數組就是這個CDB的内容。我們看到這個數組雖然最大可有16個元素,每個元素1位元組,但是我們僅僅用了其中6個元素,用來存放CDB,其中cmnd[0]最為重要,對應scsi操作碼。所有的scsi操作碼都有定義,在include/scsi/scsi.h中:

#define TEST_UNIT_READY       0x00

#define REZERO_UNIT           0x01

#define REQUEST_SENSE         0x03

#define FORMAT_UNIT           0x04

#define READ_BLOCK_LIMITS     0x05

#define REASSIGN_BLOCKS       0x07

#define INITIALIZE_ELEMENT_STATUS 0x07

#define READ_6                0x08

#define WRITE_6               0x0a

#define SEEK_6                0x0b

#define READ_REVERSE          0x0f

#define WRITE_FILEMARKS       0x10

#define SPACE                 0x11

#define INQUIRY               0x12

#define RECOVER_BUFFERED_DATA 0x14

#define MODE_SELECT           0x15

#define RESERVE               0x16

#define RELEASE               0x17

#define COPY                  0x18

#define ERASE                 0x19

#define MODE_SENSE            0x1a

#define START_STOP            0x1b

#define RECEIVE_DIAGNOSTIC    0x1c

#define SEND_DIAGNOSTIC       0x1d

#define ALLOW_MEDIUM_REMOVAL  0x1e

#define SET_WINDOW            0x24

#define READ_CAPACITY         0x25

#define READ_10               0x28

#define WRITE_10              0x2a

#define SEEK_10               0x2b

#define POSITION_TO_ELEMENT   0x2b

#define WRITE_VERIFY          0x2e

#define VERIFY                0x2f

#define SEARCH_HIGH           0x30

#define SEARCH_EQUAL          0x31

#define SEARCH_LOW            0x32

#define SET_LIMITS            0x33

#define PRE_FETCH             0x34

#define READ_POSITION         0x34

#define SYNCHRONIZE_CACHE     0x35

#define LOCK_UNLOCK_CACHE     0x36

#define READ_DEFECT_DATA      0x37

#define MEDIUM_SCAN           0x38

#define COMPARE               0x39

#define COPY_VERIFY           0x3a

#define WRITE_BUFFER          0x3b

#define READ_BUFFER           0x3c

#define UPDATE_BLOCK          0x3d

#define READ_LONG             0x3e

#define WRITE_LONG            0x3f

#define CHANGE_DEFINITION     0x40

#define WRITE_SAME            0x41

#define READ_TOC              0x43

#define LOG_SELECT            0x4c

#define LOG_SENSE             0x4d

#define MODE_SELECT_10        0x55

#define RESERVE_10            0x56

#define RELEASE_10            0x57

#define MODE_SENSE_10         0x5a

#define PERSISTENT_RESERVE_IN 0x5e

#define PERSISTENT_RESERVE_OUT 0x5f

#define REPORT_LUNS           0xa0

#define MOVE_MEDIUM           0xa5

#define EXCHANGE_MEDIUM       0xa6

#define READ_12               0xa8

#define WRITE_12              0xaa

#define WRITE_VERIFY_12       0xae

#define SEARCH_HIGH_12        0xb0

#define SEARCH_EQUAL_12       0xb1

#define SEARCH_LOW_12         0xb2

#define READ_ELEMENT_STATUS   0xb8

#define SEND_VOLUME_TAG       0xb6

#define WRITE_LONG_2          0xea

#define READ_16               0x88

#define WRITE_16              0x8a

#define VERIFY_16            0x8f

#define SERVICE_ACTION_IN     0x9e

#define    SAI_READ_CAPACITY_16  0x10

#define    ATA_16                0x85   

#define    ATA_12                0xa1   

我們列出最常使用的指令:

指令 描述
INQUIRY 請求目标裝置的摘要資訊
TEST_UNIT_READY 檢測目标裝置是否準備好進行傳輸
READ_6 從 SCSI 目标裝置傳輸資料
WRITE_6 向 SCSI 目标裝置傳輸資料
REQUEST_SENSE 請求最後一個指令的檢測資料
READ_CAPACITY 獲得存儲容量資訊

所有 SCSI 指令都要以操作代碼的第一個位元組為開端,以表明它所代表的操作。并且所有 SCSI 指令都要包含一個控制位元組。這個位元組通常是該指令的最後一個位元組,用于表示與供應商相關的資訊等等。

scsi_cmnd 結構就完全是SCSI記錄的抽象,不僅cmnd數組字段記錄了指令描述塊 (CDB);還有用于感測資料緩存 (SENSE BUFFER)的sense_buffer字段,以及用于存放IO 逾時時間等 SCSI 相關的資訊和 SCSI 子系統處理指令需要的一些其他資訊的字段,如回調函數等。

來自include/scsi/scsi_cmnd.h:

struct scsi_cmnd {

       struct scsi_device *device;

       struct list_head list;  

       struct list_head eh_entry;

       int eh_eflags;        

       void (*done) (struct scsi_cmnd *);      

       unsigned long serial_number;

       unsigned long jiffies_at_alloc;

       int retries;

       int allowed;

       int timeout_per_command;

       unsigned char cmd_len;

       enum dma_data_direction sc_data_direction;

#define MAX_COMMAND_SIZE       16

       unsigned char cmnd[MAX_COMMAND_SIZE];

       unsigned request_bufflen;     

       struct timer_list eh_timeout; 

       void *request_buffer;           

       unsigned short use_sg;  

       unsigned short sglist_len;     

       unsigned underflow;     

       unsigned transfersize;    

       int resid;        

       struct request *request;

#define SCSI_SENSE_BUFFERSIZE        96

       unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];

       void (*scsi_done) (struct scsi_cmnd *);

       struct scsi_pointer SCp; 

       unsigned char *host_scribble;      

       int result;       

       unsigned char tag; 

       unsigned long pid; 

};

scsi裝置驅動體系架構