常用SCSI数据结构的详细注释和用法

1. SCSI命令块包装器 (CBW) - USB存储设备通信核心

cpp 复制代码
// SCSI Command Block Wrapper (CBW)
// 用途:在USB大容量存储协议中封装SCSI命令,用于发起数据传输请求
#pragma pack(push, 1)  // 确保结构体按1字节对齐,避免编译器插入填充字节
typedef struct {
    // 签名,必须为0x43425355 ('USBC' in little-endian)
    // 验证数据包的合法性
    uint32_t dCBWSignature;
    
    // 命令标签,主机生成的唯一标识符
    // 用于匹配CBW和对应的CSW响应
    uint32_t dCBWTag;
    
    // 本次命令要传输的数据总长度(字节数)
    // 0表示无数据传输,比如TEST UNIT READY命令
    uint32_t dCBWDataTransferLength;
    
    // 传输方向标志位
    // Bit 7: 0=主机到设备(OUT/写),1=设备到主机(IN/读)
    // Bit 6-0: 保留,必须为0
    uint8_t bmCBWFlags;
    
    // 逻辑单元号(LUN),指定目标设备中的逻辑单元
    // 对于单LUN设备通常为0
    uint8_t bCBWLUN;
    
    // CBWCB字段的有效长度(字节数),1-16
    // 表示后面CBWCB数组中实际包含的命令字节数
    uint8_t bCBWCBLength;
    
    // SCSI命令块,包含实际的SCSI命令
    // 第一个字节是操作码,后面是参数
    uint8_t CBWCB[16];
} ScsiCBW;
#pragma pack(pop)  // 恢复之前的对齐设置

实际用法:

cpp 复制代码
// 创建一个INQUIRY命令的CBW
ScsiCBW cbw;
cbw.dCBWSignature = 0x43425355;  // 'USBC'
cbw.dCBWTag = 0x00000001;        // 唯一标签
cbw.dCBWDataTransferLength = 36;  // INQUIRY返回36字节数据
cbw.bmCBWFlags = 0x80;            // 设备到主机(读操作)
cbw.bCBWLUN = 0;                  // 第一个LUN
cbw.bCBWCBLength = 6;             // INQUIRY命令长度6字节

// 设置INQUIRY命令 (0x12是INQUIRY操作码)
cbw.CBWCB[0] = 0x12;              // INQUIRY opcode
cbw.CBWCB[1] = 0x00;              // EVPD=0, CmdDt=0
cbw.CBWCB[2] = 0x00;              // Page code
cbw.CBWCB[3] = 0x00;              // Allocation length MSB
cbw.CBWCB[4] = 36;                // Allocation length LSB (36字节)
cbw.CBWCB[5] = 0x00;              // Control

2. SCSI命令状态包装器 (CSW) - 命令执行结果

cpp 复制代码
// SCSI Command Status Wrapper (CSW)
// 用途:设备在完成CBW处理后返回的状态信息
#pragma pack(push, 1)
typedef struct {
    // 签名,必须为0x53425355 ('USBS' in little-endian)
    uint32_t dCSWSignature;
    
    // 必须与对应CBW中的dCBWTag相同
    // 用于主机确认这是哪个命令的响应
    uint32_t dCSWTag;
    
    // 未传输的数据字节数
    // dCBWDataTransferLength - 实际传输的字节数
    // 0表示所有数据已成功传输
    uint32_t dCSWDataResidue;
    
    // 命令执行状态:
    // 0x00 = 命令通过 (PASSED)
    // 0x01 = 命令失败 (FAILED)
    // 0x02 = 阶段错误 (PHASE ERROR)
    // 其他值保留
    uint8_t bCSWStatus;
} ScsiCSW;
#pragma pack(pop)

实际用法示例

cpp 复制代码
// 检查CSW状态
ScsiCSW csw;
if (csw.dCSWSignature != 0x53425355) {
    // 签名错误,数据包损坏
    handleError("Invalid CSW signature");
}

