1.6.2 scsi设备驱动体系架构
从这一层开始,整个文件读写的中心将由request转向scsi的命令结构scsi_cmnd。那么这个命令结构到底是怎么一回事呢,这还得从SCSI架构谈起。SCSI 实现了一种客户机/服务器风格的通信架构,发起者向目标设备发送命令请求。该目标处理此请求并向发起者返回响应。发起者可以是托管计算机中的一个 SCSI 设备,而 SCSI 目标则可以是一个磁盘、光盘和磁带设备或特殊设备(比如箱体设备)。
这里要提到一个概念——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 /* values for service action in */ #define SAI_READ_CAPACITY_16 0x10
/* Values for T10/04-262r7 */ #define ATA_16 0x85 /* 16-byte pass-thru */ #define ATA_12 0xa1 /* 12-byte pass-thru */ |
我们列出最常使用的命令:
命令 | 描述 |
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; /* scsi_cmnd participates in queue lists */ struct list_head eh_entry; /* entry for the host eh_cmd_q */ int eh_eflags; /* Used by error handlr */ void (*done) (struct scsi_cmnd *); /* Mid-level done function */
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;
/* These elements define the operation we are about to perform */ #define MAX_COMMAND_SIZE 16 unsigned char cmnd[MAX_COMMAND_SIZE]; unsigned request_bufflen; /* Actual request size */
struct timer_list eh_timeout; /* Used to time out the command. */ void *request_buffer; /* Actual requested buffer */
/* These elements define the operation we ultimately want to perform */ unsigned short use_sg; /* Number of pieces of scatter-gather */ unsigned short sglist_len; /* size of malloc'd scatter-gather list */
unsigned underflow;
unsigned transfersize;
int resid;
struct request *request;
#define SCSI_SENSE_BUFFERSIZE 96 unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE]; /* obtained by REQUEST SENSE when * CHECK CONDITION is received on original * command (auto-sense) */
void (*scsi_done) (struct scsi_cmnd *);
struct scsi_pointer SCp; /* Scratchpad used by some host adapters */
unsigned char *host_scribble;
int result; /* Status code from lower level driver */
unsigned char tag; /* SCSI-II queued command tag */ unsigned long pid; /* Process ID, starts at 0. Unique per host. */ }; |