1# 开发使用SCSI协议的设备驱动 2 3## 简介 4 5在企业级存储解决方案和工业应用场景中,对SCSI(Small Computer System Interface,小型计算机系统接口)设备的使用需求广泛存在,例如:磁盘阵列、磁带库以及特定类型的存储服务器等。当操作系统中缺乏针对这些设备的适配驱动时,会导致设备连接后无法被识别或正常使用。SCSI Peripheral DDK(SCSI Peripheral Driver Development Kit)是为开发者提供的专门用于开发SCSI设备驱动程序的套件,支持开发者基于用户态,在应用层进行SCSI设备驱动的开发。 6 7SCSI Peripheral DDK支持SPC(SCSI Primary Commands)、SBC(SCSI Block Commands)和MMC(MultiMedia Commands)三个命令集中的七个常用命令(INQUIRY、READ CAPACITY、TEST UNIT READY、REQUEST SENSE、READ、WRITE和VERIFY),使得开发者可以使用相对熟悉的命令进行设备驱动开发。 8 9### 基本概念 10 11在进行SCSI Peripheral DDK开发前,开发者应了解以下基本概念: 12 13- **SCSI** 14 15 SCSI是一种用于计算机和外围设备如硬盘驱动器、磁带驱动器、光盘驱动器、扫描仪等之间通信的标准化协议集。 16 17- **AMS** 18 19 AMS(Ability Manager Service)是用于协调各Ability运行关系,以及对生命周期进行调度的系统服务。 20 21- **BMS** 22 23 BMS(Bundle Manager Service)在OpenHarmony上主要负责应用的安装、卸载和数据管理。 24 25- **DDK** 26 27 DDK(Driver Development Kit)是OpenHarmony基于扩展外设框架,为开发者提供的驱动应用开发的工具包,可针对SCSI非标外设,开发对应的驱动。 28 29- **非标外设** 30 31 非标外设(也称为自定义外设或专有外设)是指不遵循通用标准或专门为特定应用场景定制设计的外围设备。这类设备往往需要专门的软件支持或者特殊的接口来实现与主机系统的通信。 32 33- **标准外设** 34 35 标准外设指的是遵循行业广泛接受的标准规范设计的外围设备(USB键盘、鼠标)。此类设备通常具有统一的接口协议、物理尺寸和电气特性,使得其可以在不同的系统之间互换使用。 36 37- **逻辑块** 38 39 逻辑块(Logical Block)是一个基本的数据存储单位。它代表设备上的一块固定大小的数据区域,通常用于数据读写操作。逻辑块的大小可以是512字节、1024字节、2048字节等,具体大小取决于设备的配置和文件系统的设计。 40 41- **CDB** 42 43 CDB(Command Descriptor Block)即命令描述块,是 SCSI协议中用于发送命令的标准数据结构。CDB是一个固定长度的字节数组,包含了SCSI命令的操作码(Opcode)以及相关的参数,用于告诉设备执行什么操作(如读取、写入、查询等)。 44 45### 实现原理 46 47非标外设应用通过扩展外设管理服务获取SCSI设备的ID,通过RPC将ID和要操作的动作下发给SCSI驱动应用,SCSI驱动应用通过调用SCSI Peripheral DDK接口可获取SCSI设备基本信息,读写数据,DDK接口使用HDI服务将指令下发至内核驱动,内核驱动使用指令与设备通信。 48 49**图1** SCSI Peripheral DDK调用原理 50 51 52 53### 约束与限制 54 55- SCSI Peripheral DDK开放API支持标准SCSI类外设扩展驱动开发场景。 56 57- SCSI Peripheral DDK开放API仅允许在DriverExtensionAbility生命周期内使用。 58 59- 使用SCSI Peripheral DDK开放API需要在module.json5中声明对应的ACL权限:ohos.permission.ACCESS_DDK_SCSI_PERIPHERAL。 60 61## 环境搭建 62 63请参考[环境准备](environmental-preparation.md)完成开发前的准备工作。 64 65## 开发指导 66 67### 接口说明 68 69| 名称 | 描述 | 70| -------- | -------- | 71| int32_t OH_ScsiPeripheral_Init(void) | 初始化SCSI Peripheral DDK。 | 72| int32_t OH_ScsiPeripheral_Release(void) | 释放SCSI Peripheral DDK。 | 73| int32_t OH_ScsiPeripheral_Open(uint64_t deviceId, uint8_t interfaceIndex, ScsiPeripheral_Device **dev) | 打开deviceId和interfaceIndex指定的SCSI设备。 | 74| int32_t OH_ScsiPeripheral_Close(ScsiPeripheral_Device **dev) | 关闭SCSI设备。 | 75| int32_t OH_ScsiPeripheral_TestUnitReady(ScsiPeripheral_Device *dev, ScsiPeripheral_TestUnitReadyRequest *request, ScsiPeripheral_Response *response) | 检查逻辑单元是否已经准备好。 | 76| int32_t OH_ScsiPeripheral_Inquiry(ScsiPeripheral_Device *dev, ScsiPeripheral_InquiryRequest *request, ScsiPeripheral_InquiryInfo *inquiryInfo, ScsiPeripheral_Response *response) | 查询SCSI设备的基本信息。 | 77| int32_t OH_ScsiPeripheral_ReadCapacity10(ScsiPeripheral_Device *dev, ScsiPeripheral_ReadCapacityRequest *request, ScsiPeripheral_CapacityInfo *capacityInfo, ScsiPeripheral_Response *response) | 获取SCSI设备的容量信息。 | 78| int32_t OH_ScsiPeripheral_RequestSense(ScsiPeripheral_Device *dev, ScsiPeripheral_RequestSenseRequest *request, ScsiPeripheral_Response *response) | 获取sense data(SCSI设备返回给主机的信息,用于报告设备的状态、错误信息以及诊断信息)。 | 79| int32_t OH_ScsiPeripheral_Read10(ScsiPeripheral_Device *dev, ScsiPeripheral_IORequest *request, ScsiPeripheral_Response *response) | 从指定逻辑块读取数据。 | 80| int32_t OH_ScsiPeripheral_Write10(ScsiPeripheral_Device *dev, ScsiPeripheral_IORequest *request, ScsiPeripheral_Response *response) | 写数据到设备的指定逻辑块。 | 81| int32_t OH_ScsiPeripheral_Verify10(ScsiPeripheral_Device *dev, ScsiPeripheral_VerifyRequest *request, ScsiPeripheral_Response *response) | 校验指定逻辑块。 | 82| int32_t OH_ScsiPeripheral_SendRequestByCdb(ScsiPeripheral_Device *dev, ScsiPeripheral_Request *request, ScsiPeripheral_Response *response) | 以CDB方式发送SCSI命令。 | 83| int32_t OH_ScsiPeripheral_CreateDeviceMemMap(ScsiPeripheral_Device *dev, size_t size, ScsiPeripheral_DeviceMemMap **devMmap) | 创建缓冲区。 | 84| int32_t OH_ScsiPeripheral_DestroyDeviceMemMap(ScsiPeripheral_DeviceMemMap *devMmap) | 销毁缓冲区。| 85| int32_t OH_ScsiPeripheral_ParseBasicSenseInfo(uint8_t *senseData, uint8_t senseDataLen, ScsiPeripheral_BasicSenseInfo *senseInfo) | 解析基本的sense data,包括Information、Command specific information、Sense key specific字段。 | 86 87详细的接口说明请参考[SCSI Peripheral DDK](../../reference/apis-driverdevelopment-kit/_s_c_s_i.md)。 88 89### 开发步骤 90 91以下步骤描述了如何使用SCSI Peripheral DDK开发非标SCSI外设的驱动: 92 93**添加动态链接库** 94 95CMakeLists.txt中添加以下lib。 96```txt 97libscsi.z.so 98``` 99 100**头文件** 101```c++ 102#include <scsi_peripheral/scsi_peripheral_api.h> 103#include <scsi_peripheral/scsi_peripheral_types.h> 104``` 105 1061. 初始化DDK。 107 108 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Init** 初始化SCSI Peripheral DDK。 109 110 ```c++ 111 // 初始化SCSI Peripheral DDK 112 int32_t ret = OH_ScsiPeripheral_Init(); 113 ``` 114 1152. 打开设备。 116 117 初始化SCSI Peripheral DDK后,使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Open** 打开SCSI设备。 118 119 ```c++ 120 uint64_t deviceId = 0x100000003; 121 uint8_t interfaceIndex = 0; 122 ScsiPeripheral_Device *dev = NULL; 123 // 打开deviceId和interfaceIndex1指定的SCSI设备 124 ret = OH_ScsiPeripheral_Open(deviceId, interfaceIndex, &dev); 125 ``` 126 1273. 创建缓冲区。 128 129 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_CreateDeviceMemMap** 创建内存缓冲区devMmap。 130 131 ```c++ 132 constexpr size_t DEVICE_MEM_MAP_SIZE = 1024; 133 ScsiPeripheral_DeviceMemMap *g_scsiDeviceMemMap = nullptr; 134 ret = OH_ScsiPeripheral_CreateDeviceMemMap(dev, DEVICE_MEM_MAP_SIZE, &g_scsiDeviceMemMap); 135 ``` 136 1374. 检查逻辑单元是否已经准备好。 138 139 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_TestUnitReady** 检查逻辑单元是否已经准备好。 140 141 ```c++ 142 ScsiPeripheral_TestUnitReadyRequest testUnitReadyRequest = {0}; 143 testUnitReadyRequest.timeout = 5000; 144 ScsiPeripheral_Response testUnitReadyResponse = {0}; 145 ret = OH_ScsiPeripheral_TestUnitReady(dev, &testUnitReadyRequest, &testUnitReadyResponse); 146 ``` 147 1485. 查询SCSI设备的基本信息。 149 150 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Inquiry** 获取SCSI设备的基本信息。 151 152 ```c++ 153 ScsiPeripheral_InquiryRequest inquiryRequest = {0}; 154 inquiryRequest.allocationLength = 512; 155 inquiryRequest.timeout = 5000; 156 ScsiPeripheral_InquiryInfo inquiryInfo = {0}; 157 inquiryInfo.data = g_scsiDeviceMemMap; 158 ScsiPeripheral_Response inquiryResponse = {0}; 159 ret = OH_ScsiPeripheral_Inquiry(dev, &inquiryRequest, &inquiryInfo, &inquiryResponse); 160 ``` 161 1626. 获取SCSI设备的容量信息。 163 164 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_ReadCapacity10** 获取SCSI设备容量信息。 165 166 ```c++ 167 ScsiPeripheral_ReadCapacityRequest readCapacityRequest = {0}; 168 readCapacityRequest.lbAddress = 0; 169 readCapacityRequest.control = 0; 170 readCapacityRequest.byte8 = 0; 171 readCapacityRequest.timeout = 5000; 172 ScsiPeripheral_CapacityInfo capacityInfo = {0}; 173 ScsiPeripheral_Response readCapacityResponse = {0}; 174 ret = OH_ScsiPeripheral_ReadCapacity10(dev, &readCapacityRequest, &capacityInfo, &readCapacityResponse); 175 ``` 176 1777. 获取sense data。 178 179 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_RequestSense** 获取sense data。 180 181 ```c++ 182 ScsiPeripheral_RequestSenseRequest senseRequest = {0}; 183 senseRequest.allocationLength = SCSIPERIPHERAL_MAX_SENSE_DATA_LEN + 1; 184 senseRequest.control = 0; 185 senseRequest.byte1 = 0; 186 senseRequest.timeout = 5000; 187 ScsiPeripheral_Response senseResponse = {0}; 188 // SCSI设备返回给主机的信息,用于报告设备的状态、错误信息以及诊断信息 189 ret = OH_ScsiPeripheral_RequestSense(dev, &senseRequest, &senseResponse); 190 ``` 191 1928. 解析sense data。 193 194 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_ParseBasicSenseInfo** 解析基本的sense data,包括Information、Command specific information、Sense key specific字段。 195 196 ```c++ 197 ScsiPeripheral_BasicSenseInfo senseInfo = {0}; 198 ret = OH_ScsiPeripheral_ParseBasicSenseInfo(senseResponse.senseData, SCSIPERIPHERAL_MAX_SENSE_DATA_LEN, &senseInfo); 199 ``` 200 2019. 读取数据。 202 203 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Read10** 读取指定逻辑块的数据。 204 205 ```c++ 206 ScsiPeripheral_IORequest readRequest = {0}; 207 readRequest.lbAddress = 1; 208 readRequest.transferLength = 1; 209 readRequest.control = 0; 210 readRequest.byte1 = 0; 211 readRequest.byte6 = 0; 212 readRequest.timeout = 20000; 213 readRequest.data = g_scsiDeviceMemMap; 214 ScsiPeripheral_Response readResponse = {0}; 215 ret = OH_ScsiPeripheral_Read10(dev, &readRequest, &readResponse); 216 ``` 217 21810. 写入数据。 219 220 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Write10** 写数据到设备指定逻辑块。 221 222 ```c++ 223 ScsiPeripheral_IORequest writeRequest = {0}; 224 writeRequest.lbAddress = 1; 225 writeRequest.transferLength = 1; 226 writeRequest.control = 0; 227 writeRequest.byte1 = 0; 228 writeRequest.byte6 = 0; 229 writeRequest.timeout = 5000; 230 writeRequest.data = g_scsiDeviceMemMap; 231 ScsiPeripheral_Response writeResponse = {0}; 232 ret = OH_ScsiPeripheral_Write10(dev, &writeRequest, &writeResponse); 233 ``` 234 23511. 校验指定逻辑块。 236 237 使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Verify10** 校验指定逻辑块。 238 239 ```c++ 240 ScsiPeripheral_VerifyRequest verifyRequest = {0}; 241 verifyRequest.lbAddress = 1; 242 verifyRequest.verificationLength = 1; 243 verifyRequest.timeout = 5000; 244 ScsiPeripheral_Response verifyResponse = {0}; 245 ret = OH_ScsiPeripheral_Verify10(dev, &verifyRequest, &verifyResponse); 246 ``` 247 24812. 以CDB方式发送SCSI命令。 249 250 使用 **scsi_peripheral_api.h** 的 **OH_SCSIPeripheral_SendRequestByCdb** 发送SCSI命令。 251 252 ```c++ 253 ScsiPeripheral_Request sendRequest = {0}; 254 uint8_t cdbData[SCSIPERIPHERAL_MAX_CMD_DESC_BLOCK_LEN] = {0x28, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 需要引入cstring头文件 255 memcpy(sendRequest.commandDescriptorBlock, cdbData, SCSIPERIPHERAL_MAX_CMD_DESC_BLOCK_LEN); 256 sendRequest.cdbLength = 10; 257 sendRequest.dataTransferDirection = -3; 258 sendRequest.timeout = 5000; 259 sendRequest.data = g_scsiDeviceMemMap; 260 ScsiPeripheral_Response sendResponse = {0}; 261 ret = OH_ScsiPeripheral_SendRequestByCdb(dev, &sendRequest, &sendResponse); 262 ``` 263 26413. 销毁缓冲区。 265 266 在所有请求处理完毕,程序退出前,使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_DestroyDeviceMemMap** 销毁缓冲区。 267 268 ```c++ 269 ret = OH_ScsiPeripheral_DestroyDeviceMemMap(g_scsiDeviceMemMap); 270 ``` 271 27214. 关闭设备。 273 274 在销毁缓冲区后,使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Close** 关闭设备。 275 276 ```c++ 277 // 关闭SCSI设备 278 ret = OH_ScsiPeripheral_Close(&dev); 279 ``` 280 28115. 释放DDK。 282 283 在关闭SCSI设备后,使用 **scsi_peripheral_api.h** 的 **OH_ScsiPeripheral_Release** 释放SCSI Peripheral DDK。 284 285 ```c++ 286 // 释放SCSI Peripheral DDK 287 ret = OH_ScsiPeripheral_Release(); 288 ``` 289 290### 调测验证 291 292驱动应用侧开发完成后,可在OpenHarmony设备上安装应用,测试步骤如下: 293 2941. 在设备上点击驱动应用,应用在设备上被拉起。 2952. 应用可以读取到SCSI设备的基础信息。 2963. 选择对应的SCSI命令,输入参数,点击发送按钮,可以执行对应的SCSI命令。 2974. 也可以通过输入方向、CDB数据及CDB长度,点击发送按钮,执行对应的SCSI命令。 298