命令 | 用途 |
---|---|
Test unit ready | 查询设备是否已经准备好进行传输 |
Inquiry | 请求设备基本信息 |
Request sense | 请求之前命令的错误信息 |
Read capacity | 请求存储容量信息 |
Read | 从设备读取数据 |
Write | 向设备写入数据 |
文件在:/usr/include/scsi/scsi.h
/*
* SCSI opcodes
*/
#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 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
/*
* Status codes
*/
#define GOOD 0x00
#define CHECK_CONDITION 0x01
#define CONDITION_GOOD 0x02
#define BUSY 0x04
#define INTERMEDIATE_GOOD 0x08
#define INTERMEDIATE_C_GOOD 0x0a
#define RESERVATION_CONFLICT 0x0c
#define COMMAND_TERMINATED 0x11
#define QUEUE_FULL 0x14
#define STATUS_MASK 0x3e
/*
* SENSE KEYS
*/
#define NO_SENSE 0x00
#define RECOVERED_ERROR 0x01
#define NOT_READY 0x02
#define MEDIUM_ERROR 0x03
#define HARDWARE_ERROR 0x04
#define ILLEGAL_REQUEST 0x05
#define UNIT_ATTENTION 0x06
#define DATA_PROTECT 0x07
#define BLANK_CHECK 0x08
#define COPY_ABORTED 0x0a
#define ABORTED_COMMAND 0x0b
#define VOLUME_OVERFLOW 0x0d
#define MISCOMPARE 0x0e
/*
* DEVICE TYPES
*/
#define TYPE_DISK 0x00
#define TYPE_TAPE 0x01
#define TYPE_PROCESSOR 0x03 /* HP scanners use this */
#define TYPE_WORM 0x04 /* Treated as ROM by our system */
#define TYPE_ROM 0x05
#define TYPE_SCANNER 0x06
#define TYPE_MOD 0x07 /* Magneto-optical disk -
* - treated as TYPE_DISK */
#define TYPE_MEDIUM_CHANGER 0x08
#define TYPE_ENCLOSURE 0x0d /* Enclosure Services Device */
#define TYPE_NO_LUN 0x7f
sg3_utils
sg3_utils 是Linux下用来直接使用 SCSI命令集访问设备.只要支持scsi命令集就可以使用,如FC/USB Storage/ATAPI/SAS/SATA/iscsi等设备, 也可以访问SATA兼容设备, 如:
sg_inq: 查询/dev/sdc的信息
sgdisk: 硬盘分区查看,设置等.
sg_dd/sg_pdd: 顺序读写硬盘
sg_read/sg_write/sg_read_buffer/sg_write_buffer: 读写硬盘
sginfo: 查看硬盘信息
sg_format: 格式化硬盘(低级格式化)
sg_log: 查看硬盘的log信息
sg_luns: 查看target上有多少个lun
sg_map: 查看硬盘映射情况, 查看硬盘所在的bus, chan, id,lun,type.
sgpio: 控制盘位的LED灯
sg_ses: scsi enclosure service控制, ses是对接机箱状态监控,设置等服务
sg_scan: 扫描系统中的硬盘
sg_raw: 可以直接发送scsi命令, 如sg_raw -r 1k /dev/sg0 12 00 00 00 60 00 是inquiry命令
smp_utils
smp_utils是linux下来访问sas的smp协议的辅助工具, 是SAS系统管理的重要工具.
udevadm
udev的控制管理命令 udevadm info -a -p /block/sda 可以查看sda设备相关信息
要查询某个SCSI设备(sg)对应的是那个块设备(sd)可以使用命令sg_map -i查询。
https://yalungdotblog.files.wordpress.com/2012/02/scsie5ad90e7b3bbe7bb9fe79fa5e8af86e680bbe7bb93.pdf
一个用户态程序通过ioctl发送SCSI命令访问SCSI设备(可以是sd设备也可以是一个sg设备)。
$cat scsi_sample.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SENSE_LEN 255
#define BLOCK_LEN 32
#define SCSI_TIMEOUT 20000
unsigned char sense_buffer[SENSE_LEN];
unsigned char data_buffer[BLOCK_LEN*256];
void show_hdr_outputs(struct sg_io_hdr * hdr) {
printf("status:%d\n", hdr->status);
printf("masked_status:%d\n", hdr->masked_status);
printf("msg_status:%d\n", hdr->msg_status);
printf("sb_len_wr:%d\n", hdr->sb_len_wr);
printf("host_status:%d\n", hdr->host_status);
printf("driver_status:%d\n", hdr->driver_status);
printf("resid:%d\n", hdr->resid);
printf("duration:%d\n", hdr->duration);
printf("info:%d\n", hdr->info);
}
void show_sense_buffer(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->sbp;
int i;
for (i=0; i<hdr->mx_sb_len; ++i) {
putchar(buffer[i]);
}
}
void show_vendor(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("the device type: %x \n",buffer[0]&(1<<5 -1));
//设备类型:0 磁盘 1磁带 12存储阵列(raid) 13拓展柜(expander)
printf("vendor id:");
for (i=8; i<16; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
void show_product(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("product id:");
for (i=16; i<32; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
void show_product_rev(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("product ver:");
for (i=32; i<36; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
/***************************************************************************
* name: execute_Inquiry
* parameter:
* fd: file descripter
* page_code: cdb page code
* evpd: cdb evpd
* p_hdr: poiter to sg_io_hdr struct
* function: make Inquiry cdb and execute it.
* **************************************************************************/
int execute_Inquiry(int fd, int page_code, int evpd, struct sg_io_hdr * p_hdr) {
unsigned char cdb[6];
/* set the cdb format */
cdb[0] = 0x12; /*This is for Inquery*/
cdb[1] = evpd & 1;
cdb[2] = page_code & 0xff;
cdb[3] = 0;
cdb[4] = 0xff;
cdb[5] = 0; /*For control filed, just use 0*/
p_hdr->dxfer_direction = SG_DXFER_FROM_DEV;
p_hdr->cmdp = cdb;
p_hdr->cmd_len = 6;
int ret = ioctl(fd, SG_IO, p_hdr);
if (ret<0) {
printf("Sending SCSI Command failed.\n");
close(fd);
exit(1);
}
return p_hdr->status;
}
void test_execute_Inquiry(char * path, int evpd, int page_code) {
int status = 0;
//初始化p_hdr
struct sg_io_hdr * p_hdr = (struct sg_io_hdr *)malloc(sizeof(struct sg_io_hdr));
memset(p_hdr, 0, sizeof(struct sg_io_hdr));
p_hdr->interface_id = 'S'; /* this is the only choice we have! */
p_hdr->flags = SG_FLAG_LUN_INHIBIT; /* this would put the LUN to 2nd byte of cdb*/
//设置传输数据buffer
p_hdr->dxferp = data_buffer; //INQUIRY 数据格式 SPC-3
p_hdr->dxfer_len = BLOCK_LEN*256;
//设置sense buffer
p_hdr->sbp = sense_buffer;
p_hdr->mx_sb_len = SENSE_LEN;
printf("the device name:%s \n",path);
int fd = open(path, O_RDWR);
if (fd>0) {
status = execute_Inquiry(fd, page_code, evpd, p_hdr);
if (status!=0) {
show_sense_buffer(p_hdr);
} else{
show_vendor(p_hdr);
show_product(p_hdr);
show_product_rev(p_hdr);
show_sense_buffer(p_hdr);
}
} else {
printf("failed to open sg file %s\n", path);
}
close(fd);
free(p_hdr);
}
int main(int argc, char * argv[]) {
test_execute_Inquiry(argv[1], 0, 0);
return EXIT_SUCCESS;
}
//编译
//gcc scsi_sample.c -o test -g
//./test /dev/nvme9n1
$sudo ./test /dev/nvme9n1
the device name:/dev/nvme9n1
the device type: 0
vendor id:NVMe
product id:INTEL SSDPE2MX02
product ver:01H0
这里借助一个结构体struct sg_io_hdr对象p_hdr通过ioctl从用户态发送命令到内核态
int ret = ioctl(fd, SG_IO, p_hdr);
typedef struct sg_io_hdr
{
int interface_id; /* [i] 'S' for SCSI generic (required) */
int dxfer_direction; /* [i] data transfer direction */
unsigned char cmd_len; /* [i] SCSI command length ( <= 16 bytes) */
unsigned char mx_sb_len; /* [i] max length to write to sbp */
unsigned short int iovec_count; /* [i] 0 implies no scatter gather */
unsigned int dxfer_len; /* [i] byte count of data transfer */
void * dxferp; /* [i], [*io] points to data transfer memory
or scatter gather list */
unsigned char * cmdp; /* [i], [*i] points to command to perform */
unsigned char * sbp; /* [i], [*o] points to sense_buffer memory */
unsigned int timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */
unsigned int flags; /* [i] 0 -> default, see SG_FLAG... */
int pack_id; /* [i->o] unused internally (normally) */
void * usr_ptr; /* [i->o] unused internally */
unsigned char status; /* [o] scsi status */
unsigned char masked_status;/* [o] shifted, masked scsi status */
unsigned char msg_status; /* [o] messaging level data (optional) */
unsigned char sb_len_wr; /* [o] byte count actually written to sbp */
unsigned short int host_status; /* [o] errors from host adapter */
unsigned short int driver_status;/* [o] errors from software driver */
int resid; /* [o] dxfer_len - actual_transferred */
unsigned int duration; /* [o] time taken by cmd (unit: millisec) */
unsigned int info; /* [o] auxiliary information */
} sg_io_hdr_t;
/*
interface_id:一般应该设置为 S。
dxfer_direction:用于确定数据传输的方向;常常使用以下值之一:
SG_DXFER_NONE:不需要传输数据。比如 SCSI Test Unit Ready 命令。
SG_DXFER_TO_DEV:将数据传输到设备。使用 SCSI WRITE 命令。
SG_DXFER_FROM_DEV:从设备输出数据。使用 SCSI READ 命令。
SG_DXFER_TO_FROM_DEV:双向传输数据。
SG_DXFER_UNKNOWN:数据的传输方向未知。
cmd_len:指向 SCSI 命令的 cmdp 的字节长度。
mx_sb_len:当 sense_buffer 为输出时,可以写回到 sbp 的最大大小。
dxfer_len:数据传输的用户内存的长度。
dxferp:指向数据传输时长度至少为 dxfer_len 字节的用户内存的指针。
cmdp:指向将要执行的 SCSI 命令的指针。
sbp:缓冲检测指针。
timeout:用于使特定命令超时。
status:由 SCSI 标准定义的 SCSI 状态字节*/
一个SCSI inquiry命令:
如果 EVPD 参数位(用于启用关键产品数据)为 0 并且 Page Code 参数字节为 0,那么目标将返回标准 inquiry 数据。如果 EVPD 参数为 1,那么目标将返回对应 page code 字段的特定于供应商的数据。
SCSI Inquiry Command(Page Code 和 EVPD 字段皆设置为 0)的标准响应很复杂。根据标准,供应商 ID 从第 8 字节扩展到第 15 字节,产品 ID 从第 16 字节扩展到第 31 字节,产品版本从第 32 字节扩展到第 35 字节。必须获取这些信息,以检查命令是否成功执行。