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

标签: Linux, int, 通信, io, tf, 存储设备, hdr, cdb, SG

相关文章推荐

添加新评论,含*的栏目为必填