写点什么

USB DDK 助你轻松实现 HarmonyOS USB 驱动开发

  • 2022 年 3 月 28 日
  • 本文字数:8308 字

    阅读完需:约 27 分钟

USB DDK助你轻松实现HarmonyOS USB驱动开发

HDF(Hardware Driver Foundation)驱动框架是 HarmonyOS 硬件生态开放的基础,为开发者提供了驱动加载、驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效的开发驱动程序。本期,我们将为大家带来 HDF 驱动框架中 USB DDK 的解析与指导。

一、USB DDK 介绍

USB(Universal Serial Bus)通用串行总线,用于规范电脑与外部设备的连接和通讯,包含了主机端(Host)和设备端(Device)。其中,主机端负责 USB 总线中的数据传输及端口管理,设备端则可以连接各种外设,所以 USB 驱动开发又分为主机端驱动开发和设备端驱动开发。

由于基于内核态开发的 USB 驱动功能扩展性较差,目前开发者通常选择 Libusb 库进行 USB 驱动开发。该库是一种跨平台的用户态开源 USB 通信库,可以满足开发者基于用户态开发功能驱动的需求。但是,由于 Libusb 库是完全按照 USB 协议来封装接口的,所以需要开发者对 USB 协议要有较深的了解才能很好的使用,对开发者的要求相对较高,让很多比较初级的开发者望而却步。为了让更多的开发者都能进行基于用户态的 USB 驱动开发,HDF 引入了 USB DDK 开发套件。

USB DDK(USB DriverDevelop Kit)是 HDF 驱动框架为开发者提供的 USB 驱动程序开发套件,包括 USB Host DDK 及 USB Device DDK 两部分,支持基于用户态开发 USB 设备驱动的同时,还提供了丰富的 USB 驱动开发能力,让广大开发者能精准且高效的开发 USB 驱动程序。下面,我们将一一道来。

1)USB Host DDK

USB Host DDK 给开发者提供了主机端 USB 驱动开发能力,按照功能分类三大类,分别是 DDK 初始化类、interface 对象操作类及 request 对象操作类。并为开发者提供了普通模式和专家模式两种开发模式。普通模式下,开发者可通过 USBDDK API 直接完成相关 USB 数据读写操作,不需要过多关注底层传输细节。专家模式下,开发者通过 USB RAW API 直接访问 OS 平台 USB 通道的接口,自定义实现更加复杂的功能。目的是给驱动层留有更灵活,更强大的扩展方案,同时也能够兼容现有驱动,便于移植。USBHost DDK 架构如图 1 所示:

图 1 USB Host DDK 架构

(1)USB Interface Pool 负责 USBInterface 管理。提供 USB Interface 申请和回收,USB Interface 记录设备端口信息以及资源。USB Interface Pool 按照 USB Port 对 USB Interface 进行分类管理。同时,此模块还提供了 USB DDK API,方便开发者 USB 数据读写操作。

(2)USB Protocol Layer 提供 USB 协议封装,根据 USB 协议对设备 IO/控制命令的“翻译/解析”,同时负责设备描述符的管理,根据 USB Device 上报的枚举信息,匹配对应的描述符,并构建对应的 USB Interface,并加入到 USB Interface Pool 中管理。

(3)Device IO Manager 负责 USBIO 请求管理,提供了同步 IO 和异步 IO 管理机制,对于异步 IO,IO Manager 负责将该请求记录下来,然后通过 Raw API Library 提供的接口依次处理待发送的 IO 请求;当收到 USB 控制器应答的处理结果后,IO 接收线程负责解析并上报处理结果给上层调用者。

(4)Raw API Library 抽象了底层 OS 能力,定义了统一的 OS 能力接口,对外提供了 USB RAW API,让开发者自定义实现更加复杂的驱动功能。

(5)OS Adapter 用于封装与平台(Linux 和 LiteOS)相关的操作,根据不同平台配置编译对应平台的封装接口。在 Linux 平台上,访问 USBFS 的操作,全部都封装在这个模块中;而在 LiteOS 平台上,基于 FreeBSD USB 框架的设备访问操作,对应的也都全部封装在这个模块中。

(6)PNP Notify 用于动态监测 USB 状态变化,当有新设备添加/移除时,变化设备信息。同时将所有 USB 设备信息都通过 KHDF 上报给 UHDF 侧的 PNPNotify Manager 模块来完成加载/卸载第三方功能驱动。

2)USB Device DDK

