1. 简介
早期的存储设备是SCSI接口,与计算通信也是基于SCSI协议。后来USB接口存储设备、SATA接口、PCIe接口的存储设备,也都是兼容SCSI协议的。所以利用SCSI传输协议可以与所有的存储设备通信。
2. 开源软件
● sg_raw,可以设置任何SCSI命令。
// 写,从文件data.bin读取数据写入设备(SG1581)
sg_raw -s 1k -i data.bin /dev/sde a2 00 00 00 00 00 00 02
// 读
sg_raw -r 1k /dev/dse a3 00 00 00 00 00 00 02
● hdparm,磁盘性能测试软件,不支持NVMe协议,只支持SATA磁盘测试。
3. 基础
ioctl是linux中与设备驱动进行通信的I/O通道函数。
int ioctl(int fd, unsigned long request, …);
● fd是通过open函数打开设备文件返回的文件标识符。
● request是设备请求控制码,控制码有系统提供的,也可以用户自定义。
● …是扩展参数,配合控制码做输入输出用,最多1个参数。
4. SCSI通信方式
4.1. 传统方式
在Linux 内核版本2.6之前,系统提供SCSI_IOCTL_SEND_COMMAND控制码与存储设备通信。
示例:
struct scsi_ioctl_command
{
unsigned int inlen; /* _excluding_ scsi command length */
unsigned int outlen;
uint8_t data[0];
};
void TestOldIoctl()
{
int nFd = open("/dev/sde", O_RDONLY);
uint8_t cbd[16] = {INQUIRY, 0, 0, 0, 0x20, 0};
uint8_t arrBuff[1024] = {0};
scsi_ioctl_command *sicp = (scsi_ioctl_command *)arrBuff;
sicp->outlen = 32;
memcpy(sicp->data, cbd, 16);
int nRes = ioctl(nFd, SCSI_IOCTL_SEND_COMMAND, arrBuff);
close(nFd);
}
4.2. SG_IO方式
在Linux 内核版本2.6之后,系统提供SG_IO控制码与存储设备通信,更方便,功能更全面。
示例:
void TestInquiry(int fd)
{
unsigned char buff[1024] = {0};
unsigned char inq_cmd[] = {INQUIRY, 1, 0x80, 0, 32, 0};
unsigned char sense[32] = {0};
sg_io_hdr io_hdr = {};
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof(inq_cmd);
io_hdr.dxferp = buff;
io_hdr.dxfer_len = 32;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof(sense);
io_hdr.timeout = 5000;
ioctl(fd, SG_IO, &io_hdr);
}
void TestRead(int fd)
{
unsigned char buff[1024] = {0};
unsigned char inq_cmd[] = {READ_10, 00, 0, 0, 0, 0, 0, 0, 0x2, 0};
unsigned char sense[32] = {0};
struct sg_io_hdr io_hdr = {};
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof(inq_cmd);
io_hdr.dxferp = buff;
io_hdr.dxfer_len = 32;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof(sense);
io_hdr.timeout = 5000;
ioctl(fd, SG_IO, &io_hdr);
}
void TestWrite(int fd)
{
unsigned char buff[1024] = {0};
unsigned char inq_cmd[] = {WRITE_10, 00, 0, 0, 0, 0, 0, 0, 0x2, 0};
unsigned char sense[32] = {0};
struct sg_io_hdr io_hdr = {};
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof(inq_cmd);
io_hdr.dxferp = buff;
io_hdr.dxfer_len = 32;
io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof(sense);
io_hdr.timeout = 5000;
ioctl(fd, SG_IO, &io_hdr);
}
5. ATA通信
5.1. 方式1
传统的存储设备,默认使用SCSI协议让计算机与存储设备建立通信。SATA设备,默认情况下使用此方式通信。SATA设备,有一些新的命令,如TRIM指令,SCSI中并没有相应的命令,此时需要使用0xA1命令包装ATA命令,下发给对应的设备驱动,设备驱动然后从SCSI命令包中解析出ATA命令,再发给SATA设备。
int sg16 (int fd, int rw, int dma, struct ata_tf *tf,
void *data, unsigned int data_bytes, unsigned int timeout_secs)
{
unsigned char cdb[SG_ATA_16_LEN];
unsigned char sb[32], *desc;
struct scsi_sg_io_hdr io_hdr;
int prefer12 = prefer_ata12, demanded_sense = 0;
if (tf->command == ATA_OP_PIDENTIFY)
prefer12 = 0;
memset(&cdb, 0, sizeof(cdb));
memset(&sb, 0, sizeof(sb));
memset(&io_hdr, 0, sizeof(struct scsi_sg_io_hdr));
if (data && data_bytes && !rw)
memset(data, 0, data_bytes);
if (dma) {
//cdb[1] = data ? (rw ? SG_ATA_PROTO_UDMA_OUT : SG_ATA_PROTO_UDMA_IN) : SG_ATA_PROTO_NON_DATA;
cdb[1] = data ? SG_ATA_PROTO_DMA : SG_ATA_PROTO_NON_DATA;
} else {
cdb[1] = data ? (rw ? SG_ATA_PROTO_PIO_OUT : SG_ATA_PROTO_PIO_IN) : SG_ATA_PROTO_NON_DATA;
}
/* libata/AHCI workaround: don't demand sense data for IDENTIFY commands */
if (data) {
cdb[2] |= SG_CDB2_TLEN_NSECT | SG_CDB2_TLEN_SECTORS;
cdb[2] |= rw ? SG_CDB2_TDIR_TO_DEV : SG_CDB2_TDIR_FROM_DEV;
} else {
cdb[2] = SG_CDB2_CHECK_COND;
}
if (!prefer12 || tf->is_lba48) {
cdb[ 0] = SG_ATA_16;
cdb[ 4] = tf->lob.feat;
cdb[ 6] = tf->lob.nsect;
cdb[ 8] = tf->lob.lbal;
cdb[10] = tf->lob.lbam;
cdb[12] = tf->lob.lbah;
cdb[13] = tf->dev;
cdb[14] = tf->command;
if (tf->is_lba48) {
cdb[ 1] |= SG_ATA_LBA48;
cdb[ 3] = tf->hob.feat;
cdb[ 5] = tf->hob.nsect;
cdb[ 7] = tf->hob.lbal;
cdb[ 9] = tf->hob.lbam;
cdb[11] = tf->hob.lbah;
}
io_hdr.cmd_len = SG_ATA_16_LEN;
} else {
cdb[ 0] = SG_ATA_12;
cdb[ 3] = tf->lob.feat;
cdb[ 4] = tf->lob.nsect;
cdb[ 5] = tf->lob.lbal;
cdb[ 6] = tf->lob.lbam;
cdb[ 7] = tf->lob.lbah;
cdb[ 8] = tf->dev;
cdb[ 9] = tf->command;
io_hdr.cmd_len = SG_ATA_12_LEN;
}
io_hdr.interface_id = 'S';
io_hdr.mx_sb_len = sizeof(sb);
io_hdr.dxfer_direction = data ? (rw ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV) : SG_DXFER_NONE;
io_hdr.dxfer_len = data ? data_bytes : 0;
io_hdr.dxferp = data;
io_hdr.cmdp = cdb;
io_hdr.sbp = sb;
io_hdr.pack_id = tf_to_lba(tf);
io_hdr.timeout = (timeout_secs ? timeout_secs : default_timeout_secs) * 1000; /* msecs */
if (ioctl(fd, SG_IO, &io_hdr) == -1) {
return -1; /* SG_IO not supported */
}
}
5.2. 方式2
直接使用ATA方式通信。
// Read相关
void Test(int fd)
{
uint8_t buff[512] = {0xEC, 0, 0, 1, 0};
int res = ioctl(fd, HDIO_DRIVE_CMD, buff);
}
// Write相关
void TestWrite(int fd)
{
uint8_t buff[512] = {0xF8, 0, 0, 0, 0, 0, 0, 0x40};
int res = ioctl(fd, HDIO_DRIVE_TASK, buff);
}