if (csw.dCSWTag != expectedTag) {
    // 标签不匹配,可能是乱序响应
    handleError("Tag mismatch");
}

switch (csw.bCSWStatus) {
    case 0x00:  // PASSED
        printf("Command completed successfully\n");
        break;
    case 0x01:  // FAILED
        printf("Command failed. Residue: %u bytes\n", csw.dCSWDataResidue);
        // 此时应发送REQUEST SENSE获取详细错误信息
        break;
    case 0x02:  // PHASE ERROR
        printf("Phase error occurred\n");
        break;
    default:
        printf("Unknown status: 0x%02X\n", csw.bCSWStatus);
}

3. SCSI查询数据 (INQUIRY) - 设备识别信息

cpp 复制代码
// SCSI Inquiry Data Structure
// 用途:响应INQUIRY命令,提供设备基本信息
#pragma pack(push, 1)
typedef struct {
    // 外设限定符 (3 bits):描述设备状态
    // 0x00 = 设备连接且支持此LUN
    // 0x01 = 设备连接但不支持此LUN
    // 0x03 = 无法提供设备信息
    // 0x07 = 无设备连接
    uint8_t PeripheralQualifier : 3;
    
    // 外设设备类型 (5 bits):SCSI设备类型代码
    // 0x00 = 直接访问设备 (磁盘)
    // 0x01 = 顺序访问设备 (磁带)
    // 0x05 = CD/DVD设备
    // 0x07 = 光存储设备
    // 0x0E = 精简块设备 (RBC)
    // 0x0F = 无LUN映射
    uint8_t PeripheralDeviceType : 5;
    
    // 可移动介质位:1=可移动,0=不可移动
    uint8_t RMB : 1;
    
    uint8_t Reserved1 : 7;
    
    // SCSI标准版本:
    // 0x00 = 无标准遵循
    // 0x03 = SPC-2 (SCSI Primary Commands-2)
    // 0x04 = SPC-3
    // 0x05 = SPC-4
    // 0x06 = SPC-5
    uint8_t Version;
    
    // 响应数据格式:应为0x02 (遵循SPC标准)
    uint8_t ResponseDataFormat;
    
    // 附加数据长度:后续数据的字节数
    uint8_t AdditionalLength;
    
    // 以下为SCSI支持的功能标志位:
    uint8_t SCCS : 1;    // 1=支持SCC(存储控制器命令)
    uint8_t ACC : 1;     // 1=支持寻址异常控制
    uint8_t TPGS : 2;    // 目标端口组支持:00=无,01=隐式,10=显式,11=两者
    uint8_t ThreePC : 1; // 1=支持第三方复制
    uint8_t Protect : 1; // 1=支持保护信息
    uint8_t BQue : 1;    // 1=支持基本队列
    uint8_t EncServ : 1; // 1=支持加密服务
    
    uint8_t MultiP : 1;  // 1=支持多端口
    uint8_t MChngr : 1;  // 1=介质更换器存在
    uint8_t Obsolete : 2; // 已废弃
    uint8_t Addr16 : 1;  // 1=支持16位SCSI地址
    uint8_t Obsolete2 : 2;
    uint8_t WBus16 : 1;  // 1=支持16位宽总线
    uint8_t Sync : 1;    // 1=支持同步传输
    uint8_t Linked : 1;  // 1=支持链接命令
    uint8_t CmdQue : 1;  // 1=支持命令队列
    uint8_t SftRe : 1;   // 1=支持软重置
    
    // 厂商标识 (8字节),ASCII码,右对齐,空格填充
    // 如:"TOSHIBA "、"SEAGATE "
    uint8_t VendorIdentification[8];
    
    // 产品标识 (16字节),ASCII码,左对齐,空格填充
    // 如:"MK3252GSX       "
    uint8_t ProductIdentification[16];
    
    // 产品修订版本 (4字节),ASCII码,左对齐,空格填充
    // 如:"FH00"
    uint8_t ProductRevisionLevel[4];
} ScsiInquiryData;
#pragma pack(pop)

