Linux开发之存储设备通信

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);
}
上一篇:c#-可以优化的测试代码


下一篇:CodeGo.net>如何测试方法抛出特定输入异常