鸿蒙next教程:扩展外设驱动开发(开发使用SCSI协议的设备驱动)
往期鸿蒙全套实战文章必看:(附带鸿蒙全栈学习资料)
-
鸿蒙开发核心知识点,看这篇文章就够了
-
最新版!鸿蒙HarmonyOS Next应用开发实战学习路线
-
鸿蒙HarmonyOS NEXT开发技术最全学习路线指南
-
鸿蒙应用开发实战项目,看这一篇文章就够了(部分项目附源码)
开发使用SCSI协议的设备驱动
简介
在企业级存储解决方案和工业应用场景中,对SCSI(Small Computer System Interface,小型计算机系统接口)设备的使用需求广泛存在,例如:磁盘阵列、磁带库以及特定类型的存储服务器等。当操作系统中缺乏针对这些设备的适配驱动时,会导致设备连接后无法被识别或正常使用。SCSI Peripheral DDK(SCSI Peripheral Driver Development Kit)是为开发者提供的专门用于开发SCSI设备驱动程序的套件,支持开发者基于用户态,在应用层进行SCSI设备驱动的开发。
SCSI Peripheral DDK支持SPC(SCSI Primary Commands)、SBC(SCSI Block Commands)和MMC(MultiMedia Commands)三个命令集中的七个常用命令(INQUIRY、READ CAPACITY、TEST UNIT READY、REQUEST SENSE、READ、WRITE和VERIFY),使得开发者可以使用相对熟悉的命令进行设备驱动开发。
基本概念
在进行SCSI Peripheral DDK开发前,开发者应了解以下基本概念:
-
SCSI
SCSI是一种用于计算机和外围设备如硬盘驱动器、磁带驱动器、光盘驱动器、扫描仪等之间通信的标准化协议集。
-
AMS
AMS(Ability Manager Service)是用于协调各Ability运行关系,以及对生命周期进行调度的系统服务。
-
BMS
BMS(Bundle Manager Service)在HarmonyOS上主要负责应用的安装、卸载和数据管理。
-
DDK
DDK(Driver Development Kit)是HarmonyOS基于扩展外设框架,为开发者提供的驱动应用开发的工具包,可针对SCSI非标外设,开发对应的驱动。
-
非标外设
非标外设(也称为自定义外设或专有外设)是指不遵循通用标准或专门为特定应用场景定制设计的外围设备。这类设备往往需要专门的软件支持或者特殊的接口来实现与主机系统的通信。
-
标准外设
标准外设指的是遵循行业广泛接受的标准规范设计的外围设备(USB键盘、鼠标)。此类设备通常具有统一的接口协议、物理尺寸和电气特性,使得其可以在不同的系统之间互换使用。
-
逻辑块
逻辑块(Logical Block)是一个基本的数据存储单位。它代表设备上的一块固定大小的数据区域,通常用于数据读写操作。逻辑块的大小可以是512字节、1024字节、2048字节等,具体大小取决于设备的配置和文件系统的设计。
-
CDB
CDB(Command Descriptor Block)即命令描述块,是 SCSI协议中用于发送命令的标准数据结构。CDB是一个固定长度的字节数组,包含了SCSI命令的操作码(Opcode)以及相关的参数,用于告诉设备执行什么操作(如读取、写入、查询等)。
实现原理
非标外设应用通过扩展外设管理服务获取SCSI设备的ID,通过RPC将ID和要操作的动作下发给SCSI驱动应用,SCSI驱动应用通过调用SCSI Peripheral DDK接口可获取SCSI设备基本信息,读写数据,DDK接口使用HDI服务将指令下发至内核驱动,内核驱动使用指令与设备通信。
图1 SCSI Peripheral DDK调用原理
约束与限制
-
SCSI Peripheral DDK开放API支持标准SCSI类外设扩展驱动开发场景。
-
SCSI Peripheral DDK开放API仅允许在DriverExtensionAbility生命周期内使用。
-
使用SCSI Peripheral DDK开放API需要在module.json5中声明对应的ACL权限:ohos.permission.ACCESS_DDK_SCSI_PERIPHERAL。
环境搭建
请参考环境准备完成开发前的准备工作。
开发指导
接口说明
详细的接口说明请参考SCSI Peripheral DDK。
开发步骤
以下步骤描述了如何使用SCSI Peripheral DDK开发非标SCSI外设的驱动:
添加动态链接库
CMakeLists.txt中添加以下lib。
libscsi.z.so
头文件
#include #include
-
初始化DDK。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Init 初始化SCSI Peripheral DDK。
// 初始化SCSI Peripheral DDKint32_t ret = OH_ScsiPeripheral_Init();
-
打开设备。
初始化SCSI Peripheral DDK后,使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Open 打开SCSI设备。
uint64_t deviceId = 0x100000003;uint8_t interfaceIndex = 0;ScsiPeripheral_Device *dev = NULL;// 打开deviceId和interfaceIndex1指定的SCSI设备ret = OH_ScsiPeripheral_Open(deviceId, interfaceIndex, &dev);
-
创建缓冲区。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_CreateDeviceMemMap 创建内存缓冲区devMmap。
constexpr size_t DEVICE_MEM_MAP_SIZE = 1024;ScsiPeripheral_DeviceMemMap *g_scsiDeviceMemMap = nullptr;ret = OH_ScsiPeripheral_CreateDeviceMemMap(dev, DEVICE_MEM_MAP_SIZE, &g_scsiDeviceMemMap);
-
检查逻辑单元是否已经准备好。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_TestUnitReady 检查逻辑单元是否已经准备好。
ScsiPeripheral_TestUnitReadyRequest testUnitReadyRequest = {0};testUnitReadyRequest.timeout = 5000;ScsiPeripheral_Response testUnitReadyResponse = {0};ret = OH_ScsiPeripheral_TestUnitReady(dev, &testUnitReadyRequest, &testUnitReadyResponse);
-
查询SCSI设备的基本信息。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Inquiry 获取SCSI设备的基本信息。
ScsiPeripheral_InquiryRequest inquiryRequest = {0};inquiryRequest.allocationLength = 512;inquiryRequest.timeout = 5000;ScsiPeripheral_InquiryInfo inquiryInfo = {0};inquiryInfo.data = g_scsiDeviceMemMap;ScsiPeripheral_Response inquiryResponse = {0};ret = OH_ScsiPeripheral_Inquiry(dev, &inquiryRequest, &inquiryInfo, &inquiryResponse);
-
获取SCSI设备的容量信息。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_ReadCapacity10 获取SCSI设备容量信息。
ScsiPeripheral_ReadCapacityRequest readCapacityRequest = {0};readCapacityRequest.lbAddress = 0;readCapacityRequest.control = 0;readCapacityRequest.byte8 = 0;readCapacityRequest.timeout = 5000;ScsiPeripheral_CapacityInfo capacityInfo = {0};ScsiPeripheral_Response readCapacityResponse = {0};ret = OH_ScsiPeripheral_ReadCapacity10(dev, &readCapacityRequest, &capacityInfo, &readCapacityResponse);
-
获取sense data。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_RequestSense 获取sense data。
ScsiPeripheral_RequestSenseRequest senseRequest = {0};senseRequest.allocationLength = SCSIPERIPHERAL_MAX_SENSE_DATA_LEN + 1;senseRequest.control = 0;senseRequest.byte1 = 0;senseRequest.timeout = 5000;ScsiPeripheral_Response senseResponse = {0};// SCSI设备返回给主机的信息,用于报告设备的状态、错误信息以及诊断信息ret = OH_ScsiPeripheral_RequestSense(dev, &senseRequest, &senseResponse);
-
解析sense data。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_ParseBasicSenseInfo 解析基本的sense data,包括Information、Command specific information、Sense key specific字段。
ScsiPeripheral_BasicSenseInfo senseInfo = {0};ret = OH_ScsiPeripheral_ParseBasicSenseInfo(senseResponse.senseData, SCSIPERIPHERAL_MAX_SENSE_DATA_LEN, &senseInfo);
-
读取数据。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Read10 读取指定逻辑块的数据。
ScsiPeripheral_IORequest readRequest = {0};readRequest.lbAddress = 1;readRequest.transferLength = 1;readRequest.control = 0;readRequest.byte1 = 0;readRequest.byte6 = 0;readRequest.timeout = 20000;readRequest.data = g_scsiDeviceMemMap;ScsiPeripheral_Response readResponse = {0};ret = OH_ScsiPeripheral_Read10(dev, &readRequest, &readResponse);
-
写入数据。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Write10 写数据到设备指定逻辑块。
ScsiPeripheral_IORequest writeRequest = {0};writeRequest.lbAddress = 1;writeRequest.transferLength = 1;writeRequest.control = 0;writeRequest.byte1 = 0;writeRequest.byte6 = 0;writeRequest.timeout = 5000;writeRequest.data = g_scsiDeviceMemMap;ScsiPeripheral_Response writeResponse = {0};ret = OH_ScsiPeripheral_Write10(dev, &writeRequest, &writeResponse);
-
校验指定逻辑块。
使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Verify10 校验指定逻辑块。
ScsiPeripheral_VerifyRequest verifyRequest = {0};verifyRequest.lbAddress = 1;verifyRequest.verificationLength = 1;verifyRequest.timeout = 5000;ScsiPeripheral_Response verifyResponse = {0};ret = OH_ScsiPeripheral_Verify10(dev, &verifyRequest, &verifyResponse);
-
以CDB方式发送SCSI命令。
使用 scsi_peripheral_api.h 的 OH_SCSIPeripheral_SendRequestByCdb 发送SCSI命令。
ScsiPeripheral_Request sendRequest = {0};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头文件memcpy(sendRequest.commandDescriptorBlock, cdbData, SCSIPERIPHERAL_MAX_CMD_DESC_BLOCK_LEN);sendRequest.cdbLength = 10;sendRequest.dataTransferDirection = -3;sendRequest.timeout = 5000;sendRequest.data = g_scsiDeviceMemMap;ScsiPeripheral_Response sendResponse = {0};ret = OH_ScsiPeripheral_SendRequestByCdb(dev, &sendRequest, &sendResponse);
-
销毁缓冲区。
在所有请求处理完毕,程序退出前,使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_DestroyDeviceMemMap 销毁缓冲区。
ret = OH_ScsiPeripheral_DestroyDeviceMemMap(g_scsiDeviceMemMap);
-
关闭设备。
在销毁缓冲区后,使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Close 关闭设备。
// 关闭SCSI设备ret = OH_ScsiPeripheral_Close(&dev);
-
释放DDK。
在关闭SCSI设备后,使用 scsi_peripheral_api.h 的 OH_ScsiPeripheral_Release 释放SCSI Peripheral DDK。
// 释放SCSI Peripheral DDKret = OH_ScsiPeripheral_Release();