实际用法示例

cpp 复制代码
ScsiInquiryData inquiryData;
// 解析设备类型
const char* deviceTypeStr;
switch (inquiryData.PeripheralDeviceType) {
    case 0x00: deviceTypeStr = "Direct-access block device (disk)"; break;
    case 0x05: deviceTypeStr = "CD/DVD device"; break;
    case 0x07: deviceTypeStr = "Optical memory device"; break;
    case 0x0E: deviceTypeStr = "Reduced block device (RBC)"; break;
    default:   deviceTypeStr = "Unknown device type"; break;
}

printf("Device Type: %s (0x%02X)\n", deviceTypeStr, inquiryData.PeripheralDeviceType);
printf("Removable: %s\n", inquiryData.RMB ? "Yes" : "No");

// 提取字符串信息(需要去除填充的空格)
char vendor[9] = {0};
char product[17] = {0};
char revision[5] = {0};

memcpy(vendor, inquiryData.VendorIdentification, 8);
memcpy(product, inquiryData.ProductIdentification, 16);
memcpy(revision, inquiryData.ProductRevisionLevel, 4);

// 去除尾部空格
vendor[8] = product[16] = revision[4] = '\0';
trimTrailingSpaces(vendor);
trimTrailingSpaces(product);
trimTrailingSpaces(revision);

printf("Vendor: %s\n", vendor);
printf("Product: %s\n", product);
printf("Revision: %s\n", revision);

4. SCSI读取容量数据 - 设备容量信息

cpp 复制代码
// SCSI Read Capacity Data Structure
// 用途:响应READ CAPACITY命令,报告设备大小
#pragma pack(push, 1)
typedef struct {
    // 最后一个逻辑块的地址(LBA)
    // 设备容量 = (LogicalBlockAddress + 1) 个逻辑块
    // 例如:如果值为0x0000FFFF,表示有0x00010000个逻辑块
    uint32_t LogicalBlockAddress;
    
    // 每个逻辑块的字节数
    // 通常是512, 1024, 2048, 4096等值
    // 与LogicalBlockAddress一起计算总容量
    uint32_t BlockLengthInBytes;
} ScsiReadCapacityData;
#pragma pack(pop)

实际用法示例

cpp 复制代码
ScsiReadCapacityData capacityData;
// 计算设备总容量
uint64_t totalBlocks = (uint64_t)capacityData.LogicalBlockAddress + 1;
uint64_t totalBytes = totalBlocks * capacityData.BlockLengthInBytes;

printf("Last LBA: 0x%08X (%u)\n", 
       capacityData.LogicalBlockAddress, capacityData.LogicalBlockAddress);
printf("Block Size: %u bytes\n", capacityData.BlockLengthInBytes);
printf("Total Capacity: %llu bytes\n", totalBytes);

// 转换为更友好的显示格式
const char* sizeUnit;
double displaySize;

if (totalBytes >= 1099511627776LL) {  // 1 TiB
    displaySize = totalBytes / 1099511627776.0;
    sizeUnit = "TiB";
} else if (totalBytes >= 1073741824) {  // 1 GiB
    displaySize = totalBytes / 1073741824.0;
    sizeUnit = "GiB";
} else if (totalBytes >= 1048576) {  // 1 MiB
    displaySize = totalBytes / 1048576.0;
    sizeUnit = "MiB";
} else {
    displaySize = totalBytes / 1024.0;
    sizeUnit = "KiB";
}

printf("Formatted: %.2f %s\n", displaySize, sizeUnit);

5. SCSI感知数据 - 错误详细信息

