1# HDF驱动开发流程 2 3## 概述 4 5HDF(Hardware Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理、驱动消息机制和配置管理。并以组件化驱动模型作为核心设计思路,让驱动开发和部署更加规范,旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的驱动管理的开发环境,力求做到一次开发,多系统部署。 6 7### 驱动加载 8 9HDF驱动框架提供把和配置的设备列表匹配成功的驱动程序加载起来的功能。 10 11### 驱动服务管理 12 13HDF框架可以集中管理驱动服务,开发者可直接通过HDF框架对外提供的能力接口获取驱动相关的服务。 14 15### 驱动消息机制 16 17HDF框架提供统一的驱动消息机制,支持用户态应用向内核态驱动发送消息,也支持内核态驱动向用户态应用发送消息。 18 19### 配置管理 20 21HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码,内容以Key-Value为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。 22 23### 驱动模型 24 25HDF框架将一类设备驱动放在同一个Host(设备容器)里面,用于管理一组设备的启动加载等过程。在划分Host时,驱动程序是部署在一个Host还是部署在不同的Host,主要考虑驱动程序之间是否存在耦合性,如果两个驱动程序之间存在依赖,可以考虑将这部分驱动程序部署在一个Host里面,否则部署到独立的Host中是更好的选择。Device对应一个真实的物理设备。DeviceNode是设备的一个部件,Device至少有一个DeviceNode。每个DeviceNode可以发布一个设备服务。驱动即驱动程序,每个DevicdNode唯一对应一个驱动,实现和硬件的功能交互。HDF驱动模型如下图所示: 26 27**图1** HDF驱动模型 28 29 30 31## 功能描述 32 33### 驱动加载 34 35HDF驱动框架提供把和配置的设备列表匹配成功的驱动程序加载起来的功能,支持按需加载和按序加载两种策略,具体设备的加载策略由配置文件中的preload字段来控制,配置值参考如下: 36 37```c 38typedef enum { 39 DEVICE_PRELOAD_ENABLE = 0, 40 DEVICE_PRELOAD_ENABLE_STEP2 = 1, 41 DEVICE_PRELOAD_DISABLE = 2, 42 DEVICE_PRELOAD_INVALID 43} DevicePreload; 44``` 45 46#### 按需加载 47 48- preload字段配置为0(DEVICE_PRELOAD_ENABLE),则系统启动过程中默认加载。 49- preload字段配置为1(DEVICE_PRELOAD_ENABLE_STEP2),当系统支持快速启动的时候,则在系统完成之后再加载这一类驱动,否则和DEVICE_PRELOAD_ENABLE含义相同。 50- preload字段配置为2(DEVICE_PRELOAD_DISABLE),则系统启动过程中默认不加载,支持后续动态加载,当用户态获取驱动服务[消息机制](#驱动消息机制管理)时,如果驱动服务不存在,HDF框架会尝试动态加载该驱动。 51 52#### 按序加载(默认加载策略) 53 54配置文件中的priority(取值范围为整数0到200)是用来表示host(驱动容器)和驱动的优先级的。不同的host内的驱动,host的priority值越小,驱动加载优先级越高;同一个host内驱动的priority值越小,加载优先级越高。 55 56#### 异常恢复(用户态驱动) 57 58当驱动服务异常退出时,恢复策略如下: 59- preload字段配置为0(DEVICE_PRELOAD_ENABLE)或1(DEVICE_PRELOAD_ENABLE_STEP2)的驱动服务,由启动模块拉起host并重新加载服务。 60- preload字段配置为2(DEVICE_PRELOAD_DISABLE)的驱动服务,需业务模块注册HDF的服务状态监听器,当收到服务退出消息时,业务模块调用LoadDevice重新加载服务。 61 62### 驱动服务管理 63 64驱动服务是HDF驱动设备对外提供能力的对象,由HDF框架统一管理。驱动服务管理主要包含驱动服务的发布和获取。HDF框架定义了驱动对外发布服务的策略,由配置文件中的policy字段来控制,policy字段的取值范围以及含义如下: 65 66```c 67typedef enum { 68 /* 驱动不提供服务 */ 69 SERVICE_POLICY_NONE = 0, 70 /* 驱动对内核态发布服务 */ 71 SERVICE_POLICY_PUBLIC = 1, 72 /* 驱动对内核态和用户态都发布服务 */ 73 SERVICE_POLICY_CAPACITY = 2, 74 /* 驱动服务不对外发布服务,但可以被订阅 */ 75 SERVICE_POLICY_FRIENDLY = 3, 76 /* 驱动私有服务不对外发布服务,也不能被订阅 */ 77 SERVICE_POLICY_PRIVATE = 4, 78 /* 错误的服务策略 */ 79 SERVICE_POLICY_INVALID 80} ServicePolicy; 81``` 82 83#### 使用场景 84 85当驱动需要以接口的形式对外提供能力时,可以使用HDF框架的驱动服务管理能力。 86 87#### 接口说明 88 89针对驱动服务管理功能,HDF框架开放了以下接口供开发者调用,如下表所示: 90 91**表1** 服务管理接口 92 93| 方法 | 描述 | 94| ------------------------------------------------------------ | ------------------------------------------------------------ | 95| int32_t (*Bind)(struct HdfDeviceObject *deviceObject) | 需要驱动开发者实现Bind函数,将自己的服务接口绑定到HDF框架中。 | 96| const struct HdfObject *DevSvcManagerClntGetService(const char *svcName) | 获取驱动的服务。 | 97| int HdfDeviceSubscribeService( struct HdfDeviceObject *deviceObject, const char *serviceName, struct SubscriberCallback callback) | 订阅驱动的服务。 | 98 99 100### 驱动消息机制管理 101 102#### 使用场景 103 104当用户态应用和内核态驱动需要交互时,可以使用HDF框架的消息机制来实现。 105 106#### 接口说明 107 108消息机制的功能主要有以下两种: 109- 用户态应用发送消息到驱动。 110- 用户态应用接收驱动主动上报事件。 111 112**表2** 消息机制接口 113 114| 方法 | 描述 | 115| ------------------------------------------------------------ | ------------------------------------------------------------ | 116| struct HdfIoService *HdfIoServiceBind(const char *serviceName); | 用户态获取驱动的服务,获取该服务之后通过服务中的Dispatch方法向驱动发送消息。 | 117| void HdfIoServiceRecycle(struct HdfIoService *service); | 释放驱动服务。 | 118| int HdfDeviceRegisterEventListener(struct HdfIoService *target, struct HdfDevEventlistener *listener); | 用户态程序注册接收驱动上报事件的操作方法。 | 119| int32_t HdfDeviceSendEvent(const struct HdfDeviceObject *deviceObject, uint32_t id, const struct HdfSBuf *data) | 驱动主动上报事件接口。 | 120 121 122 123### 配置管理 124 125#### 配置概述 126 127HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码,内容以Key-Value为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。HC-GEN(HDF Configuration Generator)是HCS配置转换工具,可以将HDF配置文件转换为软件可读取的文件格式: 128 129- 在弱性能环境中,转换为配置树源码或配置树宏定义,驱动可直接调用C代码或宏式APIs获取配置。 130- 在高性能环境中,转换为HCB(HDF Configuration Binary)二进制文件,驱动可使用HDF框架提供的配置解析接口获取配置。 131 132以下是使用HCB模式的典型应用场景: 133 134**图2** 配置使用流程图 135 136 137 138HCS经过HC-GEN编译生成HCB文件,HDF驱动框架中的HCS Parser模块会从HCB文件中重建配置树,HDF驱动模块使用HCS Parser提供的配置读取接口获取配置内容。 139 140#### 配置语法 141 142HCS的语法介绍如下: 143 144##### 关键字 145 146HCS配置语法保留了以下关键字。 147 148**表3** HCS配置语法保留关键字 149 150| 关键字 | 用途 | 说明 | 151| ---------- | -------------------------- | ------------------------------------------ | 152| root | 配置根节点 | - | 153| include | 引用其他HCS配置文件 | - | 154| delete | 删除节点或属性 | 只能用于操作include导入的配置树 | 155| template | 定义模板节点 | - | 156| match_attr | 用于标记节点的匹配查找属性 | 解析配置时可以使用该属性的值查找到对应节点 | 157 158##### 基本结构 159 160HCS主要分为属性(Attribute)和节点(Node)两种结构。 161 162**属性** 163 164属性即最小的配置单元,是一个独立的配置项。语法如下: 165 166``` 167 attribute_name = value; 168``` 169 170- attribute_name是**字母、数字、下划线**的组合且必须以字母或下划线开头,字母区分大小写。 171- value的可用格式如下: 172 - 数字常量,支持二进制、八进制、十进制、十六进制数,具体[数据类型](#数据类型)章节。 173 - 字符串,内容使用双引号("")引用。 174 - 节点引用。 175- attribute必须以分号(;)结束且必须属于一个node。 176 177**节点** 178 179节点是一组属性的集合,语法如下: 180 181``` 182 node_name { 183 module = "sample"; 184 ... 185 } 186``` 187 188- node_name是**字母、数字、下划线**的组合且必须以字母或下划线开头,字母区分大小写。 189- 大括号后无需添加结束符“;”。 190- root为保留关键字,用于声明配置表的根节点。每个配置表必须以root节点开始。 191- root节点中必须包含module属性,其值应该为一个字符串,用于表征该配置所属模块。 192- 节点中可以增加match_attr属性,其值为一个全局唯一的字符串。当驱动程序在解析配置时可以以该属性的值为参数调用查找接口查找到包含该属性的节点。 193 194##### 数据类型 195 196在属性定义中使用自动数据类型,不显式指定类型,属性支持的数据类型如下: 197 198**整型** 199 200整型长度自动推断,根据实际数据长度给与最小空间占用的类型。 201 202- 二进制,0b前缀,示例:0b1010。 203- 八进制,0前缀,示例:0664。 204- 十进制 ,无前缀,且支持有符号与无符号,示例:1024,+1024均合法。驱动程序在读取负值时注意使用有符号数读取接口。 205- 十六进制,0x前缀,示例:0xff00、0xFF。 206 207**字符串** 208 209字符串使用双引号("")表示。 210 211**数组** 212 213数组元素支持整型、字符串,不支持混合类型。整型数组中uint32_t uint64_t混用会向上转型为uint64_t数组。整型数组与字符串数组示例如下: 214 215``` 216attr_foo = [0x01, 0x02, 0x03, 0x04]; 217attr_bar = ["hello", "world"]; 218``` 219 220**bool类型** 221 222bool类型中**true**表示真,**false**表示假。 223 224##### 预处理 225 226**include** 227 228用于导入其他HCS文件。语法示例如下: 229 230``` 231#include "foo.hcs" 232#include "../bar.hcs" 233``` 234 235- 文件名必须使用双引号(""),不在同一目录使用相对路径引用。被include文件也必须是合法的HCS文件。 236- 多个include,如果存在相同的节点,后者覆盖前者,其余的节点依次展开。 237 238##### 注释 239 240支持两种注释风格。 241 242- 单行注释。 243 244 ``` 245 // comment 246 ``` 247 248- 多行注释。 249 250 ``` 251 /* 252 comment 253 */ 254 ``` 255 256 >  **说明:** 257 > 多行注释不支持嵌套。 258 259##### 引用修改 260 261引用修改的作用是在当前节点中修改另外任意一个节点的内容,语法为: 262 263``` 264 node :& source_node 265``` 266 267上述语句表示node中的内容是对source_node节点内容的修改。示例如下: 268 269``` 270root { 271 module = "sample"; 272 foo { 273 foo_ :& root.bar{ 274 attr = "foo"; 275 } 276 foo1 :& foo2 { 277 attr = 0x2; 278 } 279 foo2 { 280 attr = 0x1; 281 } 282 } 283 284 bar { 285 attr = "bar"; 286 } 287} 288``` 289 290最终生成配置树为: 291 292``` 293root { 294 module = "sample"; 295 foo { 296 foo2 { 297 attr = 0x2; 298 } 299 } 300 bar { 301 attr = "foo"; 302 } 303} 304``` 305 306在以上示例中,可以看到foo.foo_节点通过引用将bar.attr属性的值修改为了"foo",foo.foo1节点通过引用将foo.foo2.attr属性的值修改为了0x2。foo.foo_以及foo.foo1节点表示对目标节点内容的修改,其自身并不会存在最终生成的配置树中。 307 308- 引用同级node,可以直接使用node名称,否则被引用的节点必须使用绝对路径,节点间使用“.”分隔,root表示根节点,格式为root开始的节点路径序列,例如root.foo.bar即为一个合法的绝对路径。 309- 如果出现修改冲突(即多处修改同一个属性),编译器将提示warning,因为这种情况下只会生效某一个修改而导致最终结果不确定。 310 311##### 节点复制 312 313节点复制可以实现在节点定义时从另一个节点先复制内容,用于定义内容相似的节点。语法为: 314 315``` 316 node : source_node 317``` 318 319上述语句表示在定义"node"节点时将另一个节点"source_node"的属性复制过来。示例如下: 320 321``` 322root { 323 module = "sample"; 324 foo { 325 attr_0 = 0x0; 326 } 327 bar:foo { 328 attr_1 = 0x1; 329 } 330} 331``` 332 333上述代码的最终生成配置树为: 334 335``` 336root { 337 module = "sample"; 338 foo { 339 attr_0 = 0x0; 340 } 341 bar { 342 attr_1 = 0x1; 343 attr_0 = 0x0; 344 } 345} 346``` 347 348在上述示例中,编译后bar节点既包含attr_0属性又包含attr_1属性,在bar中对attr_0的修改不会影响到foo。 349 350当foo和bar在同级node中时可不指定foo的路径,否则需要使用绝对路径引用,绝对路径的介绍请参考[引用修改](#引用修改)。 351 352##### 删除 353 354要对include导入的base配置树中不需要的节点或属性进行删除,可以使用delete关键字。下面的举例中sample1.hcs通过include导入了sample2.hcs中的配置内容,并使用delete删除了sample2.hcs中的attribute2属性和foo_2节点,示例如下: 355 356``` 357// sample2.hcs 358root { 359 attr_1 = 0x1; 360 attr_2 = 0x2; 361 foo_2 { 362 t = 0x1; 363 } 364} 365 366// sample1.hcs 367#include "sample2.hcs" 368root { 369 attr_2 = delete; 370 foo_2 : delete { 371 } 372} 373``` 374 375上述代码在生成过程中将会删除root.foo_2节点与attr_2,最终生成配置树为: 376 377``` 378root { 379 attr_1 = 0x1; 380} 381``` 382 383>  **说明:** 384> 在同一个HCS文件中不允许使用delete,建议直接删除不需要的属性。 385 386##### 属性引用 387 388为了在解析配置时快速定位到关联的节点,可以把节点作为属性的右值,通过读取属性查找到对应节点。语法为: 389 390``` 391 attribute = &node; 392``` 393 394上述语句表示attribute的值是一个节点node的引用,在解析时可以用这个attribute快速定位到node,便于关联和查询其他node。示例如下: 395 396``` 397node1 { 398 attributes; 399} 400node2 { 401 attr_1 = &root.node1; 402} 403``` 404 405或 406 407``` 408node2 { 409 node1 { 410 attributes; 411 } 412 attr_1 = &node1; 413} 414``` 415 416##### 模板 417 418模板的用途在于生成严格一致的node结构,以便对同类型node进行遍历和管理。使用template关键字定义模板node,子node通过双冒号“::”声明继承关系。子节点可以改写或新增但不能删除template中的属性,子节点中没有定义的属性将使用template中的定义作为默认值。示例如下: 419 420``` 421root { 422 module = "sample"; 423 template foo { 424 attr_1 = 0x1; 425 attr_2 = 0x2; 426 } 427 428 bar :: foo { 429 } 430 431 bar_1 :: foo { 432 attr_1 = 0x2; 433 } 434} 435``` 436 437 生成配置树如下: 438 439``` 440root { 441 module = "sample"; 442 bar { 443 attr_1 = 0x1; 444 attr_2 = 0x2; 445 } 446 bar_1 { 447 attr_1 = 0x2; 448 attr_2 = 0x2; 449 } 450} 451``` 452 453在上述示例中,bar和bar_1节点继承了foo节点,生成配置树节点结构与foo保持了完全一致,只是属性的值不同。 454 455#### 配置生成 456 457 hc-gen是配置生成的工具,可以对HCS配置语法进行检查并把HCS源文件转化成HCB二进制文件。 458 459**hc-gen介绍** 460 461 hc-gen参数说明: 462 463``` 464Usage: hc-gen [Options] [File] 465options: 466 -o <file> output file name, default same as input 467 -a hcb align with four bytes 468 -b output binary output, default enable 469 -t output config in C language source file style 470 -m output config in macro source file style 471 -i output binary hex dump in C language source file style 472 -p <prefix> prefix of generated symbol name 473 -d decompile hcb to hcs 474 -V show verbose info 475 -v show version 476 -h show this help message 477``` 478 479生成.c/.h配置文件方法: 480 481``` 482hc-gen -o [OutputCFileName] -t [SourceHcsFileName] 483``` 484 485生成HCB配置文件方法: 486 487``` 488hc-gen -o [OutputHcbFileName] -b [SourceHcsFileName] 489``` 490 491生成宏定义配置文件方法: 492 493``` 494hc-gen -o [OutputMacroFileName] -m [SourceHcsFileName] 495``` 496 497反编译HCB文件为HCS方法: 498 499``` 500hc-gen -o [OutputHcsFileName] -d [SourceHcbFileName] 501``` 502 503## 开发指导 504 505### 场景介绍 506 507关于驱动的开发我们主要目的是实现驱动代码的编写,但是驱动开发过程中需要服务管理、消息机制管理,才能使驱动在代码编译过程中进行加载。以下开发步骤中介绍了驱动开发、驱动消息机制管理开发、驱动服务管理开发的步骤。 508 509### 驱动开发实例 510 511基于HDF框架的驱动开发主要分为三个部分:驱动实现、驱动编译脚本编写和驱动配置。详细开发流程如下所示: 512 513#### 驱动实现 514 515驱动实现包含驱动业务代码实现和驱动入口注册,具体写法如下: 516 517- 驱动业务代码 518 519 ```c 520 #include "hdf_device_desc.h" // HDF框架对驱动开发相关能力接口的头文件 521 #include "hdf_log.h" // HDF框架提供的日志接口头文件 522 523 #define HDF_LOG_TAG sample_driver // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签。 524 525 // 将驱动对外提供的服务能力接口绑定到HDF框架。 526 int32_t HdfSampleDriverBind(struct HdfDeviceObject *deviceObject) 527 { 528 HDF_LOGD("Sample driver bind success"); 529 return HDF_SUCCESS; 530 } 531 532 // 驱动自身业务初始化的接口 533 int32_t HdfSampleDriverInit(struct HdfDeviceObject *deviceObject) 534 { 535 HDF_LOGD("Sample driver Init success"); 536 return HDF_SUCCESS; 537 } 538 539 // 驱动资源释放的接口 540 void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject) 541 { 542 HDF_LOGD("Sample driver release success"); 543 return; 544 } 545 ``` 546 547- 驱动入口注册到HDF框架 548 549 ```c 550 // 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量。 551 struct HdfDriverEntry g_sampleDriverEntry = { 552 .moduleVersion = 1, 553 .moduleName = "sample_driver", 554 .Bind = HdfSampleDriverBind, 555 .Init = HdfSampleDriverInit, 556 .Release = HdfSampleDriverRelease, 557 }; 558 559 // 调用HDF_INIT将驱动入口注册到HDF框架中。在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动;当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 560 HDF_INIT(g_sampleDriverEntry); 561 ``` 562 563#### 驱动编译脚本编写 564 565- ##### LiteOS 566 567 涉及Makefile和BUILD.gn修改: 568 569 - Makefile部分: 570 571 驱动代码的编译必须要使用HDF框架提供的Makefile模板进行编译。 572 573 ```makefile 574 include $(LITEOSTOPDIR)/../../drivers/hdf_core/adapter/khdf/liteos/lite.mk # 【必需】导入hdf预定义内容 575 MODULE_NAME := #生成的结果文件 576 LOCAL_INCLUDE := #本驱动的头文件目录 577 LOCAL_SRCS := #本驱动的源代码文件 578 LOCAL_CFLAGS := #自定义的编译选项 579 include $(HDF_DRIVER) #导入Makefile模板完成编译 580 ``` 581 582 编译结果文件链接到内核镜像,添加到**drivers/hdf_core/adapter/khdf/liteos**目录下的**hdf_lite.mk**里面,示例如下: 583 584 ```makefile 585 LITEOS_BASELIB += -lxxx #链接生成的静态库 586 LIB_SUBDIRS += #驱动代码Makefile的目录 587 ``` 588 589 - BUILD.gn部分: 590 591 添加模块BUILD.gn,可参考如下示例: 592 593 ``` 594 import("//build/lite/config/component/lite_component.gni") 595 import("//drivers/hdf_core/adapter/khdf/liteos/hdf.gni") 596 module_switch = defined(LOSCFG_DRIVERS_HDF_xxx) 597 module_name = "xxx" 598 hdf_driver(module_name) { 599 sources = [ 600 "xxx/xxx/xxx.c", #模块要编译的源码文件 601 ] 602 public_configs = [ ":public" ] #使用依赖的头文件配置 603 } 604 config("public") { #定义依赖的头文件配置 605 include_dirs = [ 606 "xxx/xxx/xxx", #依赖的头文件目录 607 ] 608 } 609 ``` 610 611 把新增模块的BUILD.gn所在的目录添加到**/drivers/hdf_core/adapter/khdf/liteos/BUILD.gn**里面: 612 613 ``` 614 group("liteos") { 615 public_deps = [ ":$module_name" ] 616 deps = [ 617 "xxx/xxx",#新增模块BUILD.gn所在的目录,/drivers/hdf_core/adapter/khdf/liteos 618 ] 619 } 620 ``` 621 622- ##### Linux 623 624 如果需要定义模块控制宏,需要在模块目录xxx里面添加Kconfig文件,并把Kconfig文件路径添加到**drivers/hdf_core/adapter/khdf/linux/Kconfig**里面: 625 626 ``` 627 source "drivers/hdf/khdf/xxx/Kconfig" #目录为hdf模块软链接到kernel里面的目录 628 ``` 629 630 添加模块目录到**drivers/hdf_core/adapter/khdf/linux/Makefile**: 631 632 ```makefile 633 obj-$(CONFIG_DRIVERS_HDF) += xxx/ 634 ``` 635 636 在模块目录xxx里面添加Makefile文件,在Makefile文件里面添加模块代码编译规则: 637 638 ```makefile 639 obj-y += xxx.o 640 ``` 641 642#### 驱动配置 643 644HDF使用HCS作为配置描述源码,HCS详细介绍[配置管理](#配置概述)。 645 646驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息,具体写法如下: 647 648- 驱动设备描述(必选) 649 650 HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述。驱动的设备描述填写如下所示: 651 652 ``` 653 root { 654 device_info { 655 match_attr = "hdf_manager"; 656 template host { // host模板,继承该模板的节点(如下sample_host)如果使用模板中的默认值,则节点字段可以缺省。 657 hostName = ""; 658 priority = 100; 659 uid = ""; // 用户态进程uid,缺省为空,会被配置为hostName的定义值,即普通用户。 660 gid = ""; // 用户态进程gid,缺省为空,会被配置为hostName的定义值,即普通用户组。 661 caps = [""]; // 用户态进程Linux capabilities配置,缺省为空,需要业务模块按照业务需要进行配置。 662 template device { 663 template deviceNode { 664 policy = 0; 665 priority = 100; 666 preload = 0; 667 permission = 0664; 668 moduleName = ""; 669 serviceName = ""; 670 deviceMatchAttr = ""; 671 } 672 } 673 } 674 sample_host :: host{ 675 hostName = "host0"; // host名称,host节点是用来存放某一类驱动的容器。 676 priority = 100; // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序。 677 caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; // 用户态进程Linux capabilities配置。 678 device_sample :: device { // sample设备节点 679 device0 :: deviceNode { // sample驱动的DeviceNode节点 680 policy = 1; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍。 681 priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序。 682 preload = 0; // 驱动按需加载字段。 683 permission = 0664; // 驱动创建设备节点权限 684 moduleName = "sample_driver"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致。 685 serviceName = "sample_service"; // 驱动对外发布服务的名称,必须唯一。 686 deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等。 687 } 688 } 689 } 690 } 691 } 692 ``` 693 694 >  **说明:** 695 > 696 > - uid、gid、caps等配置项是用户态驱动的启动配置,内核态不用配置。 697 > - 根据进程权限最小化设计原则,业务模块uid、gid不用配置,如上面的sample_host,使用普通用户权限,即uid和gid被定义为hostName的定义值。 698 > - 如果普通用户权限不能满足业务要求,需要把uid、gid定义为system或者root权限时,请找安全专家进行评审。 699 > - 进程的uid在文件**base/startup/init/services/etc/passwd**中配置,进程的gid在文件**base/startup/init/services/etc/group**中配置,进程uid和gid配置参考:[系统服务用户组添加方法](https://gitee.com/openharmony/startup_init_lite/wikis)。 700 > - caps值:格式为caps = ["xxx"],如果要配置CAP_DAC_OVERRIDE,此处需要填写caps = ["DAC_OVERRIDE"],不能填写为caps = ["CAP_DAC_OVERRIDE"]。 701 > - preload:驱动按需加载字段。 702 703- 驱动私有配置信息(可选) 704 705 如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息。HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject中的property里面,通过Bind和Init(参考[驱动实现](#驱动实现))传递给驱动。驱动的配置信息示例如下: 706 707 ``` 708 root { 709 SampleDriverConfig { 710 sample_version = 1; 711 sample_bus = "I2C_0"; 712 match_attr = "sample_config"; // 该字段的值必须和device_info.hcs中的deviceMatchAttr值一致 713 } 714 } 715 ``` 716 717 配置信息定义之后,需要将该配置文件添加到板级配置入口文件hdf.hcs,示例如下: 718 719 ``` 720 #include "device_info/device_info.hcs" 721 #include "sample/sample_config.hcs" 722 ``` 723 724### 驱动消息机制管理开发 725 7261. 将驱动配置信息中服务策略policy字段设置为2(SERVICE_POLICY_CAPACITY,[policy定义](#驱动服务管理))。 727 728 ``` 729 device_sample :: Device { 730 policy = 2; 731 ... 732 } 733 ``` 734 7352. 配置驱动信息中的服务设备节点权限(permission字段)是框架给驱动创建设备节点的权限,默认是0666,驱动开发者根据驱动的实际使用场景配置驱动设备节点的权限。 736 7373. 在服务实现过程中,实现服务基类成员IDeviceIoService中的Dispatch方法。 738 739 ```c 740 // Dispatch是用来处理用户态发下来的消息 741 int32_t SampleDriverDispatch(struct HdfDeviceIoClient *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) 742 { 743 HDF_LOGI("sample driver lite A dispatch"); 744 return HDF_SUCCESS; 745 } 746 int32_t SampleDriverBind(struct HdfDeviceObject *device) 747 { 748 HDF_LOGI("test for lite os sample driver A Open!"); 749 if (device == NULL) { 750 HDF_LOGE("test for lite os sample driver A Open failed!"); 751 return HDF_FAILURE; 752 } 753 static struct ISampleDriverService sampleDriverA = { 754 .ioService.Dispatch = SampleDriverDispatch, 755 .ServiceA = SampleDriverServiceA, 756 .ServiceB = SampleDriverServiceB, 757 }; 758 device->service = (struct IDeviceIoService *)(&sampleDriverA); 759 return HDF_SUCCESS; 760 } 761 ``` 762 7634. 驱动定义消息处理函数中的cmd类型。 764 765 ```c 766 #define SAMPLE_WRITE_READ 1 // 读写操作码1 767 ``` 768 7695. 用户态获取服务接口并发送消息到驱动。 770 771 ```c 772 int SendMsg(const char *testMsg) 773 { 774 if (testMsg == NULL) { 775 HDF_LOGE("test msg is null"); 776 return HDF_FAILURE; 777 } 778 struct HdfIoService *serv = HdfIoServiceBind("sample_driver"); 779 if (serv == NULL) { 780 HDF_LOGE("fail to get service"); 781 return HDF_FAILURE; 782 } 783 struct HdfSBuf *data = HdfSbufObtainDefaultSize(); 784 if (data == NULL) { 785 HDF_LOGE("fail to obtain sbuf data"); 786 return HDF_FAILURE; 787 } 788 struct HdfSBuf *reply = HdfSbufObtainDefaultSize(); 789 if (reply == NULL) { 790 HDF_LOGE("fail to obtain sbuf reply"); 791 ret = HDF_DEV_ERR_NO_MEMORY; 792 goto out; 793 } 794 if (!HdfSbufWriteString(data, testMsg)) { 795 HDF_LOGE("fail to write sbuf"); 796 ret = HDF_FAILURE; 797 goto out; 798 } 799 int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply); 800 if (ret != HDF_SUCCESS) { 801 HDF_LOGE("fail to send service call"); 802 goto out; 803 } 804 out: 805 HdfSbufRecycle(data); 806 HdfSbbufRecycle(reply); 807 HdfIoServiceRecycle(serv); 808 return ret; 809 } 810 ``` 811 8126. 用户态接收该驱动上报的消息。 813 814 1. 用户态编写驱动上报消息的处理函数。 815 816 ```c 817 static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data) 818 { 819 OsalTimespec time; 820 OsalGetTime(&time); 821 HDF_LOGI("%{public}s received event at %{public}llu.%{public}llu", (char *)priv, time.sec, time.usec); 822 823 const char *string = HdfSbufReadString(data); 824 if (string == NULL) { 825 HDF_LOGE("fail to read string in event data"); 826 return HDF_FAILURE; 827 } 828 HDF_LOGI("%{public}s: dev event received: %{public}d %{public}s", (char *)priv, id, string); 829 return HDF_SUCCESS; 830 } 831 ``` 832 833 2. 用户态注册接收驱动上报消息的操作方法。 834 835 ```c 836 int RegisterListen() 837 { 838 struct HdfIoService *serv = HdfIoServiceBind("sample_driver"); 839 if (serv == NULL) { 840 HDF_LOGE("fail to get service"); 841 return HDF_FAILURE; 842 } 843 static struct HdfDevEventlistener listener = { 844 .callBack = OnDevEventReceived, 845 .priv ="Service0" 846 }; 847 if (HdfDeviceRegisterEventListener(serv, &listener) != 0) { 848 HDF_LOGE("fail to register event listener"); 849 return HDF_FAILURE; 850 } 851 ...... 852 HdfDeviceUnregisterEventListener(serv, &listener); 853 HdfIoServiceRecycle(serv); 854 return HDF_SUCCESS; 855 } 856 ``` 857 858 3. 驱动上报事件。 859 860 ```c 861 int32_t SampleDriverDispatch(HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) 862 { 863 ... // process api call here 864 return HdfDeviceSendEvent(client->device, cmdCode, data); 865 } 866 ``` 867 868### 驱动服务管理开发 869 870驱动服务管理的开发包括驱动服务的编写、绑定、获取或者订阅,详细步骤如下。 871 872#### 驱动服务编写 873 874```c 875// 驱动服务结构的定义 876struct ISampleDriverService { 877 struct IDeviceIoService ioService; // 服务结构的首个成员必须是IDeviceIoService类型的成员。 878 int32_t (*ServiceA)(void); // 驱动的第一个服务接口。 879 int32_t (*ServiceB)(uint32_t inputCode); // 驱动的第二个服务接口,有多个可以依次往下累加。 880}; 881 882// 驱动服务接口的实现 883int32_t SampleDriverServiceA(void) 884{ 885 // 驱动开发者实现业务逻辑 886 return HDF_SUCCESS; 887} 888 889int32_t SampleDriverServiceB(uint32_t inputCode) 890{ 891 // 驱动开发者实现业务逻辑 892 return HDF_SUCCESS; 893} 894``` 895 896#### 驱动服务绑定 897 898开发者实现HdfDriverEntry中的Bind指针函数,如下的SampleDriverBind,把驱动服务绑定到HDF框架中。 899 900```c 901int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject) 902{ 903 // deviceObject为HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口。 904 if (deviceObject == NULL) { 905 HDF_LOGE("Sample device object is null!"); 906 return HDF_FAILURE; 907 } 908 static struct ISampleDriverService sampleDriverA = { 909 .ServiceA = SampleDriverServiceA, 910 .ServiceB = SampleDriverServiceB, 911 }; 912 deviceObject->service = &sampleDriverA.ioService; 913 return HDF_SUCCESS; 914} 915``` 916 917#### 驱动服务获取 918 919应用程序开发者获取驱动服务有两种方式:通过HDF接口直接获取和通过HDF提供的订阅机制获取。 920 921##### 通过HDF接口直接获取 922 923当驱动服务获取者明确驱动已经加载完成时,获取该驱动的服务可以通过HDF框架提供的能力接口直接获取,如下所示: 924 925```c 926const struct ISampleDriverService *sampleService = 927 (const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver"); 928if (sampleService == NULL) { 929 return HDF_FAILURE; 930} 931sampleService->ServiceA(); 932sampleService->ServiceB(5); 933``` 934 935##### 通过HDF提供的订阅机制获取 936 937当内核态驱动服务获取者对驱动(同一个host)加载的时机不感知时,可以通过HDF框架提供的订阅机制来订阅该驱动服务。当该驱动加载完成时,HDF框架会将被订阅的驱动服务发布给订阅者(驱动服务获取者),实现方式如下所示: 938 939```c 940// 订阅回调函数的编写,当被订阅的驱动加载完成后,HDF框架会将被订阅驱动的服务发布给订阅者,通过这个回调函数给订阅者使用。 941// object为订阅者的私有数据,service为被订阅的服务对象。 942int32_t TestDriverSubCallBack(struct HdfDeviceObject *deviceObject, const struct HdfObject *service) 943{ 944 const struct ISampleDriverService *sampleService = 945 (const struct ISampleDriverService *)service; 946 if (sampleService == NULL) { 947 return HDF_FAILURE; 948 } 949 sampleService->ServiceA(); 950 sampleService->ServiceB(5); 951} 952// 订阅过程的实现 953int32_t TestDriverInit(struct HdfDeviceObject *deviceObject) 954{ 955 if (deviceObject == NULL) { 956 HDF_LOGE("Test driver init failed, deviceObject is null!"); 957 return HDF_FAILURE; 958 } 959 struct SubscriberCallback callBack; 960 callBack.deviceObject = deviceObject; 961 callBack.OnServiceConnected = TestDriverSubCallBack; 962 int32_t ret = HdfDeviceSubscribeService(deviceObject, "sample_driver", callBack); 963 if (ret != HDF_SUCCESS) { 964 HDF_LOGE("Test driver subscribe sample driver failed!"); 965 } 966 return ret; 967} 968``` 969 970## HDF开发实例 971 972下面基于HDF框架,提供一个完整的样例,包含配置文件的添加,驱动代码的实现以及用户态程序和驱动交互的流程。 973 974### 添加配置 975 976在HDF框架的配置文件(例如vendor/hisilicon/xxx/hdf_config/device_info)中添加该驱动的配置信息,如下所示: 977 978``` 979root { 980 device_info { 981 match_attr = "hdf_manager"; 982 template host { 983 hostName = ""; 984 priority = 100; 985 template device { 986 template deviceNode { 987 policy = 0; 988 priority = 100; 989 preload = 0; 990 permission = 0664; 991 moduleName = ""; 992 serviceName = ""; 993 deviceMatchAttr = ""; 994 } 995 } 996 } 997 sample_host :: host { 998 hostName = "sample_host"; 999 sample_device :: device { 1000 device0 :: deviceNode { 1001 policy = 2; 1002 priority = 100; 1003 preload = 1; 1004 permission = 0664; 1005 moduleName = "sample_driver"; 1006 serviceName = "sample_service"; 1007 } 1008 } 1009 } 1010 } 1011} 1012``` 1013 1014### 编写驱动代码 1015 1016基于HDF框架编写的sample驱动代码如下: 1017 1018```c 1019#include <fcntl.h> 1020#include <sys/stat.h> 1021#include <sys/ioctl.h> 1022#include "hdf_log.h" 1023#include "hdf_base.h" 1024#include "hdf_device_desc.h" 1025 1026#define HDF_LOG_TAG sample_driver 1027 1028#define SAMPLE_WRITE_READ 123 1029 1030static int32_t HdfSampleDriverDispatch( 1031 struct HdfDeviceIoClient *client, int id, struct HdfSBuf *data, struct HdfSBuf *reply) 1032{ 1033 HDF_LOGI("%{public}s: received cmd %{public}d", __func__, id); 1034 if (id == SAMPLE_WRITE_READ) { 1035 const char *readData = HdfSbufReadString(data); 1036 if (readData != NULL) { 1037 HDF_LOGE("%{public}s: read data is: %{public}s", __func__, readData); 1038 } 1039 if (!HdfSbufWriteInt32(reply, INT32_MAX)) { 1040 HDF_LOGE("%{public}s: reply int32 fail", __func__); 1041 } 1042 return HdfDeviceSendEvent(client->device, id, data); 1043 } 1044 return HDF_FAILURE; 1045} 1046 1047static void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject) 1048{ 1049 // release resources here 1050 return; 1051} 1052 1053static int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject) 1054{ 1055 if (deviceObject == NULL) { 1056 return HDF_FAILURE; 1057 } 1058 static struct IDeviceIoService testService = { 1059 .Dispatch = HdfSampleDriverDispatch, 1060 }; 1061 deviceObject->service = &testService; 1062 return HDF_SUCCESS; 1063} 1064 1065static int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject) 1066{ 1067 if (deviceObject == NULL) { 1068 HDF_LOGE("%{public}s::ptr is null!", __func__); 1069 return HDF_FAILURE; 1070 } 1071 HDF_LOGI("Sample driver Init success"); 1072 return HDF_SUCCESS; 1073} 1074 1075static struct HdfDriverEntry g_sampleDriverEntry = { 1076 .moduleVersion = 1, 1077 .moduleName = "sample_driver", 1078 .Bind = HdfSampleDriverBind, 1079 .Init = HdfSampleDriverInit, 1080 .Release = HdfSampleDriverRelease, 1081}; 1082 1083HDF_INIT(g_sampleDriverEntry); 1084``` 1085 1086### 编写用户程序和驱动交互代码 1087 1088基于HDF框架编写的用户态程序和驱动交互的代码如下(代码可以放在目录drivers/hdf_core/adapter/uhdf下面编译,BUILD.gn可以参考drivers/hdf_core/framework/sample/platform/uart/dev/BUILD.gn): 1089 1090```c 1091#include <fcntl.h> 1092#include <sys/stat.h> 1093#include <sys/ioctl.h> 1094#include <unistd.h> 1095#include "hdf_log.h" 1096#include "hdf_sbuf.h" 1097#include "hdf_io_service_if.h" 1098 1099#define HDF_LOG_TAG sample_test 1100#define SAMPLE_SERVICE_NAME "sample_service" 1101 1102#define SAMPLE_WRITE_READ 123 1103 1104int g_replyFlag = 0; 1105 1106static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data) 1107{ 1108 const char *string = HdfSbufReadString(data); 1109 if (string == NULL) { 1110 HDF_LOGE("fail to read string in event data"); 1111 g_replyFlag = 1; 1112 return HDF_FAILURE; 1113 } 1114 HDF_LOGI("%{public}s: dev event received: %{public}u %{public}s", (char *)priv, id, string); 1115 g_replyFlag = 1; 1116 return HDF_SUCCESS; 1117} 1118 1119static int SendEvent(struct HdfIoService *serv, char *eventData) 1120{ 1121 int ret = 0; 1122 struct HdfSBuf *data = HdfSbufObtainDefaultSize(); 1123 if (data == NULL) { 1124 HDF_LOGE("fail to obtain sbuf data"); 1125 return 1; 1126 } 1127 1128 struct HdfSBuf *reply = HdfSbufObtainDefaultSize(); 1129 if (reply == NULL) { 1130 HDF_LOGE("fail to obtain sbuf reply"); 1131 ret = HDF_DEV_ERR_NO_MEMORY; 1132 goto out; 1133 } 1134 1135 if (!HdfSbufWriteString(data, eventData)) { 1136 HDF_LOGE("fail to write sbuf"); 1137 ret = HDF_FAILURE; 1138 goto out; 1139 } 1140 1141 ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply); 1142 if (ret != HDF_SUCCESS) { 1143 HDF_LOGE("fail to send service call"); 1144 goto out; 1145 } 1146 1147 int replyData = 0; 1148 if (!HdfSbufReadInt32(reply, &replyData)) { 1149 HDF_LOGE("fail to get service call reply"); 1150 ret = HDF_ERR_INVALID_OBJECT; 1151 goto out; 1152 } 1153 HDF_LOGI("Get reply is: %{public}d", replyData); 1154out: 1155 HdfSbufRecycle(data); 1156 HdfSbufRecycle(reply); 1157 return ret; 1158} 1159 1160int main() 1161{ 1162 char *sendData = "default event info"; 1163 struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME); 1164 if (serv == NULL) { 1165 HDF_LOGE("fail to get service %s", SAMPLE_SERVICE_NAME); 1166 return HDF_FAILURE; 1167 } 1168 1169 static struct HdfDevEventlistener listener = { 1170 .callBack = OnDevEventReceived, 1171 .priv ="Service0" 1172 }; 1173 1174 if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) { 1175 HDF_LOGE("fail to register event listener"); 1176 return HDF_FAILURE; 1177 } 1178 if (SendEvent(serv, sendData)) { 1179 HDF_LOGE("fail to send event"); 1180 return HDF_FAILURE; 1181 } 1182 1183 while (g_replyFlag == 0) { 1184 sleep(1); 1185 } 1186 1187 if (HdfDeviceUnregisterEventListener(serv, &listener)) { 1188 HDF_LOGE("fail to unregister listener"); 1189 return HDF_FAILURE; 1190 } 1191 1192 HdfIoServiceRecycle(serv); 1193 return HDF_SUCCESS; 1194} 1195``` 1196 1197>  **说明:** 1198> 用户态应用程序使用了HDF框架中的消息发送接口,因此在编译用户态程序的过程中需要依赖HDF框架对外提供的hdf_core和osal的动态库,在gn编译文件中添加如下依赖项: 1199> 1200> deps = [ 1201> 1202> "//drivers/hdf_core/adapter/uhdf/manager:hdf_core", 1203> 1204> "//drivers/hdf_core/adapter/uhdf/posix:hdf_posix_osal", 1205> 1206> ] 1207