USB Device DDK 给开发者提供了设备端 USB 驱动开发能力。例如,USB 端口动态注册和去注册能力,开发者可以基于能力实现 USB 端口的动态添加和组合;动态实例化能力,支持根据动态下发设备、配置、接口及端点描述符创建设备实例及传输通道;用户态的数据发送及接收能力,支持用户态下发送及接收数据;复合设备能力,支持一个物理设备上多个逻辑设备,实现多个逻辑设备间隔离,并支持不同逻辑设备同时被不同的应用进程访问。USB Device DDK 架构如图 2 所示:

图 2 USB Device DDK 架构

(1)SDK IF 负责将 USB 设备按照设备、接口、管道进行逻辑划分,对配置管理、设备管理、IO 管理进行封装。此模块还向开发者提供了设备创建、获取接口、接收 Event 事件、收发数据等设备测驱动开发的能力接口。

(2)Configuration Manager 负责解析 HCS 文件描述的 USB 描述符信息,得到的 USB 描述符信息用于设备创建,同时模块还提供了自定义属性的读取、创建、删除、修改等操作。

(3)Device Manager 负责根据配置模块解析的 USB 描述符,并根据 USB 描述符创建设备。同时模块还负责获取设备、删除设备、获取设备状态,获取设备上面接口信息。

(4)IO Manager 负责数据的读写,包括 Events 事件、数据读写完成事件的接受,支持同步和异步模式数据读写。

(5)Adapter IF 主要是对复合设备配置驱动及通用功能驱动设备节点操作进行封装,为上层提供统一的设备管理接口。

(6)Adapter 该模块由复合设备配置驱动及通用功能驱动提供。

二、USB DDK 开发指导

相信大家已对 USB DDK 已经有了一定的认识。下面,我们来看看如何使用 USB DDK 来开发 USB Host 和 USB Device 驱动程序吧。

1)USB Host 的开发

USB Host(主机端驱动)主要完成协议封装、设备管理、驱动安装与卸载等。通过上文的介绍,开发者可通过 USB DDK API 和 USB RAW API 来实现主机端驱动。

1. USB DDK API 的使用

USB DDK API 主要实现主机端 USB 数据读写操作,如图 3 所示,是 USB DDK API 提供的部分接口。

图 3 USB DDK API 部分接口

使用步骤如下:

(1) 配置驱动匹配表,完成主机端驱动总体信息的配置,具体如下:

struct UsbPnpMatchIdTable {//驱动模块名,该字段的值必须和驱动入口结构的moduleName一致const char *moduleName;//驱动对外发布服务的名称,必须唯一const char *serviceName;//驱动私有数据匹配关键字const char *deviceMatchAttr;//从该字段开始(包含该字段)之后数据长度,以byte为单位uint8_t length;//USB驱动匹配规则uint16_t matchFlag;//厂商编号uint16_t vendorId;//产品编号uint16_t productId;//设备出厂编号,低16位uint16_t bcdDeviceLow;//设备出厂编号,高16位uint16_t bcdDeviceHigh;  //USB分配的设备类代码uint8_t deviceClass;//USB分配的子类代码uint8_t deviceSubClass;//USB分配的设备协议代码uint8_t deviceProtocol;//接口类型,根据实际需要可填写多个uint8_t interfaceClass[USB_PNP_INFO_MAX_INTERFACES];//接口子类型,根据实际需要可填写多个uint8_t interfaceSubClass[USB_PNP_INFO_MAX_INTERFACES];//接口所遵循的协议,根据实际需要可填写多个uint8_t interfaceProtocol[USB_PNP_INFO_MAX_INTERFACES];//接口的编号,根据实际需要可填写多个uint8_t interfaceNumber[USB_PNP_INFO_MAX_INTERFACES];};
复制代码

其中 matchFlag 表示驱动匹配规则,每个 bit 表示一种匹配方式,其取值如下:

enum {    USB_PNP_NOTIFY_MATCH_VENDOR = 0x0001,    USB_PNP_NOTIFY_MATCH_PRODUCT = 0x0002,    USB_PNP_NOTIFY_MATCH_DEV_LOW = 0x0004,    USB_PNP_NOTIFY_MATCH_DEV_HIGH = 0x0008,    USB_PNP_NOTIFY_MATCH_DEV_CLASS = 0x0010,    USB_PNP_NOTIFY_MATCH_DEV_SUBCLASS = 0x0020,    USB_PNP_NOTIFY_MATCH_DEV_PROTOCOL = 0x0040,    USB_PNP_NOTIFY_MATCH_INT_CLASS = 0x0080,    USB_PNP_NOTIFY_MATCH_INT_SUBCLASS = 0x0100,    USB_PNP_NOTIFY_MATCH_INT_PROTOCOL = 0x0200,    USB_PNP_NOTIFY_MATCH_INT_NUMBER = 0x0400,};
复制代码

(2) USB 主机端驱动开发工具包初始化,使用如下接口:

int32_t UsbInitHostSdk(struct UsbSession **session)
复制代码

(3) 待步骤 2 初始化完后获取 UsbInterface 对象,使用如下接口:

const struct UsbInterface *UsbClaimInterface(const struct UsbSession *session, uint8_t busNum, uint8_t usbAddr, uint8_t interfaceIndex);
复制代码

(4) 打开步骤 3 获取到的 UsbInterface 接口对象,获取对应接口的 UsbInterfaceHandle 对象,使用如下接口:

UsbInterfaceHandle *UsbOpenInterface(const struct UsbInterface *interfaceObj);
复制代码

(5) 根据步骤 4 获取到的 UsbInterfaceHandle 对象,获取指定索引为 pinpeIndex 的 pipeInfo 信息,使用如下接口:

int32_t UsbGetPipeInfo(const UsbInterfaceHandle *interfaceHandle, uint8_t settingIndex, uint8_t pipeId, struct UsbPipeInfo *pipeInfo);
复制代码

(6) 为步骤 4 获取到的 UsbInterfaceHandle 预先分配待发送的 IO Request 对象,使用如下接口:

struct UsbRequest *UsbAllocRequest(const UsbInterfaceHandle *interfaceHandle, int isoPackets, int length);
复制代码

(7) 根据输入参数 params 填充步骤 6 预先分配的 IO Request,使用如下接口:

int32_t UsbFillRequest(const struct UsbRequest *request, const UsbInterfaceHandle *interfaceHandle, const struct UsbRequestParams *params);
复制代码

(8) 提交 IO Request 对象,可以选择同步或异步两种模式,使用如下接口:

int32_t UsbSubmitRequestSync(const struct UsbRequest *request);//发送同步IO请求int32_t UsbSubmitRequestAsync(const struct UsbRequest *request);//发送异步IO请求
复制代码

2. USB RAW API 的使用

USB RAW API 主要实现 USB 更加复杂的功能,如获取描述符信息、获取设备指针、复位设备、提交传输请求等,如图 4 所示,是 USB RAW API 提供的部分接口。

图 4 USB RAW API

使用步骤如下:

(1) 同 USB DDK API 的步骤 1 一样,需先进行驱动匹配表配置。

(2) 初始化 Host RAW,使用如下接口:

int32_t UsbRawInit(struct UsbSession **session);
复制代码

(3) 待步骤 2 完成后打开 USB 设备,使用如下接口:

UsbRawHandle *UsbRawOpenDevice(const struct UsbSession *session, uint8_t busNum, uint8_t usbAddr);
复制代码

(4) 待步骤 3 完成后获取描述符,通过描述符获取接口、端点信息,使用如下接口:

int32_t UsbRawGetConfigDescriptor(const UsbRawDevice *rawDev, uint8_t configIndex, struct UsbRawConfigDescriptor **config);
复制代码

(5) 分配 Request,并根据不同的传输类型使用相应的接口对 Request 进行填充:

int32_t UsbRawFillBulkRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRawFillRequestData *fillData);// 填充用于批量传输的请求int32_t UsbRawFillControlSetup(const unsigned char *setup, const struct UsbControlRequestData *requestData);int32_t UsbRawFillControlRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRawFillRequestData *fillData);// 填充用于控制传输的请求int32_t UsbRawFillInterruptRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRawFillRequestData *fillData);// 填充用于中断传输的请求int32_t UsbRawFillIsoRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRawFillRequestData *fillData);// 填充用于同步传输的请求
复制代码

(6) 提交 IO Request 对象,可以选择同步或异步两种模式,分别使用如下接口:

int32_t UsbRawSendControlRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbControlRequestData *requestData);//发送同步USB控制传输请求int32_t UsbRawSendBulkRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRequestData *requestData);//发送同步USB批量传输请求int32_t UsbRawSendInterruptRequest(const struct UsbRawRequest *request, const UsbRawHandle *devHandle, const struct UsbRequestData *requestData);//发送同步执行USB中断传输请求int32_t UsbRawSubmitRequest(const struct UsbRawRequest *request);//提交异步IO请求
复制代码