cpp 复制代码
// SCSI Sense Data Structure
// 用途:响应REQUEST SENSE命令,提供命令失败的具体原因
#pragma pack(push, 1)
typedef struct {
    // 响应代码 (高4位):
    // 0x70 = 当前错误 (fixed格式)
    // 0x71 = 可纠正错误 (fixed格式)
    // 0x72 = 当前错误 (descriptor格式)
    // 0x73 = 可纠正错误 (descriptor格式)
    uint8_t ResponseCode;
    
    uint8_t Obsolete;  // 已废弃,通常为0
    
    // 感知键 (4 bits):主要的错误类别
    // 0x00 = NO SENSE (无错误)
    // 0x01 = RECOVERED ERROR (已恢复错误)
    // 0x02 = NOT READY (设备未就绪)
    // 0x03 = MEDIUM ERROR (介质错误)
    // 0x04 = HARDWARE ERROR (硬件错误)
    // 0x05 = ILLEGAL REQUEST (非法请求)
    // 0x06 = UNIT ATTENTION (单元注意)
    // 0x07 = DATA PROTECT (数据保护)
    // 0x08 = BLANK CHECK (空白检查)
    // 0x09 = VENDOR SPECIFIC (厂商特定)
    // 0x0A = COPY ABORTED (复制中止)
    // 0x0B = ABORTED COMMAND (命令中止)
    // 0x0C = EQUAL (相等)
    // 0x0D = VOLUME OVERFLOW (卷溢出)
    // 0x0E = MISCOMPARE (不匹配)
    // 0x0F = RESERVED (保留)
    uint8_t SenseKey : 4;
    
    uint8_t Reserved1 : 1;
    
    // ILI (Incorrect Length Indicator):长度不正确指示
    // 1=请求的长度与实际传输长度不符
    uint8_t ILI : 1;
    
    // EOM (End Of Medium):介质结束
    uint8_t EOM : 1;
    
    // File Mark:文件标记
    uint8_t FileMark : 1;
    
    // 信息字段:与错误相关的附加信息
    uint8_t Information[4];
    
    // 附加感知长度:后续字节数
    uint8_t AdditionalSenseLength;
    
    // 命令特定信息:与具体命令相关的信息
    uint8_t CommandSpecificInformation[4];
    
    // 附加感知代码 (ASC):详细错误代码
    // 与SenseKey一起精确定位错误
    // 例如:0x3A = MEDIUM NOT PRESENT (介质不存在)
    uint8_t AdditionalSenseCode;
    
    // 附加感知代码限定符 (ASCQ):ASC的进一步限定
    uint8_t AdditionalSenseCodeQualifier;
    
    // 可更换单元代码:标识发生错误的部件
    uint8_t FieldReplaceableUnitCode;
    
    // 感知键特定信息:SenseKey相关的附加信息
    uint8_t SenseKeySpecific[3];
} ScsiSenseData;
#pragma pack(pop)

实际用法示例

cpp 复制代码
ScsiSenseData senseData;
// 解析感知键
const char* senseKeyStr;
switch (senseData.SenseKey) {
    case 0x00: senseKeyStr = "NO SENSE"; break;
    case 0x01: senseKeyStr = "RECOVERED ERROR"; break;
    case 0x02: senseKeyStr = "NOT READY"; break;
    case 0x03: senseKeyStr = "MEDIUM ERROR"; break;
    case 0x04: senseKeyStr = "HARDWARE ERROR"; break;
    case 0x05: senseKeyStr = "ILLEGAL REQUEST"; break;
    case 0x06: senseKeyStr = "UNIT ATTENTION"; break;
    case 0x07: senseKeyStr = "DATA PROTECT"; break;
    case 0x08: senseKeyStr = "BLANK CHECK"; break;
    case 0x0B: senseKeyStr = "ABORTED COMMAND"; break;
    default:   senseKeyStr = "Unknown sense key"; break;
}

printf("Sense Key: 0x%01X (%s)\n", senseData.SenseKey, senseKeyStr);
printf("ASC: 0x%02X, ASCQ: 0x%02X\n", 
       senseData.AdditionalSenseCode, 
       senseData.AdditionalSenseCodeQualifier);

