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);}