2022-04-01 USB DDK助你轻松实现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模块来完成加载/卸载第三方功能驱动。

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驱动程序吧。

USB Host的开发

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

1. USB DDK API的使用 

USB DDK API主要实现主机端USB数据读写操作,是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更加复杂的功能,如获取描述符信息、获取设备指针、复位设备、提交传输请求等,是USB RAW API提供的部分接口。

使用步骤如下:

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

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

int32_tUsbRawInit(structUsbSession **session);

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

UsbRawHandle *UsbRawOpenDevice(conststruct UsbSession *session,uint8_tbusNum,uint8_tusbAddr);

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

int32_tUsbRawGetConfigDescriptor(constUsbRawDevice *rawDev, uint8_t configIndex,structUsbRawConfigDescriptor **config);

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

int32_tUsbRawFillBulkRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRawFillRequestData *fillData);// 填充用于批量传输的请求

int32_tUsbRawFillControlSetup(constunsignedchar*setup,conststructUsbControlRequestData *requestData);

int32_tUsbRawFillControlRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRawFillRequestData *fillData);// 填充用于控制传输的请求

int32_tUsbRawFillInterruptRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRawFillRequestData *fillData);// 填充用于中断传输的请求

int32_tUsbRawFillIsoRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRawFillRequestData *fillData);// 填充用于同步传输的请求

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

int32_tUsbRawSendControlRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbControlRequestData *requestData);//发送同步USB控制传输请求

int32_tUsbRawSendBulkRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRequestData *requestData);//发送同步USB批量传输请求

int32_tUsbRawSendInterruptRequest(conststructUsbRawRequest *request,constUsbRawHandle *devHandle,conststructUsbRequestData *requestData);//发送同步执行USB中断传输请求

int32_tUsbRawSubmitRequest(conststructUsbRawRequest *request);//提交异步IO请求


感兴趣的小伙伴可看完整的USB Host开发代码

USB Device的开发

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

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

1. 构造描述符

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

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

staticstructUsbFnFunctiong_acmFunction= {//功能描述符

.enable        =true,

.funcName      ="f_generic.a",

    .strings        = g_acmStrings,

    .fsDescriptors  = g_acmFsFunction,

    .hsDescriptors  = g_acmHsFunction,

    .ssDescriptors  = g_acmSsFunction,

.sspDescriptors =NULL,

};

structUsbFnFunction*g_functions[] = {

#ifdefCDC_ECM

    &g_ecmFunction,

#endif

#ifdefCDC_ACM

 &g_acmFunction,

#endif

NULL

};

staticstructUsbFnConfigurationg_masterConfig= {//配置描述符

.configurationValue =1,

    .iConfiguration    = USB_FUNC_CONFIG_IDX,

    .attributes        = USB_CFG_BUS_POWERED,

    .maxPower          = POWER,

    .functions          = g_functions,

};

staticstructUsbFnConfiguration*g_configs[] = {    &g_masterConfig,NULL,};staticstructUsbDeviceDescriptorg_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,

};

staticstructUsbFnDeviceDescg_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 = (structUsbFnInterface *)UsbFnGetInterface(fnDev, i);

//获取Pipe信息

UsbFnGetInterfacePipeInfo(fnIface, i, &pipeInfo);

//获取Handle

handle = UsbFnOpenInterface(fnIface);

//获取控制(EP0)Request

req = UsbFnAllocCtrlRequest(acm->ctrlIface.handle,

    sizeof(structUsbCdcLineCoding) +sizeof(structUsbCdcLineCoding));

//获取Request

req = UsbFnAllocCtrlRequest(acm->ctrlIface.handle,

    sizeof(structUsbCdcLineCoding) +sizeof(structUsbCdcLineCoding));

4. 接收Event事件

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

//开始接收Event事件

ret = UsbFnStartRecvInterfaceEvent(acm->ctrlIface.fn,0xff, UsbAcmEventCallback, acm);

//Event处理回调函数

staticvoidUsbAcmEventCallback(structUsbFnEvent *event)

{

structUsbAcmDevice *acm = NULL;


if(event== NULL ||event->context == NULL) {

HDF_LOGE("%s: event is null", __func__);

return; 

  }


acm = (structUsbAcmDevice *)event->context;

switch(event->type) {

caseUSBFN_STATE_BIND:

HDF_LOGI("%s: receive bind event", __func__);

break;

caseUSBFN_STATE_UNBIND:

HDF_LOGI("%s: receive unbind event", __func__);

break;

caseUSBFN_STATE_ENABLE:

HDF_LOGI("%s: receive enable event", __func__);           

AcmEnable(acm);

break;

caseUSBFN_STATE_DISABLE:

HDF_LOGI("%s: receive disable event", __func__); 

AcmDisable(acm);

acm->enableEvtCnt =0;

break;

caseUSBFN_STATE_SETUP:

HDF_LOGI("%s: receive setup event", __func__);

if(event->setup != NULL) {

AcmSetup(acm,event->setup);

            }

break;

caseUSBFN_STATE_SUSPEND:

HDF_LOGI("%s: receive suspend event", __func__);           

AcmSuspend(acm);

break;

caseUSBFN_STATE_RESUME:

HDF_LOGI("%s: receive resume event", __func__);           

AcmResume(acm);

break;

default:

break;

    }

}

5. 收发数据

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

notify = (structUsbCdcNotification *)req->buf;

    ...

    if(memcpy_s((void*)(notify +1), length, data, length)  != EOK) {

        returnHDF_FAILURE;

        }

ret = UsbFnSubmitRequestAsync(req);//异步发送

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

END

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容