// 检查特定错误
if (senseData.SenseKey == 0x02 &&  // NOT READY
    senseData.AdditionalSenseCode == 0x3A) {  // MEDIUM NOT PRESENT
    printf("Error: Medium not present - please insert media\n");
} else if (senseData.SenseKey == 0x05 &&  // ILLEGAL REQUEST
           senseData.AdditionalSenseCode == 0x20) {  // INVALID COMMAND OPERATION CODE
    printf("Error: Invalid command - unsupported operation\n");
}

6. SCSI命令结构 - 通用命令表示

cpp 复制代码
// SCSI Command Structure
// 用途:通用SCSI命令表示,可用于构建各种操作
typedef struct {
    uint8_t opcode;           // 操作码,决定命令类型
    uint8_t flags;            // 命令特定标志
    uint32_t lba;             // 逻辑块地址(起始位置)
    uint16_t transfer_length; // 传输的块数或字节数
    uint8_t control;          // 控制字节
} ScsiCommand;

常见操作码示例

cpp 复制代码
// 常用SCSI操作码
#define SCSI_OP_TEST_UNIT_READY   0x00
#define SCSI_OP_REQUEST_SENSE     0x03
#define SCSI_OP_INQUIRY           0x12
#define SCSI_OP_MODE_SELECT       0x55
#define SCSI_OP_MODE_SENSE        0x5A
#define SCSI_OP_READ_6            0x08  // 6字节命令格式
#define SCSI_OP_READ_10           0x28  // 10字节命令格式
#define SCSI_OP_READ_12           0xA8  // 12字节命令格式
#define SCSI_OP_READ_16           0x88  // 16字节命令格式
#define SCSI_OP_WRITE_6           0x0A
#define SCSI_OP_WRITE_10          0x2A
#define SCSI_OP_WRITE_12          0xAA
#define SCSI_OP_WRITE_16          0x8A
#define SCSI_OP_READ_CAPACITY     0x25  // READ CAPACITY (10)
#define SCSI_OP_READ_CAPACITY_16  0x9E  // READ CAPACITY (16)

// 构建READ(10)命令示例
ScsiCommand buildReadCommand(uint32_t startLBA, uint16_t blockCount) {
    ScsiCommand cmd;
    cmd.opcode = 0x28;  // READ(10)
    cmd.flags = 0x00;   // 通常为0
    cmd.lba = startLBA;
    cmd.transfer_length = blockCount;
    cmd.control = 0x00; // 控制字节
    return cmd;
}

// 构建WRITE(10)命令示例
ScsiCommand buildWriteCommand(uint32_t startLBA, uint16_t blockCount) {
    ScsiCommand cmd;
    cmd.opcode = 0x2A;  // WRITE(10)
    cmd.flags = 0x00;
    cmd.lba = startLBA;
    cmd.transfer_length = blockCount;
    cmd.control = 0x00;
    return cmd;
}

7. SCSI请求类 - 命令请求封装

cpp 复制代码
// SCSI Request Structure
class ScsiRequest {
public:
    ScsiRequest() : m_lun(), m_command(), m_data() {}
    
    // 获取LUN(逻辑单元号)
    const ScsiLun& lun() const { return m_lun; }
    void setLun(const ScsiLun& lun) { m_lun = lun; }
    
    // 获取SCSI命令
    const ScsiCommand& command() const { return m_command; }
    void setCommand(const ScsiCommand& command) { m_command = command; }
    
    // 获取/设置传输数据
    // 对于写操作:包含要写入的数据
    // 对于读操作:接收读取的数据
    const std::vector<uint8_t>& data() const { return m_data; }
    std::vector<uint8_t>& data() { return m_data; }
    void setData(const std::vector<uint8_t>& data) { m_data = data; }
    
private:
    ScsiLun m_lun;                      // 目标逻辑单元
    ScsiCommand m_command;              // SCSI命令
    std::vector<uint8_t> m_data;        // 关联的数据缓冲区
};

实际用法示例