感兴趣的小伙伴可点击下方链接查看完整的 USB Host 开发代码:

https://gitee.com/openharmony/drivers_peripheral/tree/master/usb/serial/src

2)USB Device 的开发

USB Device(设备端驱动)主要实现设备管理、配置管理、IO 管理、数据通信等。USB Deivce DDK 给开发者提供了设备创建、获取接口、接收 Event 事件、收发数据等驱动能力接口,如图 5 所示:

图 5 USB Device DDK 开放的 API

下面,我们将根据 USB Deivce DDK 提供的驱动能力接口来开发设备端驱动。

1. 构造描述符

首先,需构造描述符来说明设备的总体信息。开发者可以通过设备功能代码及设备私有数据 HCS 两种途径进行配置,下面将分别介绍。

(1) 在设备功能代码中配置描述符,配置代码如下:

static struct UsbFnFunction g_acmFunction = {//功能描述符    .enable         = true,    .funcName       = "f_generic.a",    .strings        = g_acmStrings,    .fsDescriptors  = g_acmFsFunction,    .hsDescriptors  = g_acmHsFunction,    .ssDescriptors  = g_acmSsFunction,.sspDescriptors = NULL,};struct UsbFnFunction *g_functions[] = {#ifdef CDC_ECM    &g_ecmFunction,#endif#ifdef CDC_ACM    &g_acmFunction,#endifNULL};static struct UsbFnConfiguration g_masterConfig = {//配置描述符    .configurationValue = 1,    .iConfiguration     = USB_FUNC_CONFIG_IDX,    .attributes         = USB_CFG_BUS_POWERED,    .maxPower           = POWER,    .functions          = g_functions,};static struct UsbFnConfiguration *g_configs[] = {    &g_masterConfig,    NULL,};static struct UsbDeviceDescriptor g_cdcMasterDeviceDesc = {//设备描述符    .bLength            = sizeof(g_cdcMasterDeviceDesc),    .bDescriptorType    = USB_DDK_DT_DEVICE,    .bcdUSB             = CpuToLe16(BCD_USB),    .bDeviceClass       = 0,    .bDeviceSubClass    = 0,    .bDeviceProtocol    = 0,    .bMaxPacketSize0    = USB_MAX_PACKET_SIZE,    .idVendor           = CpuToLe16(DEVICE_VENDOR_ID),    .idProduct          = CpuToLe16(DEVICE_PRODUCT_ID),    .bcdDevice          = CpuToLe16(DEVICE_VERSION),    .iManufacturer      = USB_FUNC_MANUFACTURER_IDX,    .iProduct           = USB_FUNC_PRODUCT_IDX,    .iSerialNumber      = USB_FUNC_SERIAL_IDX,    .bNumConfigurations = 1,};static struct UsbFnDeviceDesc g_masterFuncDevice = {//描述符入口    .deviceDesc    = &g_cdcMasterDeviceDesc,    .deviceStrings = g_devStrings,    .configs       = g_configs,};
复制代码

(2) 在设备私有数据 HCS 中配置,配置代码如下:

root {    module = "master";master_config {        match_attr         = "usbfn_master_driver";//该字段与device中deviceMatchAttr                                                             保持一致,否则无法找到的这个节点的信息。    use_hcs            = 1;                         //用户可以用该值决定是否使用hcs配置信息    udc_name           = "100e0000.hidwc3_0";   //UDC的名字        usb_dev_desc       = "UsbDeviceDescriptor";//设备描述符的节点UsbDeviceDescriptor        usb_dev_string     = "UsbDeviceStrings";   //设备字符串的节点为UsbDeviceStrings        usb_configuration = "UsbConfigs";           //配置描述符的节点为UsbConfigs        ...   }}
复制代码

设备描述符的节点为 UsbDeviceDescriptor,配置如下:

UsbDeviceDescriptor {            bLength            = 18;            bDescriptorType  = 0x01;            bcdUSB             = 0x0200;            bDeviceClass      = 0;            bDeviceSubClass  = 0;            bDeviceProtocol  = 0;            bMaxPacketSize0  = 0x40;            idVendor           = 0x0525;            idProduct          = 0xA4A7;            bcdDevice          = 0x0100;            manufacturer       = 0;            product             = 1;            serialnumber       = 2;            numConfigurations = 1;  }
复制代码

2. 创建设备

描述符构造完成后,使用 UsbFnDeviceCreate 函数创建一个 USB 设备,并传入 UDC 控制器名和 UsbFnDescriptorData 结构体。实现代码如下:

if (useHcs == 0) {//使用代码编写的描述符        descData.type        = USBFN_DESC_DATA_TYPE_DESC;        descData.descriptor = &g_acmFuncDevice;    } else {             //使用hcs编写的描述符        descData.type         = USBFN_DESC_DATA_TYPE_PROP;        descData.property    = acm->device->property;}   //创建设备    fnDev = (struct UsbFnDevice *) UsbFnCreateDevice(acm->udcName, &descData);
复制代码

3.获取接口

设备创建后,使用 UsbFnDeviceGetInterface 函数获取 UsbInterface 接口对象,并通过 UsbFnGetInterfacePipeInfo 函数获取 USB 管道信息,实现代码如下:

//获取接口fnIface = (struct UsbFnInterface *)UsbFnGetInterface(fnDev, i);//获取Pipe信息UsbFnGetInterfacePipeInfo(fnIface, i, &pipeInfo);//获取Handlehandle = UsbFnOpenInterface(fnIface);//获取控制(EP0)Requestreq = UsbFnAllocCtrlRequest(acm->ctrlIface.handle,            sizeof(struct UsbCdcLineCoding) + sizeof(struct UsbCdcLineCoding));//获取Requestreq = UsbFnAllocCtrlRequest(acm->ctrlIface.handle,            sizeof(struct UsbCdcLineCoding) + sizeof(struct UsbCdcLineCoding));
复制代码

4. 接收 Event 事件

通过 UsbFnStartRecvInterfaceEvent 函数接收 Event 事件,并通过 UsbFnEventCallback 回调函数对 Event 事件做出响应,实现代码如下:

//开始接收Event事件ret = UsbFnStartRecvInterfaceEvent(acm->ctrlIface.fn, 0xff, UsbAcmEventCallback, acm);//Event处理回调函数static void UsbAcmEventCallback(struct UsbFnEvent *event){struct UsbAcmDevice *acm = NULL;

if (event == NULL || event->context == NULL) { HDF_LOGE("%s: event is null", __func__); return; }

acm = (struct UsbAcmDevice *)event->context; switch (event->type) { case USBFN_STATE_BIND: HDF_LOGI("%s: receive bind event", __func__); break; case USBFN_STATE_UNBIND: HDF_LOGI("%s: receive unbind event", __func__); break; case USBFN_STATE_ENABLE: HDF_LOGI("%s: receive enable event", __func__); AcmEnable(acm); break; case USBFN_STATE_DISABLE: HDF_LOGI("%s: receive disable event", __func__); AcmDisable(acm); acm->enableEvtCnt = 0; break; case USBFN_STATE_SETUP: HDF_LOGI("%s: receive setup event", __func__); if (event->setup != NULL) { AcmSetup(acm, event->setup); } break; case USBFN_STATE_SUSPEND: HDF_LOGI("%s: receive suspend event", __func__); AcmSuspend(acm); break; case USBFN_STATE_RESUME: HDF_LOGI("%s: receive resume event", __func__); AcmResume(acm); break; default: break; }}
复制代码

5. 收发数据

可以选择同步异步发送模式,实现代码如下:

notify = (struct UsbCdcNotification *)req->buf;    ...    if (memcpy_s((void *)(notify + 1), length, data, length)  != EOK) {        return HDF_FAILURE;    }ret = UsbFnSubmitRequestAsync(req);//异步发送
复制代码

感兴趣的小伙伴可点击下方链接查看完整的设备测开发代码。

完整设备测开发代码:https://gitee.com/openharmony/drivers_peripheral/tree/master/usb/gadget/function/

以上就是本期全部内容,通过本文的介绍相信你已经对 USB DDK 有了深刻的认识,期待广大的开发者加入我们,一起丰富基于 USB DDK 的第三方驱动。

 

扫码添加开发者小助手微信

获取更多 HarmonyOS 开发资源和开发者活动资讯

用户头像

每一位开发者都是华为要汇聚的星星之火 2021.10.15 加入

提供HarmonyOS关键技术解析、版本更新、开发者实践和活动资讯,欢迎各位开发者加入HarmonyOS生态,一起创造无限可能!

评论

发布
暂无评论
USB DDK助你轻松实现HarmonyOS USB驱动开发_HarmonyOS_HarmonyOS开发者社区_InfoQ写作平台