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