cpp 复制代码
// 创建INQUIRY请求
ScsiRequest createInquiryRequest() {
    ScsiRequest request;
    
    // 设置LUN
    ScsiLun lun;
    lun.lun = 0;  // 第一个逻辑单元
    request.setLun(lun);
    
    // 设置INQUIRY命令
    ScsiCommand cmd;
    cmd.opcode = 0x12;  // INQUIRY opcode
    cmd.flags = 0x00;
    cmd.lba = 0;
    cmd.transfer_length = 36;  // 标准INQUIRY数据长度
    cmd.control = 0x00;
    request.setCommand(cmd);
    
    // 预留数据缓冲区用于接收响应
    request.data().resize(36);
    
    return request;
}

// 创建读取请求
ScsiRequest createReadRequest(uint32_t startLBA, uint16_t blockCount, uint32_t blockSize) {
    ScsiRequest request;
    
    ScsiLun lun;
    lun.lun = 0;
    request.setLun(lun);
    
    ScsiCommand cmd;
    cmd.opcode = 0x28;  // READ(10)
    cmd.flags = 0x00;
    cmd.lba = startLBA;
    cmd.transfer_length = blockCount;
    cmd.control = 0x00;
    request.setCommand(cmd);
    
    // 预留缓冲区用于接收读取的数据
    size_t dataSize = blockCount * blockSize;
    request.data().resize(dataSize);
    
    return request;
}

// 创建写入请求
ScsiRequest createWriteRequest(uint32_t startLBA, 
                               const std::vector<uint8_t>& writeData,
                               uint32_t blockSize) {
    ScsiRequest request;
    
    ScsiLun lun;
    lun.lun = 0;
    request.setLun(lun);
    
    ScsiCommand cmd;
    cmd.opcode = 0x2A;  // WRITE(10)
    cmd.flags = 0x00;
    cmd.lba = startLBA;
    cmd.transfer_length = writeData.size() / blockSize;
    cmd.control = 0x00;
    request.setCommand(cmd);
    
    // 设置要写入的数据
    request.setData(writeData);
    
    return request;
}

总结表格

结构体 主要用途 关键字段 应用场景
ScsiCBW USB存储命令封装 dCBWSignature, bmCBWFlags, CBWCB USB大容量存储设备通信
ScsiCSW 命令状态返回 dCSWTag, bCSWStatus 验证命令执行结果
ScsiInquiryData 设备信息查询 PeripheralDeviceType, VendorIdentification 设备识别和兼容性检查
ScsiReadCapacityData 容量报告 LogicalBlockAddress, BlockLengthInBytes 计算存储容量
ScsiSenseData 错误报告 SenseKey, ASC, ASCQ 错误诊断和恢复
ScsiCommand 通用命令 opcode, lba, transfer_length 构建各种SCSI操作
ScsiRequest 请求封装 lun(), command(), data() 应用程序层命令管理

这些数据结构构成了SCSI协议栈的核心,在你的光存储项目中:

  1. CBW/CSW 用于USB接口通信

  2. InquiryData 识别光存储设备类型

  3. ReadCapacityData 确定存储空间

  4. SenseData 处理写入/读取错误

  5. ScsiRequest 封装你的激光器/DMD控制命令

相关推荐
福楠1 天前
C++ STL | list
c语言·开发语言·数据结构·c++·算法·list
红豆诗人1 天前
算法和数据结构--时间复杂度和空间复杂度
数据结构·算法
黎雁·泠崖1 天前
栈与队列之栈入门攻略:从核心概念到数组实现
c语言·数据结构
郝学胜-神的一滴1 天前
Linux线程使用注意事项:骈文技术指南
linux·服务器·开发语言·数据结构·c++·程序人生
星火开发设计1 天前
折半插入排序原理与C++实现详解
java·数据结构·c++·学习·算法·排序算法·知识
福楠1 天前
模拟实现list容器
c语言·开发语言·数据结构·c++·list
海天一色y1 天前
python--数据结构--链表
数据结构·链表
三川6981 天前
数据结构设计高频题目
数据结构·哈希算法·散列表
yangpipi-1 天前
《C++并发编程实战》第6章 设计基于锁的并发数据结构
开发语言·数据结构·c++