1# 标准系统方案之瑞芯微RK3568移植案例 2 3本文章是基于瑞芯微RK3568芯片的DAYU200开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。 4 5## 产品配置和目录规划 6 7### 产品配置 8 9在产品`//productdefine/common/device`目录下创建以rk3568名字命名的json文件,并指定CPU的架构。`//productdefine/common/device/rk3568.json`配置如下: 10 11``` 12{ 13 "device_name": "rk3568", 14 "device_company": "rockchip", 15 "target_os": "ohos", 16 "target_cpu": "arm", 17 "kernel_version": "", 18 "device_build_path": "device/board/hihope/rk3568", 19 "enable_ramdisk": true, //是否支持ramdisk二级启动 20 "build_selinux": true // 是否支持selinux权限管理 21} 22``` 23 24在`//productdefine/common/products`目录下创建以产品名命名的rk3568.json文件。该文件用于描述产品所使用的SOC 以及所需的子系统。配置如下 25 26``` 27{ 28 "product_name": "rk3568", 29 "product_company" : "hihope", 30 "product_device": "rk3568", 31 "version": "2.0", 32 "type": "standard", 33 "parts":{ 34 "ace:ace_engine_standard":{}, 35 "ace:napi":{}, 36 ... 37 "xts:phone_tests":{} 38 } 39} 40``` 41 42主要的配置内容包括: 43 441. product_device:配置所使用的SOC。 452. type:配置系统的级别, 这里直接standard即可。 463. parts:系统需要启用的子系统。子系统可以简单理解为一块独立构建的功能块。 47 48已定义的子系统可以在`//build/subsystem_config.json`中找到。当然你也可以定制子系统。 49 50这里建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不适合rk3568。 51 52### 目录规划 53 54参考[Board和SoC解耦的设计思路](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md),并把芯片适配目录规划为: 55 56``` 57device 58├── board --- 单板厂商目录 59│ └── hihope --- 单板厂商名字: 60│ └── rk3568 --- 单板名:rk3568,主要放置开发板相关的驱动业务代码 61└── soc --- SoC厂商目录 62 └── rockchip --- SoC厂商名字:rockchip 63 └── rk3568 --- SoC Series名:rk3568,主要为芯片原厂提供的一些方案,以及闭源库等 64 65 66``` 67 68``` 69vendor 70└── hihope 71 └── rk3568 --- 产品名字:产品、hcs以及demo相关 72``` 73 74## **内核启动** 75 76### 二级启动 77 78二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。 79 80Rk3568适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作: 81 821.使能二级启动 83 84 在productdefine/common/device/rk3568.json 中使能enable_ramdisk。 85 86 ``` 87 { 88 "device_name": "rk3568", 89 "device_company": "hihope", 90 "target_os": "ohos", 91 "target_cpu": "arm", 92 "kernel_version": "", 93 "device_build_path": "device/hihope/build", 94 "enable_ramdisk": true, 95 "build_selinux": true 96 } 97 ``` 98 992.把主线编译出来的ramdsik.img 打包到boot_linux.img 100 101配置: 102 103由于rk 启动uboot 支持从ramdisk 启动,只需要在打包boot_linux.img 的配置文件中增加ramdisk.img ,因此没有使用主线的its格式,具体配置就是在内核编译脚本make-ohos.sh 中增加: 104 105``` 106function make_extlinux_conf() 107{ 108 dtb_path=$1 109 uart=$2 110 image=$3 111 112 echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF} 113 echo " kernel /extlinux/${image}" >> ${EXTLINUX_CONF} 114 echo " fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF} 115 if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then 116 echo " initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF} 117 fi 118 cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4" 119 echo " ${cmdline}" >> ${EXTLINUX_CONF} 120} 121``` 122 123### 打包 124 125增加了打包boot镜像的脚本make-boot.sh,供编译完ramdisk,打包boot 镜像时调用, 主要内容: 126 127``` 128genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img 129``` 130 131调用make-boot.sh 的修改可以参考如下pr: 132 133https://gitee.com/openharmony/build/pulls/569/files 134 135### INIT配置 136 137init相关配置请参考[启动子系统的规范要求](https://gitee.com/openharmony/docs/blob/master/zh-cn/readme/%E5%90%AF%E5%8A%A8%E6%81%A2%E5%A4%8D%E5%AD%90%E7%B3%BB%E7%BB%9F.md)即可 138 139## **音频** 140 141### RK3568 Audio总体结构图 142 143![dayu200-audio-01.png](figures/dayu200/dayu200-audio-01.png) 144 145### ADM适配方案介绍 146 147#### RK3568平台适配ADM框架图 148 149![](figures/dayu200/dayu200-audio-02.png) 150 1511. ADM Drivers adapter 152 153 主要完成Codec/DMA/I2S驱动注册,使得ADM可以加载驱动节点;并注册ADM与Drivers交互的接口函数 154 1552. ADM Drivers impl 156 157 主要完成ADM Drivers adapter接口函数的实现,以及Codec_config.hcs/dai_config.hcs等配置信息的获取,并注册到对应的设备 158 1593. Linux Drivers 160 161 ADM Drivers impl可以直接阅读硬件手册,完成驱动端到端的配置;也可以借用Linux原生驱动实现与接口,减少开发者工作量。 162 163#### 目录结构 164 165``` 166./device/board/hihope/rk3568/audio_drivers 167├── codec 168│ └── rk809_codec 169│ ├── include 170│ │ ├── rk809_codec_impl.h 171│ │ └── rk817_codec.h 172│ └── src 173│ ├── rk809_codec_adapter.c 174│ ├── rk809_codec_linux_driver.c 175│ └── rk809_codec_ops.c 176├── dai 177│ ├── include 178│ │ ├── rk3568_dai_linux.h 179│ │ └── rk3568_dai_ops.h 180│ └── src 181│ ├── rk3568_dai_adapter.c 182│ ├── rk3568_dai_linux_driver.c 183│ └── rk3568_dai_ops.c 184├── dsp 185│ ├── include 186│ │ └── rk3568_dsp_ops.h 187│ └── src 188│ ├── rk3568_dsp_adapter.c 189│ └── rk3568_dsp_ops.c 190├── include 191│ ├── audio_device_log.h 192│ └── rk3568_audio_common.h 193└── soc 194 ├── include 195 │ └── rk3568_dma_ops.h 196 └── src 197 ├── rk3568_dma_adapter.c 198 └── rk3568_dma_ops.c 199``` 200 201### RK3568适配ADM详细过程 202 203#### 梳理平台Audio框架 204 205梳理目标平台的Audio结构,明确数据流与控制流通路。 206 2071. 针对RK3568平台,Audio的结构相对简单见RK3568 Audio总体结构图,Codec作为一个独立设备。I2C完成对设备的控制,I2S完成Codec设备与CPU之间的交互。 2082. 结合原理图整理I2S通道号,对应的引脚编号;I2C的通道号,地址等硬件信息。 2093. 获取Codec对应的datasheet,以及RK3568平台的Datasheet(包含I2S/DMA通道等寄存器的介绍)。 210 211#### 熟悉并了解ADM结构 212 213ADM结构框图如下,Audio Peripheral Drivers和Platform Drivers为平台适配需要完成的工作。 214 215![dayu200-audio-03.png](figures/dayu200/dayu200-audio-03.png) 216 217结合第1步梳理出来的Audio结构分析,Audio Peripheral Drivers包含Rk809的驱动,Platform Drivers包含DMA驱动和I2S驱动。 218 219| 需要适配的驱动 | ADM对应模块 | 接口文件路径 | 220| -------------- | ----------- | ---------------------------------------------------- | 221| RK809驱动 | Accessory | drivers/framework/include/audio/audio_accessory_if.h | 222| DMA驱动 | platform | drivers/framework/include/audio/audio_platform_if.h | 223| I2S驱动 | DAI | drivers/framework/include/audio/audio_dai_if.h.h | 224 225#### 搭建驱动代码框架 226 227##### 配置HCS文件 228 229在device_info.hcs文件中Audio下注册驱动节点 230 231```c 232 audio :: host { 233 hostName = "audio_host"; 234 priority = 60; 235 device_dai0 :: device { 236 device0 :: deviceNode { 237 policy = 1; 238 priority = 50; 239 preload = 0; 240 permission = 0666; 241 moduleName = "DAI_RK3568"; 242 serviceName = "dai_service"; 243 deviceMatchAttr = "hdf_dai_driver"; 244 } 245 } 246 device_codec :: device { 247 device0 :: deviceNode { 248 policy = 1; 249 priority = 50; 250 preload = 0; 251 permission = 0666; 252 moduleName = "CODEC_RK809"; 253 serviceName = "codec_service_0"; 254 deviceMatchAttr = "hdf_codec_driver"; 255 } 256 } 257 device_codec_ex :: device { 258 device0 :: deviceNode { 259 policy = 1; 260 priority = 50; 261 preload = 0; 262 permission = 0666; 263 moduleName = "CODEC_RK817"; 264 serviceName = "codec_service_1"; 265 deviceMatchAttr = "hdf_codec_driver_ex"; 266 } 267 } 268 device_dsp :: device { 269 device0 :: deviceNode { 270 policy = 1; 271 priority = 50; 272 preload = 0; 273 permission = 0666; 274 moduleName = "DSP_RK3568"; 275 serviceName = "dsp_service_0"; 276 deviceMatchAttr = "hdf_dsp_driver"; 277 } 278 } 279 device_dma :: device { 280 device0 :: deviceNode { 281 policy = 1; 282 priority = 50; 283 preload = 0; 284 permission = 0666; 285 moduleName = "DMA_RK3568"; 286 serviceName = "dma_service_0"; 287 deviceMatchAttr = "hdf_dma_driver"; 288 } 289 } 290 ...... 291 } 292 293``` 294 295根据接入的设备,选择Codec节点还是Accessory节点,配置硬件设备对应的私有属性(包含寄存器首地址,相关control寄存器地址)涉及Codec_config.hcs和DAI_config.hcs 296 297配置相关介绍见[Audio](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/driver/driver-peripherals-audio-des.md) hcs配置章节以及ADM框架的audio_parse模块代码。 298 299##### codec/accessory模块 300 3011. 将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致 302 303 ``` 304 struct HdfDriverEntry g_codecDriverEntry = { 305 .moduleVersion = 1, 306 .moduleName = "CODEC_HI3516", 307 .Bind = CodecDriverBind, 308 .Init = CodecDriverInit, 309 .Release = CodecDriverRelease, 310 }; 311 HDF_INIT(g_codecDriverEntry); 312 ``` 313 3142. Codec模块需要填充: 315 316 g_codecData:codec设备的操作函数集和私有数据集。 317 318 g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。 319 320 g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。 321 3223. 完成 bind、init和release函数的实现 323 3244. 验证 325 326 在bind和init函数加调试日志,编译版本并获取系统系统日志: 327 328 ``` 329 [ 1.548624] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:258]: enter 330 [ 1.548635] [E/"rk809_codec_adapter"] [Rk809DriverBind][line:260]: success 331 [ 1.548655] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:270]: enter 332 [ 1.549050] [E/"rk809_codec_adapter"] [GetServiceName][line:226]: enter 333 [ 1.549061] [E/"rk809_codec_adapter"] [GetServiceName][line:250]: success 334 [ 1.549072] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:316]: g_chip->accessory.drvAccessoryName = codec_service_1 335 [ 1.549085] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [accessory_dai] success. 336 [ 1.549096] [E/audio_core] [AudioRegisterAccessory][line:120]: Register [codec_service_1] success. 337 [ 1.549107] [E/"rk809_codec_adapter"] [Rk809DriverInit][line:323]: success! 338 ``` 339 340 341 342##### DAI模块 343 3441. 将I2S驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致 345 346 ```c 347 struct HdfDriverEntry g_daiDriverEntry = { 348 .moduleVersion = 1, 349 .moduleName = "DAI_RK3568", 350 .Bind = DaiDriverBind, 351 .Init = DaiDriverInit, 352 .Release = DaiDriverRelease, 353 }; 354 HDF_INIT(g_daiDriverEntry); 355 ``` 356 3572. DAI模块填充: 358 359 ```c 360 struct AudioDaiOps g_daiDeviceOps = { 361 .Startup = Rk3568DaiStartup, 362 .HwParams = Rk3568DaiHwParams, 363 .Trigger = Rk3568NormalTrigger, 364 }; 365 366 struct DaiData g_daiData = { 367 .Read = Rk3568DeviceReadReg, 368 .Write = Rk3568DeviceWriteReg, 369 .DaiInit = Rk3568DaiDeviceInit, 370 .ops = &g_daiDeviceOps, 371 }; 372 ``` 373 3743. 完成 bind、init和release函数的实现 375 3764. 验证 377 378 在bind/init函数加调试日志,编译版本并获取系统系统日志 379 380 ``` 381 [ 1.549193] [I/device_node] launch devnode dai_service 382 [ 1.549204] [E/HDF_LOG_TAG] [DaiDriverBind][line:38]: entry! 383 [ 1.549216] [E/HDF_LOG_TAG] [DaiDriverBind][line:55]: success! 384 [ 1.549504] [E/audio_core] [AudioSocRegisterDai][line:86]: Register [dai_service] success. 385 [ 1.549515] [E/HDF_LOG_TAG] [DaiDriverInit][line:116]: success. 386 ``` 387 388##### Platform模块 389 3901. 将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致 391 392 ``` 393 struct HdfDriverEntry g_platformDriverEntry = { 394 .moduleVersion = 1, 395 .moduleName = "DMA_RK3568", 396 .Bind = PlatformDriverBind, 397 .Init = PlatformDriverInit, 398 .Release = PlatformDriverRelease, 399 }; 400 HDF_INIT(g_platformDriverEntry); 401 ``` 402 4032. DMA模块需要填充: 404 405 ```c 406 struct AudioDmaOps g_dmaDeviceOps = { 407 .DmaBufAlloc = Rk3568DmaBufAlloc, 408 .DmaBufFree = Rk3568DmaBufFree, 409 .DmaRequestChannel = Rk3568DmaRequestChannel, 410 .DmaConfigChannel = Rk3568DmaConfigChannel, 411 .DmaPrep = Rk3568DmaPrep, 412 .DmaSubmit = Rk3568DmaSubmit, 413 .DmaPending = Rk3568DmaPending, 414 .DmaPause = Rk3568DmaPause, 415 .DmaResume = Rk3568DmaResume, 416 .DmaPointer = Rk3568PcmPointer, 417 }; 418 419 struct PlatformData g_platformData = { 420 .PlatformInit = AudioDmaDeviceInit, 421 .ops = &g_dmaDeviceOps, 422 }; 423 ``` 424 4253. 完成 bind、init和release函数的实现 426 4274. 验证 428 429 在bind和init函数加调试日志,编译版本并获取系统系统日志 430 431 ``` 432 [ 1.548469] [E/rk3568_platform_adapter] [PlatformDriverBind][line:42]: entry! 433 [ 1.548481] [E/rk3568_platform_adapter] [PlatformDriverBind][line:58]: success! 434 [ 1.548492] [E/rk3568_platform_adapter] [PlatformDriverInit][line:100]: entry. 435 [ 1.548504] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:67]: entry! 436 [ 1.548515] [E/rk3568_platform_adapter] [PlatformGetServiceName][line:91]: success! 437 [ 1.548528] [E/audio_core] [AudioSocRegisterPlatform][line:63]: Register [dma_service_0] success. 438 [ 1.548536] [E/rk3568_platform_adapter] [PlatformDriverInit][line:119]: success. 439 ``` 440 441#### 驱动适配 442 443##### code/accessory模块 444 4451. 读取DTS文件,获取到对应设备节点,使用Linux原生的驱动注册函数,获取到对应device。 446 447 ``` 448 static int rk817_platform_probe(struct platform_device *pdev) { 449 rk817_pdev = pdev; 450 dev_info(&pdev->dev, "got rk817-codec platform_device"); 451 return 0; 452 } 453 454 static struct platform_driver rk817_codec_driver = { 455 .driver = { 456 .name = "rk817-codec", // codec node in dts file 457 .of_match_table = rk817_codec_dt_ids, 458 }, 459 .probe = rk817_platform_probe, 460 .remove = rk817_platform_remove, 461 }; 462 ``` 463 4642. 读写寄存器函数封装 465 根据上述获取到的device, 使用Linux的regmap函数,开发者不需要获取模块的基地址 466 获取rk817的regmap代码段 467 468 ``` 469 g_chip = devm_kzalloc(&rk817_pdev->dev, sizeof(struct Rk809ChipData), GFP_KERNEL); 470 if (!g_chip) { 471 AUDIO_DEVICE_LOG_ERR("no memory"); 472 return HDF_ERR_MALLOC_FAIL; 473 } 474 g_chip->pdev = rk817_pdev; 475 476 struct rk808 *rk808 = dev_get_drvdata(g_chip->pdev->dev.parent); 477 if (!rk808) { 478 AUDIO_DEVICE_LOG_ERR("%s: rk808 is NULL\n", __func__); 479 ret = HDF_FAILURE; 480 RK809ChipRelease(); 481 return ret; 482 } 483 g_chip->regmap = devm_regmap_init_i2c(rk808->i2c, 484 &rk817_codec_regmap_config); 485 if (IS_ERR(g_chip->regmap)) { 486 AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap)); 487 RK809ChipRelease(); 488 return ret; 489 } 490 ``` 491 492 寄存器读写函数代码段 493 494 ``` 495 int32_t Rk809DeviceRegRead(uint32_t reg, uint32_t *val) 496 { 497 if (regmap_read(g_chip->regmap, reg, val)) { 498 AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg); 499 return HDF_FAILURE; 500 } 501 502 return HDF_SUCCESS; 503 } 504 505 int32_t Rk809DeviceRegWrite(uint32_t reg, uint32_t value) { 506 if (regmap_write(g_chip->regmap, reg, value)) { 507 AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value); 508 return HDF_FAILURE; 509 } 510 511 return HDF_SUCCESS; 512 } 513 514 int32_t Rk809DeviceRegUpdatebits(uint32_t reg, uint32_t mask, uint32_t value) { 515 if (regmap_update_bits(g_chip->regmap, reg, mask, value)) { 516 AUDIO_DRIVER_LOG_ERR("update register bits fail: [%04x] = %04x", reg, value); 517 return HDF_FAILURE; 518 } 519 520 return HDF_SUCCESS; 521 } 522 ``` 523 5243. 寄存器初始化函数 525 526 因为使用Linux的regmap函数,所以需要自行定义RegDefaultInit函数,读取hcs中initSeqConfig的寄存器以及数值来进行配置 527 528 RK809RegDefaultInit代码段 529 530 ```c 531 int32_t RK809RegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup) 532 { 533 int32_t i; 534 struct AudioAddrConfig *regAttr = NULL; 535 536 if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL || 537 regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) { 538 AUDIO_DEVICE_LOG_ERR("input invalid parameter."); 539 540 return HDF_ERR_INVALID_PARAM; 541 } 542 543 regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem; 544 545 for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) { 546 Rk809DeviceRegWrite(regAttr[i].addr, regAttr[i].value); 547 } 548 549 return HDF_SUCCESS; 550 } 551 ``` 552 5534. 封装控制接口的读写函数 554 555 设置控制读写函数为RK809CodecReadReg和RK809CodecWriteReg 556 557 ```c 558 struct CodecData g_rk809Data = { 559 .Init = Rk809DeviceInit, 560 .Read = RK809CodecReadReg, 561 .Write = RK809CodecWriteReg, 562 }; 563 564 struct AudioDaiOps g_rk809DaiDeviceOps = { 565 .Startup = Rk809DaiStartup, 566 .HwParams = Rk809DaiHwParams, 567 .Trigger = RK809NormalTrigger, 568 }; 569 570 struct DaiData g_rk809DaiData = { 571 .DaiInit = Rk809DaiDeviceInit, 572 .ops = &g_rk809DaiDeviceOps, 573 }; 574 ``` 575 576 封装控制接口的读写函数 577 578 因为原来的读写原型,涉及三个参数(unsigned long virtualAddress,uint32_t reg, uint32_t *val),其中virtualAddress我们并不需要用到,所以封装个接口即可,封装如下 579 580 ```c 581 int32_t RK809CodecReadReg(unsigned long virtualAddress,uint32_t reg, uint32_t *val) 582 { 583 if (val == NULL) { 584 AUDIO_DRIVER_LOG_ERR("param val is null."); 585 return HDF_FAILURE; 586 } 587 if (Rk809DeviceRegRead(reg, val)) { 588 AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg); 589 return HDF_FAILURE; 590 } 591 ADM_LOG_ERR("read reg 0x[%02x] = 0x[%02x]",reg,*val); 592 return HDF_SUCCESS; 593 } 594 595 int32_t RK809CodecWriteReg(unsigned long virtualAddress,uint32_t reg, uint32_t value) 596 { 597 if (Rk809DeviceRegWrite(reg, value)) { 598 AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value); 599 return HDF_FAILURE; 600 } 601 ADM_LOG_ERR("write reg 0x[%02x] = 0x[%02x]",reg,value); 602 return HDF_SUCCESS; 603 } 604 ``` 605 6065. 其他ops函数 607 608 - Rk809DeviceInit,读取hcs文件,初始化Codec寄存器,同时将对应的control配置(/* reg, rreg, shift, rshift, min, max, mask, invert, value */添加到kcontrol,便于dispatch contro进行控制 609 - Rk809DaiStartup, 读取hcs文件,配置可选设备(codec/accessory)的控制寄存器 610 - Rk809DaiHwParams, 根据hal下发的audio attrs(采样率、format、channel等),配置对应的寄存器 611 - RK809NormalTrigger,根据hal下发的操作命令码,操作对应的寄存器,实现Codec的启动停止、录音和放音的切换等 612 613##### DAI(i2s)模块 614 6151. 读写寄存器函数 616 思路与Codec模块的一致,读取Linux DTS文件,使用Linux的regmap函数完成寄存器的读写操作 617 618 ```c 619 int32_t Rk3568DeviceReadReg(unsigned long regBase, uint32_t reg, uint32_t *val) 620 { 621 AUDIO_DEVICE_LOG_ERR("entry"); 622 (void)regBase; 623 struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000"); 624 if(dmaOfNode == NULL) { 625 AUDIO_DEVICE_LOG_ERR("of_node is NULL."); 626 } 627 struct platform_device *platformdev = of_find_device_by_node(dmaOfNode); 628 struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev); 629 630 (void)regBase; 631 if (regmap_read(i2s_tdm->regmap, reg, val)) { 632 AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", reg); 633 return HDF_FAILURE; 634 } 635 return HDF_SUCCESS; 636 } 637 638 int32_t Rk3568DeviceWriteReg(unsigned long regBase, uint32_t reg, uint32_t value) 639 { 640 AUDIO_DEVICE_LOG_ERR("entry"); 641 (void)regBase; 642 struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000"); 643 if(dmaOfNode == NULL) { 644 AUDIO_DEVICE_LOG_ERR("of_node is NULL."); 645 } 646 struct platform_device *platformdev = of_find_device_by_node(dmaOfNode); 647 struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev); 648 if (regmap_write(i2s_tdm->regmap, reg, value)) { 649 AUDIO_DEVICE_LOG_ERR("write register fail: [%04x] = %04x", reg, value); 650 return HDF_FAILURE; 651 } 652 return HDF_SUCCESS; 653 } 654 ``` 655 6562. 其他ops函数 657 658 - Rk3568DaiDeviceInit 659 原始框架,主要完成DAI_config.hcs参数列表的读取,与HwParams结合,完成参数的设置。 660 661 - Rk3568DaiHwParams 662 主要完成I2S MCLK/BCLK/LRCLK时钟配置。 663 664 1. 根据不同采样率计算MCLK 665 666 ```c 667 int32_t RK3568I2sTdmSetSysClk(struct rk3568_i2s_tdm_dev *i2s_tdm, const struct AudioPcmHwParams *param) 668 { 669 /* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */ 670 uint32_t sampleRate = param->rate; 671 uint32_t mclk_parent_freq = 0; 672 switch (sampleRate) { 673 case AUDIO_DEVICE_SAMPLE_RATE_8000: 674 case AUDIO_DEVICE_SAMPLE_RATE_16000: 675 case AUDIO_DEVICE_SAMPLE_RATE_24000: 676 case AUDIO_DEVICE_SAMPLE_RATE_32000: 677 case AUDIO_DEVICE_SAMPLE_RATE_48000: 678 case AUDIO_DEVICE_SAMPLE_RATE_64000: 679 case AUDIO_DEVICE_SAMPLE_RATE_96000: 680 mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_192000; 681 break; 682 case AUDIO_DEVICE_SAMPLE_RATE_11025: 683 case AUDIO_DEVICE_SAMPLE_RATE_22050: 684 case AUDIO_DEVICE_SAMPLE_RATE_44100: 685 686 mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_176400; 687 break; 688 default: 689 AUDIO_DEVICE_LOG_ERR("Invalid LRCK freq: %u Hz\n", sampleRate); 690 return HDF_FAILURE; 691 } 692 i2s_tdm->mclk_tx_freq = mclk_parent_freq; 693 i2s_tdm->mclk_rx_freq = mclk_parent_freq; 694 695 return HDF_SUCCESS; 696 } 697 ``` 698 699 2. 根据获取的mclk,计算BCLK/LRclk分频系数 700 701 - Rk3568NormalTrigger 702 根据输入输出类型,以及cmd(启动/停止/暂停/恢复),完成一系列配置: 703 704 1. mclk的启停 705 2. DMA搬运的启停 706 3. 传输的启停 707 详细实现见代码,参考Linux原生I2s驱动对应接口函数 708 709 ```c 710 // 启动/恢复流程 711 if (streamType == AUDIO_RENDER_STREAM) { 712 clk_prepare_enable(i2s_tdm->mclk_tx); 713 regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, 714 I2S_DMACR_TDE_ENABLE, 715 I2S_DMACR_TDE_ENABLE); 716 } else { 717 clk_prepare_enable(i2s_tdm->mclk_rx); 718 regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, 719 I2S_DMACR_RDE_ENABLE, 720 I2S_DMACR_RDE_ENABLE); 721 if (regmap_read(i2s_tdm->regmap, I2S_DMACR, &val)) { 722 AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_DMACR); 723 return ; 724 } 725 AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_DMACR, val); 726 } 727 728 if (atomic_inc_return(&i2s_tdm->refcount) == 1) { 729 regmap_update_bits(i2s_tdm->regmap, I2S_XFER, 730 I2S_XFER_TXS_START | 731 I2S_XFER_RXS_START, 732 I2S_XFER_TXS_START | 733 I2S_XFER_RXS_START); 734 if (regmap_read(i2s_tdm->regmap, I2S_XFER, &val)) { 735 AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_XFER); 736 return ; 737 } 738 AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_XFER, val); 739 } 740 ``` 741 742 ##### Platform(DMA)模块 743 744ops函数相关函数 745 7461. Rk3568DmaBufAlloc/Rk3568DmaBufFree 747 748 获取DMA设备节点,参考I2s设备获取方式,使用系统函数dma_alloc_wc/dma_free_wc,完成DMA虚拟内存与物理内存的申请/释放 749 7502. Rk3568DmaRequestChannel 751 752 使用Linux DMA原生接口函数获取DMA传输通道,dma_request_slave_channel 753 754 ``` 755 dmaRtd->dmaChn[streamType] = dma_request_slave_channel(dmaDevice, dmaChannelNames[streamType]); 756 ``` 757 7583. Rk3568DmaConfigChannel 759 760 ``` 761 //设置通道配置参数 762 // 放音通道参数配置 763 slave_config.direction = DMA_MEM_TO_DEV; 764 slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 765 slave_config.dst_addr = I2S1_ADDR + I2S_TXDR; 766 slave_config.dst_maxburst = 8; 767 // 录音通道参数配置 768 slave_config.direction = DMA_DEV_TO_MEM; 769 slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 770 slave_config.src_addr = I2S1_ADDR + I2S_RXDR; 771 slave_config.src_maxburst = 8; 772 773 //使用Linux DMA原生接口函数完成DMA通道配置 774 ret = dmaengine_slave_config(dmaChan, &slave_config); 775 if (ret != 0) { 776 AUDIO_DEVICE_LOG_ERR("dmaengine_slave_config failed"); 777 return HDF_FAILURE; 778 } 779 ``` 780 7814. Rk3568DmaSubmit/Rk3568DmaPending 782 783 使用Linux DMA原生接口函数dmaengine_prep_dma_cyclic,初始化一个具体的周期性的DMA传输描述符dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。 784 7855. Rk3568PcmPointer 786 787第4步完成之后,ADM框架调用Rk3568PcmPointer,循环写cirBuf,计算pointer 788 789 ``` 790 791 dma_chn = dmaRtd->dmaChn[DMA_TX_CHANNEL]; 792 buf_size = data->renderBufInfo.cirBufSize; 793 dmaengine_tx_status(dma_chn, dmaRtd->cookie[DMA_TX_CHANNEL], &dma_state); 794 if (dma_state.residue) { 795 currentPointer = buf_size - dma_state.residue; 796 *pointer = BytesToFrames(data->pcmInfo.frameSize, currentPointer); 797 } else { 798 *pointer = 0; 799 } 800 ``` 801 8026. Rk3568DmaPause 803 804 使用Linux DMA原生接口函数dmaengine_terminate_async,停止DMA传输 805 806 ``` 807 dmaengine_terminate_async(dmaChan); 808 ``` 809 8107. Rk3568DmaResume 811 812 暂停使用的DMA停止函数,对应恢复,相当于重启DMA传输,执行Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成 813 814##### 适配中遇到问题与解决方案 815 8161. 播放一段时间后,停止播放,持续有尖锐的很小的声音 817 问题原因:播放停止后,Codec相关器件没有下电 818 解决方案:注册Codec的trigger函数,当接收到Cmd为Stop时,对Codec进行下电 819 8202. 播放一段时间后,停止播放,然后重新播放没有声音 821 问题原因:DMA驱动的PAUSE接口函数,并未停止DMA传输 822 解决方案:暂停状态不再使用DMA的PAUSE函数,而是使用DAM传输停止接口; 相对应的,恢复函数的业务逻辑相当于重启DMA传输,执行 Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成 823 8243. 播放存在杂音 825 问题原因:DMA数据搬运pointer位置不正确 826 解决方案:Rk3568PcmPointer函数返回值为DMA搬运的内存位置,用缓存区buf与dma_state.residue的差值计算 827 8284. 可以放音,但Mclk引脚没有时钟信号 829 问题原因:DTS文件pin-ctrl没有配置mclk的引脚 830 解决方案:修改DTS文件 831 832### Camera 833 834**基本概念** 835 836OpenHarmony相机驱动框架模型对上实现相机HDI接口,对下实现相机Pipeline模型,管理相机各个硬件设备。各层的基本概念如下。 837 8381. HDI实现层:对上实现OHOS相机标准南向接口。 839 8402. 框架层:对接HDI实现层的控制、流的转发,实现数据通路的搭建、管理相机各个硬件设备等功能。 841 8423. 适配层:屏蔽底层芯片和OS差异,支持多平台适配。 843 844### Camera驱动框架介绍 845 846#### 源码框架介绍 847 848Camera 驱动框架所在的仓为:drivers_peripheral,源码目录为:“drivers/peripheral/camera”。 849 850``` 851|-- README_zh.md 852|-- figures 853| -- logic-view-of-modules-related-to-this-repository_zh.png 854|-- hal 855| |-- BUILD.gn #Camera驱动框架构建入口 856| |-- adapter #平台适配层,适配平台 857| |-- buffer_manager 858| |-- camera.gni #定义组件所使用的全局变量 859| |-- device_manager 860| |-- hdi_impl 861| |-- include 862| |-- init #demo sample 863| |-- pipeline_core 864| |-- test #测试代码 865| |-- utils 866|-- hal_c #为海思平台提供专用C接口 867| |-- BUILD.gn 868| |-- camera.gni 869| |-- hdi_cif 870| |-- include 871|-- interfaces #HDI接口 872 |-- hdi_ipc 873|-- hdi_passthrough 874 |-- include 875``` 876 877Camera hcs文件是每个chipset可配置的。所以放在chipset相关的仓下。以rk3568为例。仓名为: vendor_hihope,源码目录为:“vendor/hihope/rk3568/hdf_config/uhdf/camera”。 878 879 ├── hdi_impl 880 │ └── camera_host_config.hcs 881 └── pipeline_core 882 ├── config.hcs 883 ├── ipp_algo_config.hcs 884 └── params.hcs 885Camera chipset 相关代码路径以3568为例仓名为:device_hihope。路径为:device/board/hihope/rk3568/camera/ 886``` 887├── BUILD.gn 888├── demo 889│ └── include 890│ └── project_camera_demo.h 891├── device_manager 892│ ├── BUILD.gn 893│ ├── include 894│ │ ├── imx600.h 895│ │ ├── project_hardware.h 896│ │ └── rkispv5.h 897│ └── src 898│ ├── imx600.cpp 899│ └── rkispv5.cpp 900├── driver_adapter 901│ └── test 902│ ├── BUILD.gn 903│ ├── unittest 904│ │ ├── include 905│ │ │ └── utest_v4l2_dev.h 906│ │ └── src 907│ │ └── utest_v4l2_dev.cpp 908│ └── v4l2_test 909│ └── include 910│ └── project_v4l2_main.h 911└── pipeline_core 912 ├── BUILD.gn 913 └── src 914 ├── ipp_algo_example 915 │ └── ipp_algo_example.c 916 └── node 917 ├── rk_codec_node.cpp 918 └── rk_codec_node.h 919``` 920 #### Camera 驱动框架配置 921 922 RK3568 配置文件路径: 923 924 “vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs”。说明:其他平台可参考RK3568适配。 925 926``` 927 hdi_server :: host { 928 hostName = "camera_host"; 929 priority = 50; 930 caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; 931 camera_device :: device { 932 device0 :: deviceNode { 933 policy = 2; 934 priority = 100; 935 moduleName = "libcamera_hdi_impl.z.so"; 936 serviceName = "camera_service"; 937 } 938 } 939 ... 940 } 941``` 942 943参数说明: 944 Host:一个host节点即为一个独立进程,如果需要独立进程,新增属于自己的host节点。 945 Policy: 服务发布策略,HDI服务请设置为“**2**” 946 moduleName: 驱动实现库名。 947 serviceName:服务名称,请保持全局唯一性。 948 949Camera_host驱动实现入口 950 951文件路径:drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp 952 953分发设备服务消息 954 cmd Id:请求消息命令字。 955 Data:其他服务或者IO请求数据。 956 Reply:存储返回消息内容数据。 957 958``` 959static int32_t CameraServiceDispatch(struct HdfDeviceIoClient *client, int cmdId, 960 struct HdfSBuf *data, struct HdfSBuf *reply) 961{ 962 HdfCameraService *hdfCameraService = CONTAINER_OF(client->device->service, HdfCameraService, ioservice); 963 return CameraHostServiceOnRemoteRequest(hdfCameraService->instance, cmdId, data, reply); 964 } 965``` 966 967 绑定设备服务:初始化设备服务对象和资源对象。 968 969``` 970int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject) 971{ 972 HDF_LOGI("HdfCameraHostDriverBind enter!"); 973 if (deviceObject == nullptr) { 974 HDF_LOGE("HdfCameraHostDriverBind: HdfDeviceObject is NULL !"); 975 return HDF_FAILURE; 976} 977``` 978 979驱动初始化函数: 探测并初始化驱动程序 980 981``` 982int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject) 983{ 984 return HDF_SUCCESS; 985} 986``` 987 988驱动资源释放函数 : 如已经绑定的设备服务对象 989 990``` 991 void HdfCameraHostDriverRelease(HdfDeviceObject *deviceObject) 992 { 993 if (deviceObject == nullptr || deviceObject->service == nullptr) { 994 HDF_LOGE("%{public}s deviceObject or deviceObject->service is NULL!", __FUNCTION__); 995 return; 996 } 997 HdfCameraService *hdfCameraService = CONTAINER_OF(deviceObject->service, HdfCameraService, ioservice); 998 if (hdfCameraService == nullptr) { 999 HDF_LOGE("%{public}s hdfCameraService is NULL!", __FUNCTION__); 1000 return; 1001 } 1002``` 1003 1004定义驱动描述符:将驱动代码注册给驱动框架。 1005 1006 1007 struct HdfDriverEntry g_cameraHostDriverEntry = { 1008 .moduleVersion = 1, 1009 .moduleName = "camera_service", 1010 .Bind = HdfCameraHostDriverBind, 1011 .Init = HdfCameraHostDriverInit, 1012 .Release = HdfCameraHostDriverRelease, 1013 }; 1014 1015 1016#### Camera配置信息介绍 1017 1018Camera模块内部,所有配置文件使用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS解析接口解析HCB文件,获取配置文件中的信息。 1019 1020 1021 hc_gen("build_camera_host_config") { 1022 sources = [ rebase_path( 1023 "$camera_product_name_path/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs") ] 1024 } 1025 1026 ohos_prebuilt_etc("camera_host_config.hcb") { 1027 deps = [ ":build_camera_host_config" ] 1028 hcs_outputs = get_target_outputs(":build_camera_host_config") 1029 source = hcs_outputs[0] 1030 relative_install_dir = "hdfconfig" 1031 install_images = [ chipset_base_dir ] 1032 subsystem_name = "hdf" 1033 part_name = "camera_device_driver" 1034 } 1035 1036### Camera适配介绍 1037 1038#### 新产品平台适配简介 1039 1040 1041drivers/peripheral/camera/hal/camera.gni 文件中可根据编译时传入的product_company product_name和device_name调用不同chipset的product.gni 1042 1043 if (defined(ohos_lite)) { 1044 import("//build/lite/config/component/lite_component.gni") 1045 import( 1046 "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni") 1047 } else { 1048 import("//build/ohos.gni") 1049 if ("${product_name}" == "ohos-arm64") { 1050 import( 1051 "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni") 1052 } else if ("${product_name}" == "Hi3516DV300") { 1053 import( 1054 "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni") 1055 } else if ("${product_name}" == "watchos") { 1056 import( 1057 "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni") 1058 } else { 1059 import( 1060 "//device/board/${product_company}/${device_name}/camera/product.gni") 1061 } 1062 } 1063 1064在如下路径的product.gni指定了编译不同chipset相关的代码的路径: 1065 1066``` 1067 device/${product_company}/${device_name}/camera/ 1068``` 1069 1070如下是rk3568的product.gni: 1071 1072 camera_device_name_path = "//device/board/${product_company}/${device_name}" 1073 is_support_v4l2 = true 1074 if (is_support_v4l2) { 1075 is_support_mpi = false 1076 defines += [ "SUPPORT_V4L2" ] 1077 chipset_build_deps = "$camera_device_name_path/camera/:chipset_build" 1078 camera_device_manager_deps = 1079 "$camera_device_name_path/camera/src/device_manager:camera_device_manager" 1080 camera_pipeline_core_deps = 1081 "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core" 1082 } 1083 1084product.gni中指定了chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps 三个代码编译路径。该路径在drivers/peripheral/camera/hal/BUILD.gn中会被使用 1085 1086#### 框架适配介绍 1087 1088 ![dayu200-camera-01.png](figures/dayu200/dayu200-camera-01.png) 1089 1090 1091以V4l2为例,pipeline的连接方式是在HCS配置文件中配置连接,数据源我们称之为SourceNode,主要包括硬件设备的控制、数据流的轮转等。 1092 ISPNode可根据需要确定是否添加此Node,因为在很多操作上其都可以和SensorNode统一为SourceNode。SinkNode为pipeline中数据传输的重点,到此处会将数据传输回buffer queue中。 1093 1094 pipeline中的Node是硬件/软件模块的抽象,所以对于其中硬件模块Node,其是需要向下控制硬件模块的,在控制硬件模块前,需要先获取其对应硬件模块的deviceManager,通过deviceManager向下传输控制命令/数据buffer,所以deviceManager中有一个v4l2 device manager抽象模块,用来创建各个硬件设备的manager、controller.如上sensorManager、IspManager,sensorController等,所以v4l2 device manager其实是各个硬件设备总的一个管理者。 1095 1096deviceManager中的controller和驱动适配层直接交互。 1097 1098基于以上所描述,如需适配一款以linux v4l2框架的芯片平台,只需要修改适配如上图中颜色标记模块及HCS配置文件(如为标准v4l2框架,基本可以延用当前已适配代码),接下来单独介绍修改模块。 1099 1100主要适配添加如下目录: 1101 1102 “vendor/hihope/rk3568/hdf_config/uhdf/camera/”:当前芯片产品的HCS配置文件目录。 1103 1104 “device/hihope/rk3568/camera/”:当前芯片产品的代码适配目录。 1105 1106 “drivers/peripheral/camera/hal/adapter/platform/v4l2”:平台通用公共代码。 1107 1108#### HCS配置文件适配介绍 1109 1110``` 1111 ├── hdi_impl 1112 │ └── camera_host_config.hcs 1113 └── pipeline_core 1114 ├── config.hcs 1115 ├── ipp_algo_config.hcs 1116 └── params.hcs 1117``` 1118 1119以RK3568开发板为例,其hcs文件应该放在对应的路径中。 1120 1121``` 1122 vendor/${product_company}/${product_name}/ hdf_config/uhdf/camera/ 1123``` 1124 1125 ``` 1126 template ability { 1127 logicCameraId = "lcam001"; 1128 physicsCameraIds = [ 1129 "CAMERA_FIRST", 1130 "CAMERA_SECOND" 1131 ]; 1132 metadata { 1133 aeAvailableAntiBandingModes = [ 1134 "OHOS_CONTROL_AE_ANTIBANDING_MODE_OFF", 1135 "OHOS_CONTROL_AE_ANTIBANDING_MODE_50HZ", 1136 "OHOS_CONTROL_AE_ANTIBANDING_MODE_60HZ", 1137 "OHOS_CONTROL_AE_ANTIBANDING_MODE_AUTO" 1138 ]; 1139 1140 ``` 1141 1142 hdi_impl下的“camera_host_config.hcs”为物理/逻辑Camera配置、能力配置,此处的物理/逻辑Camera配置,需要在hal内部使用,逻辑Camera及能力配置需要上报给上层,请按照所适配的芯片产品添加其能力配置。其中所用的能力值为键值对,定义在//drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h中。 1143 1144``` 1145 normal_preview :: pipeline_spec { 1146 name = "normal_preview"; 1147 v4l2_source :: node_spec { 1148 name = "v4l2_source#0"; 1149 status = "new"; 1150 out_port_0 :: port_spec { 1151 name = "out0"; 1152 peer_port_name = "in0"; 1153 peer_port_node_name = "sink#0"; 1154 direction = 1; 1155 width = 0; 1156 height = 0; 1157 format = 0; 1158 } 1159 } 1160 sink :: node_spec { 1161 name = "sink#0"; 1162 status = "new"; 1163 stream_type = "preview"; 1164 in_port_0 :: port_spec { 1165 name = "in0"; 1166 peer_port_name = "out0"; 1167 peer_port_node_name = "v4l2_source#0"; 1168 direction = 0; 1169 } 1170 } 1171 } 1172``` 1173 1174 pipeline_core下的“config.hcs”为pipeline的连接方式,按场景划分每一路流由哪些Node组成,其连接方式是怎样的。 1175 1176上面为preview场景的示例,normal_preview为该场景的名称,source和sink为Node,source为数据数据源端,sink为末端,source为第一个node,node的名称是source#0,status、in/out_port分别为Node状态及输入/输出口的配置。 1177 1178 以in_port_0为例,name = “in0”代表它的输入为“port0”,它的对端为source node的port口out0口,direction为它的源Node和对端Node是否为直连方式。如新添加芯片产品,必须按实际连接方式配置此文件。 1179 1180新增功能node时需继承NodeBase类,且在cpp文件中注册该node。具体可参考//drivers/peripheral/camera/hal/pipeline_core/nodes/src下已经实现的node。 1181 1182 1183 root { 1184 module = ""; 1185 template stream_info { 1186 id = 0; 1187 name = ""; 1188 } 1189 template scene_info { 1190 id = 0; 1191 name = ""; 1192 } 1193 preview :: stream_info { 1194 id = 0; 1195 name = "preview"; 1196 } 1197 video :: stream_info { 1198 id = 1; 1199 name = "video"; 1200 } 1201 1202 1203param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处需要添加定义。 1204 1205#### Chipset 和Platform适配介绍 1206 1207platform为平台性公共代码,如linux标准v4l2适配接口定义,为v4l2框架适配的通用node.以及为v4l2框架适配的通用device_manager等。目录结构如下: 1208 1209 drivers/peripheral/camera/hal/adapter/platform 1210 ├── mpp 1211 │ └── src 1212 │ ├── device_manager 1213 │ └── pipeline_core 1214 └── v4l2 1215 └── src 1216 ├── device_manager 1217 ├── driver_adapter 1218 └── pipeline_core 1219 1220“platform”目录下的“v4l2”包含了“src”, “src”中“driver_adapter”为linux v4l2标准适配接口,如有定制化功能需求,可继承driver_adapter,将定制化的具体功能接口放在chipset中实现。如无芯片定制化功能,可直接使用已有的driver_adapter。 1221 1222platform目录下的Nodes为依据linux v4l2标准实现的硬件模块v4l2_source_node和uvc_node(usb热插拔设备,此模块也为linux标准接口,可直接使用),如下图为v4l2_source_node的接口声明头文件。 1223 1224 1225 namespace OHOS::Camera { 1226 class V4L2SourceNode : public SourceNode { 1227 public: 1228 V4L2SourceNode(const std::string& name, const std::string& type); 1229 ~V4L2SourceNode() override; 1230 RetCode Init(const int32_t streamId) override; 1231 RetCode Start(const int32_t streamId) override; 1232 RetCode Flush(const int32_t streamId) override; 1233 RetCode Stop(const int32_t streamId) override; 1234 RetCode GetDeviceController(); 1235 void SetBufferCallback() override; 1236 RetCode ProvideBuffers(std::shared_ptr<FrameSpec> frameSpec) override; 1237 1238 private: 1239 std::mutex requestLock_; 1240 std::map<int32_t, std::list<int32_t>> captureRequests_ = {}; 1241 std::shared_ptr<SensorController> sensorController_ = nullptr; 1242 std::shared_ptr<IDeviceManager> deviceManager_ = nullptr; 1243 }; 1244 } // namespace OHOS::Camera 1245 1246 Init接口为模块初始化接口。 1247 1248Start为使能接口,比如start stream功能等。 1249 1250 Stop为停止接口。 1251 1252 GetDeviceController为获取deviceManager对应的controller接口。 1253 1254chipset为具体某芯片平台相关代码,例如,如和“rk3568”开发板 为例。device_manager目录下可存放该开发板适配过的sensor的相关配置文件。pipeline_core路径下可以存放由chipset开发者为满足特点需求增加的pipeline node等。 1255 1256``` 1257 device/board/hihope/rk3568/camera 1258 ├── BUILD.gn 1259 ├── camera_demo 1260 │ └── project_camera_demo.h 1261 ├── include 1262 │ └── device_manager 1263 ├── product.gni 1264 └── src 1265 ├── device_manager 1266 ├── driver_adapter 1267 └── pipeline_core 1268``` 1269 1270device/board/hihope/rk3568/camera/目录包含了“include”和“src”,“camera_demo”“src”中“device_manager”中包含了chipset 适配的sensor的文件,配合platform下device_manager的设备管理目录,主要对接pipeline,实现平台特有的硬件处理接口及数据buffer的下发和上报、metadata的交互。 1271 1272下图为device_manager的实现框图,pipeline控制管理各个硬件模块,首先要获取对应设备的manager,通过manager获取其对应的controller,controller和对应的驱动进行交互 。 1273 1274 ![img](figures/dayu200/dayu200-camera-02.png) 1275 1276deviceManager中需要实现关键接口介绍。 1277 1278``` 1279 class SensorController : public IController { 1280 public: 1281 SensorController(); 1282 explicit SensorController(std::string hardwareName); 1283 virtual ~SensorController(); 1284 RetCode Init(); 1285 RetCode PowerUp(); 1286 RetCode PowerDown(); 1287 RetCode Configure(std::shared_ptr<CameraStandard::CameraMetadata> meta); 1288 RetCode Start(int buffCont, DeviceFormat& format); 1289 RetCode Stop(); 1290 RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer); 1291 void SetNodeCallBack(const NodeBufferCb cb); 1292 void SetMetaDataCallBack(const MetaDataCb cb); 1293 void BufferCallback(std::shared_ptr<FrameSpec> buffer); 1294 void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag); 1295 } 1296``` 1297 1298 PowerUp为上电接口,OpenCamera时调用此接口进行设备上电操作。 1299 PowerDown为下电接口,CloseCamera时调用此接口进行设备下电操作。 1300 Configures为Metadata下发接口,如需设置metadata参数到硬件设备,可实现此接口进行解析及下发。 1301 Start为硬件模块使能接口,pipeline中的各个node进行使能的时候,会去调用,可根据需要定义实现,比如sensor的起流操作就可放在此处进行实现。 1302 Stop和Start为相反操作,可实现停流操作。 1303 SendFrameBuffer为每一帧buffer下发接口,所有和驱动进行buffer交互的操作,都是通过此接口进行的。 1304 SetNodeCallBack为pipeline,通过此接口将buffer回调函数设置到devicemanager。 1305 SetMetaDataCallBack为metadata回调接口,通过此接口将从底层获取的metadata数据上报给上层。 1306 BufferCallback上传每一帧已填充数据buffer的接口,通过此接口将buffer上报给pipeline。 1307 SetAbilityMetaDataTag设置需要从底层获取哪些类型的metadata数据,因为框架支持单独获取某一类型或多类型的硬件设备信息,所以可以通过此接口,获取想要的metadata数据。 1308 1309 其余接口可参考“drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/” 1310 1311#### IPP适配介绍 1312 1313IPP是pipeline 中的一个算法插件模块,由ippnode加载,对流数据进行算法处理,ippnode支持同时多路数据输入,只支持一路数据输出。ippnode加载算法插件通过如下hcs文件指定: 1314vendor/${product_company}/${product_name}/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs 其中: 1315 1316``` 1317 root { 1318 module="sample"; 1319 ipp_algo_config { 1320 algo1 { 1321 name = "example"; 1322 description = "example algorithm"; 1323 path = "libcamera_ipp_algo_example.z.so"; 1324 mode = "IPP_ALGO_MODE_NORMAL"; 1325 } 1326 } 1327 } 1328 1329``` 1330 1331 name:算法插件名称 1332 description:描述算法插件的功能 1333 path:算法插件所在路径 1334 mode:算法插件所运行的模式 1335 1336算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据需要进行扩展。 1337 1338``` 1339 enum IppAlgoMode { 1340 IPP_ALGO_MODE_BEGIN, 1341 IPP_ALGO_MODE_NORMAL = IPP_ALGO_MODE_BEGIN, 1342 IPP_ALGO_MODE_BEAUTY, 1343 IPP_ALGO_MODE_HDR, 1344 IPP_ALGO_MODE_END 1345 }; 1346``` 1347 1348算法插件由gn文件 device/${product_company}/${device_name}/camera/BUILD.gn进行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用: 1349 1350 typedef struct IppAlgoFunc { 1351 int (*Init)(IppAlgoMeta* meta); 1352 int (*Start)(); 1353 int (*Flush)(); 1354 int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta); 1355 int (*Stop)(); 1356 } IppAlgoFunc; 1357 1358 1) Init : 算法插件初始化接口,在起流前被ippnode 调用,其中IppAlgoMeta 定义在ipp_algo.h 中,为ippnode和算法插件提供非图像数据的传递通道,如当前运行的场景,算法处理后输出的人脸坐标等等,可根据实际需求进行扩展。 1359 2) Start:开始接口,起流时被ippnode 调用 1360 3) Flush:刷新数据的接口,停流之前被ippnode 调用。此接口被调用时,算法插件需尽可能快地停止处理。 1361 4) Process: 数据处理接口,每帧数据都通过此接口输入至算法插件进行处理。inBuffer是一组输入buffer,inBufferCount是输入buffer 的个数,outBuffer是输出buffer,meta是算法处理时产生的非图像数据,IppAlgoBuffer在ipp_algo.h中定义 1362 5) Stop:停止处理接口,停流时被ippnode调用 1363 1364 1365``` 1366typedef struct IppAlgoBuffer { 1367 void* addr; 1368 unsigned int width; 1369 unsigned int height; 1370 unsigned int stride; 1371 unsigned int size; 1372 int id; 1373 } IppAlgoBuffer; 1374``` 1375 1376其中上边代码中的id指的是和ippnode对应的port口id,比如inBuffer[0]的id为0,则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空,此时其中一个输入buffer 被ippnode作为输出buffer传递到下个node,inBuffer至少有一个buffer不为空。输入输出buffer 由pipeline配置决定。 1377比如在普通预览场景无算法处理且只有一路拍照数据传递到ippnode的情况下,输入buffer只有一个,输出buffer为空,即对于算法插件输入buffer 进行了透传; 1378比如算法插件进行两路预览图像数据进行合并的场景,第一路buffer需要预览送显示。把第二路图像拷贝到第一路的buffer即可,此时输入buffer有两个,输出buffer为空; 1379比如在算法插件中进行预览数据格式转换的场景,yuv转换为RGBA,那么只有一个yuv格式的输入buffer的情况下无法完成RGBA格式buffer的输出,此时需要一个新的buffer,那么ippnode的输出port口buffer作为outBuffer传递到算法插件。也即输入buffer只有一个,输出buffer也有一个。 1380 1381ippnode的port口配置请查看3.3小节的config.hcs的说明。 1382 1383#### 适配V4L2驱动实例 1384 1385本章节目的是在v4l2框架下适配RK3568开发板。 1386 1387 区分V4L2 platform相关代码并将其放置“drivers/peripheral/camera/hal/adapter/platform/v4l2”目录下,该目录中包含了“device_manager”“driver_adapter”和“pipeline_core”三个目录。其中“driver_adapter”目录中存放着v4l2协议相关代码。可通过它们实现与v4l2底层驱动交互。该目录下“Pipeline_core”目录与“drivers/peripheral/camera/hal/pipeline_core”中代码组合为pipeline框架。v4l2_source_node 和 uvc_node为v4l2专用Node。device_manager目录存放着向北与pipeline向南与v4l2 adapter交互的代码 1388 1389``` 1390 drivers/peripheral/camera/hal/adapter/platform/v4l2/src/ 1391 ├── device_manager 1392 │ ├── enumerator_manager.cpp 1393 │ ├── flash_controller.cpp 1394 │ ├── flash_manager.cpp 1395 │ ├── idevice_manager.cpp 1396 │ ├── include 1397 │ ├── isp_controller.cpp 1398 │ ├── isp_manager.cpp 1399 │ ├── sensor_controller.cpp 1400 │ ├── sensor_manager.cpp 1401 │ └── v4l2_device_manager.cpp 1402 ├── driver_adapter 1403 │ ├── BUILD.gn 1404 │ ├── include 1405 │ ├── main_test 1406 │ └── src 1407 └── pipeline_core 1408 └── nodes 1409``` 1410 1411 区分V4L2 chipset相关代码并将其放置在“device/ ${product_company}/${device_name} /camera”目录下。 1412 1413``` 1414 ├── BUILD.gn 1415 ├── camera_demo 1416 │ └── project_camera_demo.h 1417 ├── include 1418 │ └── device_manager 1419 ├── product.gni 1420 └── src 1421 ├── device_manager 1422 ├── driver_adapter 1423 └── pipeline_core 1424``` 1425 1426其中“driver_adapter”目录中包含了关于RK3568 driver adapter的测试用例头文件。Camera_demo目录存放了camera hal 中demo测试用例的chipset相关的头文件。device_manager存放了RK3568适配的camera sensor 读取设备能力的代码 其中,project_hardware.h 比较关键,存放了device_manager支持当前chipset的设备列表。如下: 1427 1428``` 1429 namespace OHOS::Camera { 1430 std::vector<HardwareConfiguration> hardware = { 1431 {CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "rkisp_v5"}, 1432 {CAMERA_FIRST, DM_M_ISP, DM_C_ISP, (std::string) "isp"}, 1433 {CAMERA_FIRST, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"}, 1434 {CAMERA_SECOND, DM_M_SENSOR, DM_C_SENSOR, (std::string) "Imx600"}, 1435 {CAMERA_SECOND, DM_M_ISP, DM_C_ISP, (std::string) "isp"}, 1436 {CAMERA_SECOND, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"} 1437 }; 1438 } // namespace OHOS::Camera 1439``` 1440 1441修改编译选项来达到根据不同的编译chipset来区分v4l2和其他框架代码编译。增加device/${product_company}/${device_name}/camera/product.gni 1442 1443``` 1444 camera_product_name_path = "//vendor/${product_company}/${product_name}" 1445 camera_device_name_path = "//device/board/${product_company}/${device_name}" 1446 is_support_v4l2 = true 1447 if (is_support_v4l2) { 1448 is_support_mpi = false 1449 defines += [ "SUPPORT_V4L2" ] 1450 chipset_build_deps = "$camera_device_name_path/camera/:chipset_build" 1451 camera_device_manager_deps = 1452 "$camera_device_name_path/camera/src/device_manager:camera_device_manager" 1453 camera_pipeline_core_deps = 1454 "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core" 1455 } 1456``` 1457 1458当“product.gni”被// drivers/peripheral/camera/hal/camera.gni加载,就说明要编译v4l2相关代码。在//drivers/peripheral/camera/hal/camera.gni中根据编译时传入的product_name和device_name名来加载相应的gni文件。 1459 1460 ``` 1461 import("//build/ohos.gni") 1462 if ("${product_name}" == "ohos-arm64") { 1463 import( 1464 "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni") 1465 } else if ("${product_name}" == "Hi3516DV300") { 1466 import( 1467 "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni") 1468 ``` 1469 1470 “drivers/peripheral/camera/hal/BUILD.gn”中会根据 chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps来编译不同的chipset 1471 1472 print("product_name : , ${product_name}") 1473 group("camera_hal") { 1474 if (is_standard_system) { 1475 deps = [ 1476 "$camera_path/../interfaces/hdi_ipc/client:libcamera_client", 1477 "buffer_manager:camera_buffer_manager", 1478 "device_manager:camera_device_manager", 1479 "hdi_impl:camera_hdi_impl", 1480 "init:ohos_camera_demo", 1481 "pipeline_core:camera_pipeline_core", 1482 "utils:camera_utils", 1483 ] 1484 deps += [ "${chipset_build_deps}" ] 1485 } 1486 1487 1488Camera hal层向下屏蔽了平台及芯片差异,对外(Camera service或者测试程序)提供统一接口,其接口定义在“drivers/peripheral/camera/interfaces/include”目录下: 1489 1490 ├── icamera_device_callback.h 1491 ├── icamera_device.h 1492 ├── icamera_host_callback.h 1493 ├── icamera_host.h 1494 ├── ioffline_stream_operator.h 1495 ├── istream_operator_callback.h 1496 ├── istream_operator.h 1497 1498测试时,只需要针对所提供的对外接口进行测试,即可完整测试Camera hal层代码,具体接口说明,可参考“drivers/peripheral/camera/interfaces”目录下的“README_zh.md”和头文件接口定义。具体的调用流程,可参考测试demo:drivers/peripheral/camera/hal/init。 1499 1500### camera适配过程中问题以及解决方案 1501 1502#### 修改SUBWINDOW_TYPE和送显format 1503 1504修改RGBA888送显,模式由video 改为 SUBWINDOW_TYPE为normal模式: 1505 1506由于openharmony 较早实现的是3516平台camera, 该平台采用PIXEL_FMT_YCRCB_420_SP格式送显,而RK3568需将预览流由yuv420转换为PIXEL_FMT_RGBA_8888送上屏幕才可被正确的显示。具体需修改foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp 文件中如下内容,该文件被编译在libace.z.so中 1507 1508 1509 #ifdef PRODUCT_RK 1510 previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_RGBA_8888)); 1511 previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat, 1512 std::to_string(OHOS_CAMERA_FORMAT_RGBA_8888)); 1513 #else 1514 previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_YCRCB_420_SP)); 1515 previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat, 1516 std::to_string(OHOS_CAMERA_FORMAT_YCRCB_420_SP)); 1517 #endif 1518 1519foundation/multimedia/camera_standard/services/camera_service/src/hstream_repeat.cpp 文件中如下内容,该文件被编译在libcamera_service.z.so中 1520 1521``` 1522void HStreamRepeat::SetStreamInfo(std::shared_ptr<Camera::StreamInfo> streamInfo) 1523 { 1524 int32_t pixelFormat; 1525 auto it = g_cameraToPixelFormat.find(format_); 1526 if (it != g_cameraToPixelFormat.end()) { 1527 pixelFormat = it->second; 1528 } else { 1529 #ifdef RK_CAMERA 1530 pixelFormat = PIXEL_FMT_RGBA_8888; 1531 #else 1532 pixelFormat = PIXEL_FMT_YCRCB_420_SP; 1533 #endif 1534``` 1535 1536如上3516平台是使用VO通过VO模块驱动直接送显,所以在ace中配置的subwindows模式为SUBWINDOW_TYPE_VIDEO. 需在foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp文件中做如下修改,该文件被编译在libace.z.so中 1537 1538 #ifdef PRODUCT_RK 1539 option->SetWindowType(SUBWINDOW_TYPE_NORMAL); 1540 #else 1541 option->SetWindowType(SUBWINDOW_TYPE_VIDEO); 1542 #endif 1543 1544#### 增加rk_codec_node 1545 1546在该node中完成rgb转换,jpeg和h264压缩编解码前文讲过camera hal的pipeline模型的每一个node都是camera数据轮转过程中的一个节点,由于当前camera hal v4l2 adapter只支持一路流进行数据轮转,那么拍照和录像流就必须从单一的预览流中拷贝。现阶段openharmony也没有专门的服务端去做codec和rgb转换jpeg压缩的工作。那么只能在camera hal中开辟一个专有node去做这些事情,也就是rk_codec_node。 1547Hcs中增加rk_codec_node连接模型: 1548修改vendor/hihope/rk3568/hdf_config/uhdf/camera/pipeline_core/config.hcs文件 1549 1550 1551 normal_preview_snapshot :: pipeline_spec { 1552 name = "normal_preview_snapshot"; 1553 v4l2_source :: node_spec { 1554 name = "v4l2_source#0"; 1555 status = "new"; 1556 out_port_0 :: port_spec { 1557 name = "out0"; 1558 peer_port_name = "in0"; 1559 peer_port_node_name = "fork#0"; 1560 direction = 1; 1561 } 1562 } 1563 fork :: node_spec { 1564 name = "fork#0"; 1565 status = "new"; 1566 in_port_0 :: port_spec { 1567 name = "in0"; 1568 peer_port_name = "out0"; 1569 peer_port_node_name = "v4l2_source#0"; 1570 direction = 0; 1571 } 1572 out_port_0 :: port_spec { 1573 name = "out0"; 1574 peer_port_name = "in0"; 1575 peer_port_node_name = "RKCodec#0"; 1576 direction = 1; 1577 } 1578 out_port_1 :: port_spec { 1579 name = "out1"; 1580 peer_port_name = "in0"; 1581 peer_port_node_name = "RKCodec#1"; 1582 direction = 1; 1583 } 1584 } 1585 RKCodec_1 :: node_spec { 1586 name = "RKCodec#0"; 1587 status = "new"; 1588 in_port_0 :: port_spec { 1589 name = "in0"; 1590 peer_port_name = "out0"; 1591 peer_port_node_name = "fork#0"; 1592 direction = 0; 1593 } 1594 out_port_0 :: port_spec { 1595 name = "out0"; 1596 peer_port_name = "in0"; 1597 peer_port_node_name = "sink#0"; 1598 direction = 1; 1599 } 1600 } 1601 RKCodec_2 :: node_spec { 1602 name = "RKCodec#1"; 1603 1604 1605以预览加拍照双路流为列,v4l2_source_node为数据源,流向了fork_node,rork_node将预览数据直接送给RKCodec node, 将拍照数据流拷贝一份也送给RKCodec node进行转换。转换完成的数据将送给sink node后交至buffer的消费端。 1606 1607device/board/hihope/rk3568/camera/src/pipeline_core/BUILD.gn中添加rk_codec_node.cpp和相关依赖库的编译。其中librga为yuv到rgb格式转换库,libmpp为yuv到H264编解码库,libjpeg为yuv到jpeg照片的压缩库。 1608 1609 1610 ohos_shared_library("camera_pipeline_core") { 1611 sources = [ 1612 "$camera_device_name_path/camera/src/pipeline_core/node/rk_codec_node.cpp", 1613 "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/uvc_node/uvc_node.cpp", 1614 "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/v4l2_source_node/v4l2_source_node.cpp", 1615 deps = [ 1616 "$camera_path/buffer_manager:camera_buffer_manager", 1617 "$camera_path/device_manager:camera_device_manager", 1618 "//device/soc/rockchip/hardware/mpp:libmpp", 1619 "//device/soc/rockchip/hardware/rga:librga", 1620 "//foundation/multimedia/camera_standard/frameworks/native/metadata:metadata", 1621 "//third_party/libjpeg:libjpeg_static", 1622 1623 1624 openharmony/device/board/hihope/rk3568/camera/src/pipeline_core/node/rk_codec_node.cpp主要接口: 1625 1626 1627 void RKCodecNode::DeliverBuffer(std::shared_ptr<IBuffer>& buffer) 1628 { 1629 if (buffer == nullptr) { 1630 CAMERA_LOGE("RKCodecNode::DeliverBuffer frameSpec is null"); 1631 return; 1632 } 1633 1634 int32_t id = buffer->GetStreamId(); 1635 CAMERA_LOGE("RKCodecNode::DeliverBuffer StreamId %{public}d", id); 1636 if (buffer->GetEncodeType() == ENCODE_TYPE_JPEG) { 1637 Yuv420ToJpeg(buffer); 1638 } else if (buffer->GetEncodeType() == ENCODE_TYPE_H264) { 1639 Yuv420ToH264(buffer); 1640 } else { 1641 Yuv420ToRGBA8888(buffer); 1642 } 1643 1644由fork_node出来的数据流将会被deliver到rk_codec_node的DeliverBuffer接口中,该接口会根据不同的EncodeType去做不同的转换处理。经过转换过的buffers再deliver到下一级node中处理。直到deliver到buffer消费者手中。 1645 1646#### H264帧时间戳和音频时间戳不同步问题。 1647 1648问题点:Ace在CreateRecorder时会同时获取音频和视频数据并将他们合成为.mp4文件。但在实际合成过程当中需要检查音视频信息中的时间戳是否一致,如不一致将会Recorder失败。表现出的现象是camera app点击录像按钮后无法正常停止,强行停止后发现mp4文件为空。 1649 1650解决方法:首先需找到audio模块对于音频时间戳的获取方式。 1651 1652``` 1653 int32_t AudioCaptureAsImpl::GetSegmentInfo(uint64_t &start) 1654 { 1655 CHECK_AND_RETURN_RET(audioCapturer_ != nullptr, MSERR_INVALID_OPERATION); 1656 AudioStandard::Timestamp timeStamp; 1657 auto timestampBase = AudioStandard::Timestamp::Timestampbase::MONOTONIC; 1658 CHECK_AND_RETURN_RET(audioCapturer_->GetAudioTime(timeStamp, timestampBase), MSERR_UNKNOWN); 1659 CHECK_AND_RETURN_RET(timeStamp.time.tv_nsec >= 0 && timeStamp.time.tv_sec >= 0, MSERR_UNKNOWN); 1660 if (((UINT64_MAX - timeStamp.time.tv_nsec) / SEC_TO_NANOSECOND) <= static_cast<uint64_t>(timeStamp.time.tv_sec)) { 1661 MEDIA_LOGW("audio frame pts too long, this shouldn't happen"); 1662 } 1663 start = timeStamp.time.tv_nsec + timeStamp.time.tv_sec * SEC_TO_NANOSECOND; 1664 MEDIA_LOGI("timestamp from audioCapturer: %{public}" PRIu64 "", start); 1665 return MSERR_OK; 1666 } 1667``` 1668 1669可以看到,audio_capture_as_impl.cpp 文件中。audio模块用的是CLOCK_MONOTONIC,即系统启动时开始计时的相对时间。而camera 模块使用的是CLOCK_REALTIME,即系统实时时间。 1670 1671 1672 mppStatus_ = 1; 1673 buf_size = ((MpiEncTestData *)halCtx_)->frame_size; 1674 1675 ret = hal_mpp_encode(halCtx_, dma_fd, (unsigned char *)buffer->GetVirAddress(), &buf_size); 1676 SearchIFps((unsigned char *)buffer->GetVirAddress(), buf_size, buffer); 1677 1678 buffer->SetEsFrameSize(buf_size); 1679 clock_gettime(CLOCK_MONOTONIC, &ts); 1680 timestamp = ts.tv_nsec + ts.tv_sec * TIME_CONVERSION_NS_S; 1681 buffer->SetEsTimestamp(timestamp); 1682 CAMERA_LOGI("RKCodecNode::Yuv420ToH264 video capture on\n"); 1683 1684 解决方法:修改camera hal中rk_codec_node.cpp中的获取时间类型为CLOCK_MONOTONIC即可解决问题。 1685 1686#### time_t改为64位以后匹配4.19 kernel问题。 1687 1688背景介绍:RK3568在遇到这个问题时的环境是上层运行的32位系统,底层是linux4.19 64位kernel。在32位系统环境下time_t这个typedef是long类型的,也就是32位。但在下面这个提交中将time_t 改成_Int64位。这样就会导致camera v4l2在ioctl时发生错误。 1689 1690 TYPEDEF _Int64 time_t; 1691 TYPEDEF _Int64 suseconds_t; 1692 1693 具体错误以及临时修改方案: 1694 1695 1,发生错误时在hilog中搜索camera_host 会发现在V4L2AllocBuffer接口中下发VIDIOC_QUERYBUF的CMD时上报了一个Not a tty的错误。如下: 1696 1697``` 1698V4L2AllocBuffer error:ioctl VIDIOC_QUERYBUF failed: Not a tty 1699 1700``` 1701 1702 RetCode HosV4L2Buffers::V4L2AllocBuffer(int fd, const std::shared_ptr<FrameSpec>& frameSpec) 1703 { 1704 struct v4l2_buffer buf = {}; 1705 struct v4l2_plane planes[1] = {}; 1706 CAMERA_LOGD("V4L2AllocBuffer\n"); 1707 1708 if (frameSpec == nullptr) { 1709 CAMERA_LOGE("V4L2AllocBuffer frameSpec is NULL\n"); 1710 return RC_ERROR; 1711 } 1712 1713 switch (memoryType_) { 1714 case V4L2_MEMORY_MMAP: 1715 // to do something 1716 break; 1717 case V4L2_MEMORY_USERPTR: 1718 buf.type = bufferType_; 1719 buf.memory = memoryType_; 1720 buf.index = (uint32_t)frameSpec->buffer_->GetIndex(); 1721 1722 if (bufferType_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { 1723 buf.m.planes = planes; 1724 buf.length = 1; 1725 } 1726 CAMERA_LOGD("V4L2_MEMORY_USERPTR Print the cnt: %{public}d\n", buf.index); 1727 1728 if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { 1729 CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno)); 1730 return RC_ERROR; 1731 2,我们知道,一般ioctl系统调用的CMD都是以第三个参数的sizeof为CMD值主要组成传递进内核去寻找内核中相对应的switch case. 如下图,v4l2_buffer为VIDIOC_QUERYBUF宏的值得主要组成部分,那么v4l2_buffer的size发生变化,VIDIOC_QUERYBUF的值也会发生变化。 1732 1733``` 1734 #define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format) 1735 #define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers) 1736 #define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer) 1737 #define VIDIOC_G_FBUF _IOR('V', 10, struct v4l2_framebuffer) 1738``` 1739 1740 3,当kernel 打开CONFIG_COMPAT这个宏时,可以实现32位系统到64位kernel的兼容,对于32位系统下发的ioctl会先进入下面截图中的接口里去做cmd值由32到64位的转换。 1741 1742 long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) 1743 { 1744 struct video_device *vdev = video_devdata(file); 1745 long ret = -ENOIOCTLCMD; 1746 1747 if (!file->f_op->unlocked_ioctl) 1748 return ret; 1749 1750 if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE) 1751 ret = do_video_ioctl(file, cmd, arg); 1752 else if (vdev->fops->compat_ioctl32) 1753 ret = vdev->fops->compat_ioctl32(file, cmd, arg); 1754 1755 4,那么在kernel中会定义一个kernel认为的VIDIOC_QUERYBUF的值。 1756 1757 #define VIDIOC_S_FMT32 _IOWR('V', 5, struct v4l2_format32) 1758 #define VIDIOC_QUERYBUF32 _IOWR('V', 9, struct v4l2_buffer32) 1759 #define VIDIOC_QUERYBUF32_TIME32 _IOWR('V', 9, struct v4l2_buffer32_time32) 17605,前文提到过,上层musl中time_t已经由32位被改为64位,v4l2_buffer结构体中的struct timeval中就用到了time_t。那么应用层的v4l2_buffer的大小就会跟kernel层的不一致,因为kernel的struct timeval 中编译时使用的是kernel自己在time.h中定义的 kernel_time_t。这就导致应用和驱动层对于v4l2_buffer的sizeof计算不一致从而调用到内核态后找不到cmd的错误。 1761 1762``` 1763 struct v4l2_buffer { 1764 __u32 index; 1765 __u32 type; 1766 __u32 bytesused; 1767 __u32 flags; 1768 __u32 field; 1769 struct timeval timestamp; 1770 struct v4l2_timecode timecode; 1771 __u32 sequence; 1772``` 17736,临时解决方案是修改videodev2.h中的struct timeval为自己临时定义的结构体, 保证上下层size一致。如下: 1774 1775``` 1776 struct timeval1 { 1777 long tv_sec; 1778 long tv_usec; 1779 } 1780 struct v4l2_buffer { 1781 __u32 index; 1782 __u32 type; 1783 __u32 bytesused; 1784 __u32 flags; 1785 __u32 field; 1786 struct timeval1 timestamp; 1787 struct v4l2_timecode timecode; 1788``` 1789 1790 根本解决方案: 1791 1792 如需要根本解决这个问题,只有两种方法。第一将系统升级为64位系统,保证用户态和内核态对于time_t变量的size保持一致。第二,升级5.10之后版本的kernel 1793 因为5.10版本的kernel在videodev2.h文件中解决了这个情况。目前我们已在5.10的kernel上验证成功,如下图,可以看到在编译kernel时考虑到了64位time_t的问题。 1794 1795``` 1796struct v4l2_buffer { 1797 __u32 index; 1798 __u32 type; 1799 __u32 bytesused; 1800 __u32 flags; 1801 __u32 field; 1802 #ifdef __KERNEL__ 1803 struct __kernel_v4l2_timeval timestamp; 1804 #else 1805 struct timeval timestamp; 1806 #endif 1807 struct v4l2_timecode timecode; 1808 } 1809 1810 struct __kernel_v4l2_timeval { 1811 long long ._sec; 1812 #if defined(__sparc__) && defined(__arch64__) 1813 int tv_usec; 1814 int __pad; 1815 #else 1816 long long tv_usec; 1817 #endif 1818 }; 1819``` 1820#### H264 关键帧获取上报 1821 1822 H264除了需要上报经过编解码的数据外,还需上报关键帧信息。即这一帧是否为关键帧?mp4编码时需要用到这些信息,那么怎么分析那一帧是关键帧那?主要是分析NALU头信息。Nalu type & 0x1f就代表该帧的类型。Nalu头是以0x00000001或0x000001为起始标志的。 该图为nal_unit_type为不同数值时的帧类型。我们主要关心type为5也就是IDR帧信息。 1823 ![1647875911244](figures/dayu200/dayu200-camera-03.png) 1824 1825 1826 rk_cedec_node.cpp文件里对IDR帧分析进行了代码化: 1827 1828 static constexpr uint32_t nalBit = 0x1F; 1829 #define NAL_TYPE(value) ((value) & nalBit) 1830 void RKCodecNode::SearchIFps(unsigned char* buf, size_t bufSize, std::shared_ptr<IBuffer>& buffer) 1831 { 1832 size_t nalType = 0; 1833 size_t idx = 0; 1834 size_t size = bufSize; 1835 constexpr uint32_t nalTypeValue = 0x05; 1836 1837 if (buffer == nullptr || buf == nullptr) { 1838 CAMERA_LOGI("RKCodecNode::SearchIFps parameter == nullptr"); 1839 return; 1840 } 1841 1842 for (int i = 0; i < bufSize; i++) { 1843 int ret = findStartCode(buf + idx, size); 1844 if (ret == -1) { 1845 idx += 1; 1846 size -= 1; 1847 } else { 1848 nalType = NAL_TYPE(buf[idx + ret]); 1849 CAMERA_LOGI("ForkNode::ForkBuffers nalu == 0x%{public}x buf == 0x%{public}x \n", nalType, buf[idx + ret]); 1850 1851每经过一个h264转换过的buffer都会被传入SearchIFps接口中寻找IDR帧。其中findStartCode()接口会对buffer中的内容逐个字节扫描,知道寻找出NALU头来 1852 1853 ``` 1854 int RKCodecNode::findStartCode(unsigned char *data, size_t dataSz) 1855 { 1856 constexpr uint32_t dataSize = 4; 1857 constexpr uint32_t dataBit2 = 2; 1858 constexpr uint32_t dataBit3 = 3; 1859 1860 if (data == nullptr) { 1861 CAMERA_LOGI("RKCodecNode::findStartCode parameter == nullptr"); 1862 return -1; 1863 } 1864 1865 if ((dataSz > dataSize) && (data[0] == 0) && (data[1] == 0) && \ 1866 (data[dataBit2] == 0) && (data[dataBit3] == 1)) { 1867 return 4; // 4:start node 1868 } 1869 1870 return -1; 1871 } 1872 ``` 1873当找到NALU头后就会对&0x1F 找出nal_unit_type,如果type为5标记关键帧信息并通过buffer->SetEsKeyFrame(1);接口上报。 1874 1875## TP 1876 1877### TP驱动模型 1878 1879主要包含Input模块HDI(Hardware Driver Interface)接口定义及其实现,对上层输入服务提供操作input设备的驱动能力接口,HDI接口主要包括如下三大类: 1880 1881- InputManager:管理输入设备,包括输入设备的打开、关闭、设备列表信息获取等; 1882- InputReporter:负责输入事件的上报,包括注册、注销数据上报回调函数等; 1883- InputController:提供input设备的业务控制接口,包括获取器件信息及设备类型、设置电源状态等。 1884 1885**图 1** INPUT模块HDI接口层框架图 1886 1887![dayu200-tp-01.png](figures/dayu200/dayu200-tp-01.png) 1888 1889相关目录下源代码目录结构如下所示 1890 1891``` 1892/drivers/peripheral/input 1893├── hal # input模块的hal层代码 1894│ └── include # input模块hal层内部的头文件 1895│ └── src # input模块hal层代码的具体实现 1896├── interfaces # input模块对上层服务提供的驱动能力接口 1897│ └── include # input模块对外提供的接口定义 1898├── test # input模块的测试代码 1899│ └── unittest # input模块的单元测试代码 1900``` 1901 1902详细请参考input子系统[README](https://gitee.com/openharmony/drivers_peripheral/blob/master/input/README_zh.md) 1903 1904### TP HDF驱动适配 1905 1906#### TP驱动涉及的文件及目录 1907 1908dayu200平台默认支持GT5688这颗TP IC。 1909 1910开发板移植touch驱动涉及的文件及目录: 1911 19121、 Makefile文件: drivers\adapter\khdf\linux\model\input\Makefile 1913 19142、 vendor\hihope\rk3568\hdf_config\khdf\device_info\device_info.hcs 1915 19163、 vendor\hihope\rk3568\hdf_config\khdf\input\input_config.hcs 1917 19184、 drivers\framework\model\input\driver\touchscreen 1919 1920TP驱动的适配涉及TP驱动和hcs配置 1921 1922tp驱动的适配依赖hdf的input模型,hdf的input模型提供了TP,KEY,HID等场景的设备注册,管理,数据转发层,hcs解析等场景的支持能力。hdf的input模型可大致抽象为驱动管理层、公共驱动层以及器件驱动三层。 1923 1924从功能的角度看hdf input模块的框架如下: 1925 1926![dayu200-tp-02.png](figures/dayu200/dayu200-tp-02.png) 1927 1928因为hdf input模型的高度抽象集成,TP驱动的适配驱动主要涉及器件驱动层的适配。 1929 1930在适配前,需要先明确tp所需要的的资源。 1931 1932对于硬件资源,tp模组需要主机上的如下资源: 1933 19341.中断引脚 1935 19362.Reset引脚 1937 19383.使用的哪一组i2c,从设备的地址是什么 1939 19404.TP的初始化固件(这个通常由IC厂商提供) 1941 19425.触摸屏的分辨率 1943 1944对于软件资源,在hdf上适配tp,需要依赖如下几个hdf基础模组: 1945 19461.Hdf gpio子系统 用于设置gpio pin脚以及一些中断资源 1947 19482.Hdf i2c 子系统 用于进行i2c通信 1949 19503.Input模型 1951 1952器件驱动主要围绕如下结构体展开 1953 1954``` 1955static struct TouchChipOps g_gt911ChipOps = { 1956 .Init = ChipInit, 1957 .Detect = ChipDetect, 1958 .Resume = ChipResume, 1959 .Suspend = ChipSuspend, 1960 .DataHandle = ChipDataHandle, 1961 .UpdateFirmware = UpdateFirmware, 1962 .SetAbility = SetAbility, 1963}; 1964``` 1965 1966ChipInit负责器件驱动的初始化动作 1967 1968ChipDetect负责初始化后的器件有效性检测 1969 1970SetAbility设置按键属性 1971 1972ChipDataHandle负责解析键值 1973 1974UpdateFirmware负责升级固件 1975 1976ChipSuspend负责器件的休眠 1977 1978ChipResume负责器件的唤醒 1979 1980按照器件的特性实现如上接口回调,并将该结构体注册进input模型即可 1981 1982#### HCS 配置 1983 1984device_info.hcs中加入新的器件节点 1985 1986``` 1987device_touch_chip :: device { 1988 device0 :: deviceNode { 1989 policy = 0; 1990 priority = 180; 1991 preload = 0;//0表示默认加载 1992 permission = 0660; 1993 moduleName = "HDF_TOUCH_GT911";//需要和器件driver中保持一致 1994 serviceName = "hdf_touch_gt911_service"; 1995 deviceMatchAttr = "zsj_gt911_5p5"; 1996 } 1997 } 1998``` 1999 2000input_config.hcs中加入器件的特性 2001 2002``` 2003chipConfig { 2004 template touchChip { 2005 match_attr = ""; 2006 chipName = "gt911"; 2007 vendorName = "zsj"; 2008 chipInfo = "AAAA11222"; // 4-ProjectName, 2-TP IC, 3-TP Module 2009 /* 0:i2c 1:spi*/ 2010 busType = 0; 2011 deviceAddr = 0x5D; 2012 /* 0:None 1:Rising 2:Failing 4:High-level 8:Low-level */ 2013 irqFlag = 2; 2014 maxSpeed = 400; 2015 chipVersion = 0; //parse Coord TypeA 2016 powerSequence { 2017 /* [type, status, dir , delay] 2018 <type> 0:none 1:vcc-1.8v 2:vci-3.3v 3:reset 4:int 2019 <status> 0:off or low 1:on or high 2:no ops 2020 <dir> 0:input 1:output 2:no ops 2021 <delay> meanings delay xms, 20: delay 20ms 2022 */ 2023 powerOnSeq = [4, 0, 1, 5, 2024 3, 0, 1, 10, 2025 3, 1, 1, 60, 2026 4, 2, 0, 50]; 2027 suspendSeq = [3, 0, 2, 10]; 2028 resumeSeq = [3, 1, 2, 10]; 2029 powerOffSeq = [3, 0, 2, 10, 2030 1, 0, 2, 20]; 2031 } 2032 } 2033``` 2034 2035## 显示适配 2036 2037显示适配需要完成的工作:图形服务HDI接口适配、GPU适配、LCD驱动适配 2038 2039### 显示HDI 2040 2041[显示HDI](https://gitee.com/openharmony/drivers_peripheral/blob/master/display/README_zh.md)对图形服务提供显示驱动能力,包括显示图层的管理、显示内存的管理及硬件加速等。 显示HDI需要适配两部分:gralloc 和 display_device。 2042 2043#### gralloc适配 2044 2045gralloc模块提供显示内存管理功能,OpenHarmony提供了使用与Hi3516DV300参考实现,厂商可根据实际情况参考适配,该实现基于drm开发,[源码链接](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard)。 2046 2047drm设备节点定义在//drivers_peripheral/display/hal/default_standard/srd/display_gralloc/display_gralloc_gbm.c文件中,可根据实际情况修改 2048 2049``` 2050const char *g_drmFileNode = "/dev/dri/card0"; 2051``` 2052 2053该实现中存在一个海思的私有ioctl命令码 DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR 定义在//drivers_peripheral/display/hal/default_standard/src/display_gralloc/hisilicon_drm.h 文件中, 在//drivers_peripheral/display/hal/default_standard/src/display_gralloc/display_gralloc_gbm.c文件中调用,属于海思的私有功能,适配时根据实际情况修改 2054 2055``` 2056... 2057 InitBufferHandle(bo, fd, info, priBuffer); 2058 priBuffer->hdl.phyAddr = GetPhysicalAddr(grallocManager->drmFd, fd); 2059 *buffer = &priBuffer->hdl; 2060... 2061``` 2062 2063#### display device适配 2064 2065display device模块提供显示设备管理、layer管理、硬件加速等功能。 2066 2067OpenHarmony提供了[基于drm的Hi3516DV300芯片的参考实现](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard/src/display_device),该实现默认支持硬件合成; 2068 2069如开发板不支持硬件合成,需要在drm_display.cpp文件中跳过gfx的初始化, 2070 2071``` 2072drivers_peripheral/blob/master/display/hal/default_standard/src/display_device/drm/drm_display.cpp 2073int32_t DrmDisplay::Init() 2074{ 2075 ... 2076 ... 2077 ret = HdiDisplay::Init(); 2078 DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("init failed")); 2079 auto preComp = std::make_unique<HdiGfxComposition>(); 2080 DISPLAY_CHK_RETURN((preComp == nullptr), DISPLAY_FAILURE, 2081 DISPLAY_LOGE("can not new HdiGfxComposition errno %{public}d", errno)); 2082 ret = preComp->Init(); // gfx初始化,这里需要跳过 2083 DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not init HdiGfxComposition")); // 或者不判断返回值 2084 2085 ... 2086} 2087``` 2088 2089同时在//drivers_peripheral/display/hal/default_standard/src/display_device/hdi_gfx_composition.cpp文件中修改set_layers方法,全部使用CPU合成显示 2090 2091``` 2092int32_t HdiGfxComposition::SetLayers(std::vector<HdiLayer *> &layers, HdiLayer &clientLayer) 2093{ 2094 DISPLAY_LOGD("layers size %{public}zd", layers.size()); 2095 mClientLayer = &clientLayer; 2096 mCompLayers.clear(); 2097 for (auto &layer : layers) { 2098 if (CanHandle(*layer)) { 2099#if 0 // CPU合成 2100 layer->SetDeviceSelect(COMPOSITION_CLIENT); 2101#else 2102 if ((layer->GetCompositionType() != COMPOSITION_VIDEO) && 2103 (layer->GetCompositionType() != COMPOSITION_CURSOR)) { 2104 layer->SetDeviceSelect(COMPOSITION_DEVICE); 2105 } else { 2106 layer->SetDeviceSelect(layer->GetCompositionType()); 2107 } 2108#endif 2109 mCompLayers.push_back(layer); 2110 } 2111 } 2112 DISPLAY_LOGD("composer layers size %{public}zd", mCompLayers.size()); 2113 return DISPLAY_SUCCESS; 2114} 2115``` 2116 2117#### 测试验证 2118 2119hello_composer测试模块:Rosen图形框架提供的测试程序,主要显示流程,HDI接口等功能是否正常。默认随系统编译。 2120 2121代码路径: 2122 2123``` 2124foundation/graphic/graphic/rosen/samples/composer/ 2125├── BUILD.gn 2126├── hello_composer.cpp 2127├── hello_composer.h 2128├── layer_context.cpp 2129├── layer_context.h 2130└── main.cpp 2131``` 2132 2133具体验证如下: 2134 21351. 关闭render service 2136 2137 ``` 2138 service_control stop render_service 2139 ``` 2140 21412. 关闭 foundation进程 2142 2143 ``` 2144 service_control stop foundation 2145 ``` 2146 21473. 运行hello_composer 测试相关接口 2148 2149 ``` 2150 ./hello_composer 2151 ``` 2152 2153devicetest测试:HDI显示模块提供的测试模块,主要测试HDI接口、显示buffer、驱动等能力,测试时也需要关闭render service和 foundation进程。 2154 2155代码路径:/drivers/peripheral/display/test/unittest/standard 2156 2157``` 2158├── BUILD.gn 2159├── common 2160│ ├── display_test.h 2161│ ├── display_test_utils.cpp 2162│ └── display_test_utils.h 2163├── display_device 2164│ ├── hdi_composition_check.cpp 2165│ ├── hdi_composition_check.h 2166│ ├── hdi_device_test.cpp 2167│ ├── hdi_device_test.h 2168│ ├── hdi_test_device_common.h 2169│ ├── hdi_test_device.cpp 2170│ ├── hdi_test_device.h 2171│ ├── hdi_test_display.cpp 2172│ ├── hdi_test_display.h 2173│ ├── hdi_test_layer.cpp 2174│ ├── hdi_test_layer.h 2175│ ├── hdi_test_render_utils.cpp 2176│ └── hdi_test_render_utils.h 2177└── display_gralloc 2178 ├── display_gralloc_test.cpp 2179 └── display_gralloc_test.h 2180``` 2181 2182### GPU 2183 2184编译器clang 2185 2186``` 2187prebuilts/clang/ohos/linux-x86_64/llvm 2188``` 2189 2190musl库 2191 2192``` 2193./build.sh --product-name rk3568 --build-target musl_all 2194``` 2195 2196编译完成后,会在 out/{product_name}/obj/third_party/musl/usr/lib目录下生成对应的头文件和库: 2197 2198``` 219932位对应arm-linux-ohos 2200 220164位对应aarch64-linux-ohos 2202``` 2203 2204源码目录: 2205 2206``` 2207third_party/musl 2208``` 2209 2210GPU 编译参数参考 2211 2212``` 2213TARGET_CFLAGS=" -march=armv7-a -mfloat-abi=softfp -mtune=generic-armv7-a -mfpu=neon -mthumb --target=arm-linux-ohosmusl -fPIC -ftls-model=global-dynamic -mtls-direct-seg-refs -DUSE_MUSL" 2214``` 2215 2216## LCD 2217 2218dayu200平台默认支持一个mipi接口的lcd屏幕 2219 2220LCD的适配主要依赖于HDF显示模型,显示驱动模型基于 HDF 驱动框架、Platform 接口及 OSAL 接口开发,可以屏蔽不同内核形态(LiteOS、Linux)差异,适用于不同芯片平台,为显示屏器件提供统一的驱动平台。 2221 2222如图为 HDF Display驱动模型层次关系 2223 2224![640](figures/dayu200/dayu200-lcd-01.png) 2225 2226当前驱动模型主要部署在内核态中,向上对接到 Display 公共 hal 层,辅助 HDI 的实现。显示驱动通过 Display-HDI 层对图形服务暴露显示屏驱动能力;向下对接显示屏 panel 器件,驱动屏幕正常工作,自上而下打通显示全流程通路。 2227 2228所以LCD的适配主要在于LCD panel器件驱动的适配 2229 2230器件驱动的适配分为2部分:panel驱动和hcs配置 2231 2232涉及的文件有: 2233 2234``` 2235drivers/framework/model/display/driver/panel 2236 2237vendor/hihope/rk3568/hdf_config/khdf/device_info 2238 2239vendor/hihope/rk3568/hdf_config/khdf/input 2240``` 2241 2242### panel驱动 2243 2244器件驱动主要围绕如下接口展开: 2245 2246``` 2247struct PanelData { 2248 struct HdfDeviceObject *object; 2249 int32_t (*init)(struct PanelData *panel); 2250 int32_t (*on)(struct PanelData *panel); 2251 int32_t (*off)(struct PanelData *panel); 2252 int32_t (*prepare)(struct PanelData *panel); 2253 int32_t (*unprepare)(struct PanelData *panel); 2254 struct PanelInfo *info; 2255 enum PowerStatus powerStatus; 2256 struct PanelEsd *esd; 2257 struct BacklightDev *blDev; 2258 void *priv; 2259}; 2260``` 2261 2262驱动中在初始化接口中实例化该结构体: 2263 2264 panelSimpleDev->panel.init = PanelSimpleInit; 2265 panelSimpleDev->panel.on = PanelSimpleOn; 2266 panelSimpleDev->panel.off = PanelSimpleOff; 2267 panelSimpleDev->panel.prepare = PanelSimplePrepare; 2268 panelSimpleDev->panel.unprepare = PanelSimpleUnprepare; 2269PanelSimpleInit负责panel的软件初始化 2270 2271PanelSimpleOn负责亮屏 2272 2273PanelSimpleOff负责灭屏 2274 2275PanelSimplePrepare负责亮屏的硬件时序初始化 2276 2277PanelSimpleUnprepare负责灭屏的硬件时序初始化 2278 2279实例化后使用RegisterPanel接口向display模型注册该panel驱动即可 2280 2281需要说明的是,dayu200上的这款lcd 使用的是DRM显示框架 2282 2283### hcs配置 2284 2285``` 2286device4 :: deviceNode { 2287 policy = 0; 2288 priority = 100; 2289 preload = 0; 2290 moduleName = "LCD_PANEL_SIMPLE"; 2291 } 2292``` 2293 2294## 背光 2295 2296基于HDF框架开发的 背光驱动模型 2297 2298![dayu200-backlight-01.png](figures/dayu200/dayu200-backlight-01.png) 2299 2300rk3568背光是通过pwm控制占空比实现的,具体使用的是pwm4 2301 2302原生背光驱动代码路径 2303 2304```c 2305linux-5.10/drivers/video/backlight/pwm_bl.c 2306linux-5.10/drivers/video/backlight/backlight.c 2307linux-5.10/drivers/pwm/pwm-rockchip.c 2308``` 2309 2310使用HDF框架下的背光驱动,需要关闭原生驱动 2311 2312```c 2313# CONFIG_BACKLIGHT_PWM is not set 2314``` 2315 2316### HDF实现 2317 2318代码路径 2319 2320```c 2321drivers/framework/model/display/driver/backlight/hdf_bl.c 2322``` 2323 2324HDF BL 入口函数 2325 2326```c 2327static int32_t BacklightInit(struct HdfDeviceObject *object) 2328{ 2329 if (object == NULL) { 2330 HDF_LOGE("%s: object is null!", __func__); 2331 return HDF_FAILURE; 2332 } 2333 HDF_LOGI("%s success", __func__); 2334 return HDF_SUCCESS; 2335} 2336 2337struct HdfDriverEntry g_blDevEntry = { 2338 .moduleVersion = 1, 2339 .moduleName = "HDF_BL", 2340 .Init = BacklightInit, 2341 .Bind = BacklightBind, 2342}; 2343 2344HDF_INIT(g_blDevEntry); 2345``` 2346 2347代码路径: 2348 2349```c 2350drivers/framework/model/display/driver/backlight/pwm_bl.c 2351``` 2352 2353HDF PWM 入口函数 2354 2355```c 2356struct HdfDriverEntry g_pwmBlDevEntry = { 2357 .moduleVersion = 1, 2358 .moduleName = "PWM_BL", 2359 .Init = BlPwmEntryInit, 2360}; 2361 2362HDF_INIT(g_pwmBlDevEntry); 2363``` 2364 2365具体控制背光的接口: 2366 2367```c 2368static int32_t BlPwmUpdateBrightness(struct BacklightDev *blDev, uint32_t brightness) 2369{ 2370 int32_t ret; 2371 uint32_t duty; 2372 struct BlPwmDev *blPwmDev = NULL; 2373 2374 blPwmDev = ToBlDevPriv(blDev); 2375 if (blPwmDev == NULL) { 2376 HDF_LOGE("%s blPwmDev is null", __func__); 2377 return HDF_FAILURE; 2378 } 2379 if (blPwmDev->props.maxBrightness == 0) { 2380 HDF_LOGE("%s maxBrightness is 0", __func__); 2381 return HDF_FAILURE; 2382 } 2383 if (brightness == 0) { 2384 return PwmDisable(blPwmDev->pwmHandle); 2385 } 2386 duty = (brightness * blPwmDev->config.period) / blPwmDev->props.maxBrightness; 2387 ret = PwmSetDuty(blPwmDev->pwmHandle, duty); 2388 if (ret != HDF_SUCCESS) { 2389 HDF_LOGE("%s: PwmSetDuty failed, ret %d", __func__, ret); 2390 return HDF_FAILURE; 2391 } 2392 return PwmEnable(blPwmDev->pwmHandle); 2393} 2394 2395static struct BacklightOps g_blDevOps = { 2396 .updateBrightness = BlPwmUpdateBrightness, 2397}; 2398``` 2399 2400其实使用的就是HDF PWM 实现的对接内核pwm的接口 2401 2402![dayu200-backlight-02.png](figures/dayu200/dayu200-backlight-02.png) 2403 2404在LCD HDF器件驱动注册背光 2405 2406代码路径 2407 2408```c 2409drivers/framework/model/display/driver/panel/ili9881c_boe.c 2410``` 2411 2412```c 2413ili9881cBoeDev->panel.blDev = GetBacklightDev("hdf_pwm"); 2414if (ili9881cBoeDev->panel.blDev == NULL) { 2415 HDF_LOGE("%s GetBacklightDev fail", __func__); 2416 goto FAIL; 2417} 2418``` 2419 2420### HCS配置 2421 2422驱动hcs配置 2423 2424```c 2425device_pwm_bl :: device { 2426 device0 :: deviceNode { 2427 policy = 0; 2428 priority = 95; 2429 preload = 0; 2430 moduleName = "PWM_BL"; 2431 deviceMatchAttr = "pwm_bl_dev"; 2432 } 2433} 2434device_backlight :: device { 2435 device0 :: deviceNode { 2436 policy = 2; 2437 priority = 90; 2438 preload = 0; 2439 permission = 0660; 2440 moduleName = "HDF_BL"; 2441 serviceName = "hdf_bl"; 2442 } 2443} 2444``` 2445 2446pwm背光的hcs配置 2447 2448```c 2449root { 2450 backlightConfig { 2451 pwmBacklightConfig { 2452 match_attr = "pwm_bl_dev"; 2453 pwmDevNum = 1; 2454 pwmMaxPeriod = 25000; 2455 backlightDevName = "hdf_pwm"; 2456 minBrightness = 0; 2457 defBrightness = 127; 2458 maxBrightness = 255; 2459 } 2460 } 2461} 2462``` 2463 2464### 测试 2465 2466cat /sys/kernel/debug/pwm 来查看hdf pwm 是否申请到pwm4 2467 2468申请成功有如下结果: 2469 2470requested 代表申请成功 2471 2472enabled 代表pwm4使能成功 2473 2474```c 2475# cat /sys/kernel/debug/pwm 2476 2477platform/fe6e0000.pwm, 1 PWM device 2478 pwm-0 ((null) ): requested enabled period: 25000 ns duty: 9705 ns polarity: normal 2479``` 2480 2481## **WIFI** 2482 2483### WIFI HDF化思路 2484 2485主要参考[《OpenHarmony HDF WLAN驱动分析》](https://mp.weixin.qq.com/s/iiE97pqPtzWIZadcjrQtsw)与使用 这篇文章,熟悉HDF WLAN的框架以及需要实现的主要接口,包括HDF驱动初始化接口、WLAN控制侧接口集、AP模式接口集、STA模式接口集、网络侧接口集、事件上报接口的实现。 2486 2487接下来熟悉HCS文件的格式以及"HDF WIFI”核心驱动框架的代码启动初始化过程,参考hi3881的代码进行改造。 2488 2489HDF WiFi框架总体框架图 2490 2491 ![image-20220320160720306](figures/dayu200/dayu200-wifi-01.png) 2492 2493### ap6275s驱动代码流程分析 2494 2495#### 驱动模块初始化流程分析 2496 2497![dayu200-wifi-02.png](figures/dayu200/dayu200-wifi-02.png) 2498 2499Ap6275s 是一款SDIO设备WiFi模组驱动,使用标准Linux的SDIO设备驱动。内核模块初始化入口module_init()调用dhd_wifi_platform_load_sdio()函数进行初始化工作,这里调用wifi_platform_set_power()进行GPIO上电,调用dhd_wlan_set_carddetect()进行探测SDIO设备卡,最后调用sdio_register_driver(&bcmsdh_sdmmc_driver);进行SDIO设备驱动的注册,SDIO总线已经检测到WiFi模块设备 根据设备号和厂商号与该设备驱动匹配, 所以立即回调该驱动的bcmsdh_sdmmc_probe()函数,这里进行WiFi模组芯片的初始化工作,最后创建net_device网络接口wlan0,然后注册到Linux内核协议栈中。 2500 2501l 创建net_device网络接口wlan0对象 2502 2503dhd_allocate_if()会调用alloc_etherdev()创建net_device对象,即wlan0网络接口。 2504 2505l 将wlan0注册到内核协议栈 2506 2507调用dhd_register_if()函数,这里调用register_netdev(net);将wlan0网络接口注册到协议栈。 2508 2509### 整改代码适配HDF WiFi框架 2510 2511对于系统WiFi功能的使用,需要实现AP模式、STA模式、P2P三种主流模式,这里使用wpa_supplicant应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现STA模式和P2P模式的功能,使用hostapd应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现AP模式和P2P模式的功能。 2512 2513Ap6275s WiFi6内核驱动依赖platform能力,主要包括SDIO总线的通讯能力;与用户态通信依赖HDF WiFi框架的能力,在确保上述能力功能正常后,即可开始本次WiFi驱动的HDF适配移植工作。本文档基于已经开源的rk3568开源版代码为基础版本,来进行此次移植。 2514 2515适配移植ap6275s WiFi6驱动涉及到的文件和目录如下: 2516 25171). 编译配置文件 2518 2519drivers/adapter/khdf/linux/model/network/wifi/Kconfig 2520 2521drivers/adapter/khdf/linux/model/network/wifi/vendor/Makefile 2522 25232). WiFi驱动源码目录 2524 2525原生驱动代码存放于: 2526 2527linux-5.10/drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd_wifi6/ 2528 2529在原生驱动上增加以及修改的代码文件位于: 2530 2531device/hihope/rk3568/wifi/bcmdhd_wifi6/ 2532 2533目录结构: 2534 2535``` 2536./device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf 2537├── hdf_bdh_mac80211.c 2538├── hdf_driver_bdh_register.c 2539├── hdfinit_bdh.c 2540├── hdf_mac80211_ap.c 2541├── hdf_mac80211_sta.c 2542├── hdf_mac80211_sta.h 2543├── hdf_mac80211_sta_event.c 2544├── hdf_mac80211_sta_event.h 2545├── hdf_mac80211_p2p.c 2546├── hdf_public_ap6275s.h 2547├── net_bdh_adpater.c 2548├── net_bdh_adpater.h 2549``` 2550 2551其中hdf_bdh_mac80211.c主要对g_bdh6_baseOps所需函数的填充, hdf_mac80211_ap.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_sta.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_p2p.c主要对g_bdh6_p2pOps所需函数进行填充,在openharmony/drivers/framework/include/wifi/wifi_mac80211_ops.h里有对wifi基本功能所需api的说明。 2552 2553#### 驱动文件编写 2554 2555HDF WLAN驱动框架由Module、NetDevice、NetBuf、BUS、HAL、Client 和 Message 这七个部分组成。开发者在WiFi驱动HDF适配过程中主要实现以下几部分功能: 2556 25571) 适配HDF WLAN框架的驱动模块初始化 2558 2559代码流程框图如下: 2560 2561![dayu200-wifi-03.png](figures/dayu200/dayu200-wifi-03.png) 2562 2563代码位于device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf_driver_bdh_register.c 2564 2565``` 2566struct HdfDriverEntry g_hdfBdh6ChipEntry = { 2567 .moduleVersion = 1, 2568 .Bind = HdfWlanBDH6DriverBind, 2569 .Init = HdfWlanBDH6ChipDriverInit, 2570 .Release = HdfWlanBDH6ChipRelease, 2571 .moduleName = "HDF_WLAN_CHIPS" 2572}; 2573HDF_INIT(g_hdfBdh6ChipEntry); 2574``` 2575 2576在驱动初始化时会实现SDIO主控扫描探卡、WiFi芯片初始化、主接口的创建和初始化等工作。 2577 25782) HDF WLAN Base控制侧接口的实现 2579 2580代码位于hdf_bdh_mac80211.c 2581 2582``` 2583static struct HdfMac80211BaseOps g_bdh6_baseOps = { 2584 .SetMode = BDH6WalSetMode, 2585 .AddKey = BDH6WalAddKey, 2586 .DelKey = BDH6WalDelKey, 2587 .SetDefaultKey = BDH6WalSetDefaultKey, 2588 .GetDeviceMacAddr = BDH6WalGetDeviceMacAddr, 2589 .SetMacAddr = BDH6WalSetMacAddr, 2590 .SetTxPower = BDH6WalSetTxPower, 2591 .GetValidFreqsWithBand = BDH6WalGetValidFreqsWithBand, 2592 .GetHwCapability = BDH6WalGetHwCapability, 2593 .SendAction = BDH6WalSendAction, 2594 .GetIftype = BDH6WalGetIftype, 2595}; 2596``` 2597 2598上述实现的接口供STA、AP、P2P三种模式中所调用。 2599 26003) HDF WLAN STA模式接口的实现 2601 2602STA模式调用流程图如下: 2603 2604 ![image-20220320161412663](figures/dayu200/dayu200-wifi-04.png) 2605 2606代码位于hdf_mac80211_sta.c 2607 2608``` 2609struct HdfMac80211STAOps g_bdh6_staOps = { 2610 .Connect = HdfConnect, 2611 .Disconnect = HdfDisconnect, 2612 .StartScan = HdfStartScan, 2613 .AbortScan = HdfAbortScan, 2614 .SetScanningMacAddress = HdfSetScanningMacAddress, 2615}; 2616``` 2617 26184) HDF WLAN AP模式接口的实现 2619 2620AP模式调用流程图如下: 2621 2622 ![image-20220320161432068](figures/dayu200/dayu200-wifi-05.png) 2623 2624代码位于hdf_mac80211_ap.c 2625 2626``` 2627struct HdfMac80211APOps g_bdh6_apOps = { 2628 .ConfigAp = WalConfigAp, 2629 .StartAp = WalStartAp, 2630 .StopAp = WalStopAp, 2631 .ConfigBeacon = WalChangeBeacon, 2632 .DelStation = WalDelStation, 2633 .SetCountryCode = WalSetCountryCode, 2634 .GetAssociatedStasCount = WalGetAssociatedStasCount, 2635 .GetAssociatedStasInfo = WalGetAssociatedStasInfo 2636}; 2637``` 2638 26395) HDF WLAN P2P模式接口的实现 2640 2641P2P模式调用流程图如下: 2642 2643 ![image-20220320161442845](figures/dayu200/dayu200-wifi-06.png) 2644 2645``` 2646struct HdfMac80211P2POps g_bdh6_p2pOps = { 2647 .RemainOnChannel = WalRemainOnChannel, 2648 .CancelRemainOnChannel = WalCancelRemainOnChannel, 2649 .ProbeReqReport = WalProbeReqReport, 2650 .AddIf = WalAddIf, 2651 .RemoveIf = WalRemoveIf, 2652 .SetApWpsP2pIe = WalSetApWpsP2pIe, 2653 .GetDriverFlag = WalGetDriverFlag, 2654}; 2655``` 2656 26576) HDF WLAN框架事件上报接口的实现 2658 2659WiFi驱动需要通过上报事件给wpa_supplicant和hostapd应用程序,比如扫描热点结果上报,新STA终端关联完成事件上报等等,HDF WLAN事件上报的所有接口请参考drivers/framework/include/wifi/hdf_wifi_event.h: 2660 2661事件上报HDF WLAN接口主要有: 2662 2663| 头文件 hdf_wifi_event.h接口名称 | 功能描述 | 2664| ----------------------------------- | ------------------------ | 2665| HdfWifiEventNewSta() | 上报一个新的sta事件 | 2666| HdfWifiEventDelSta() | 上报一个删除sta事件 | 2667| HdfWifiEventInformBssFrame() | 上报扫描Bss事件 | 2668| HdfWifiEventScanDone() | 上报扫描完成事件 | 2669| HdfWifiEventConnectResult() | 上报连接结果事件 | 2670| HdfWifiEventDisconnected() | 上报断开连接事件 | 2671| HdfWifiEventMgmtTxStatus() | 上报发送状态事件 | 2672| HdfWifiEventRxMgmt() | 上报接受状态事件 | 2673| HdfWifiEventCsaChannelSwitch() | 上报Csa频段切换事件 | 2674| HdfWifiEventTimeoutDisconnected() | 上报连接超时事件 | 2675| HdfWifiEventEapolRecv() | 上报Eapol接收事件 | 2676| HdfWifiEventResetResult() | 上报wlan驱动复位结果事件 | 2677| HdfWifiEventRemainOnChannel() | 上报保持信道事件 | 2678| HdfWifiEventCancelRemainOnChannel | 上报取消保持信道事件 | 2679 2680### 所有关键问题总结 2681 2682#### 调试AP模块时,启动AP模式的方法 2683 2684调试AP模块时,无法正常开启AP功能的解决方法 2685 2686需要使用到busybox和hostapd配置ap功能,操作步骤如下: 2687 2688``` 2689ifconfig wlan0 up 2690ifconfig wlan0 192.168.12.1 netmask 255.255.255.0 2691busybox udhcpd /data/udhcpd.conf 2692./hostapd -d /data/hostapd.conf 2693``` 2694 2695#### 调试STA模块时,启动STA模式的方法 2696 2697``` 2698wpa_supplicant -iwlan0 -c /data/l2tool/wpa_supplicant.conf -d & 2699./busybox udhcpc -i wlan0 -s /data/l2tool/dhcpc.sh 2700``` 2701 2702#### 扫描热点事件无法上报到wap_supplicant的解决办法 2703 2704wpa_supplicant 这个应用程序启动时不能加 -B参数后台启动,-B后台启动的话,调用poll()等待接收事件的线程会退出,所以无法接收上报事件, 2705 2706wpa_supplicant -iwlan0 -c /data/wpa_supplicant.conf & 这样后台启动就可以了。 2707 2708#### wpa2psk方式无法认证超时问题解决方法 2709 2710分析流程发现 hostapd没有接收到WIFI_WPA_EVENT_EAPOL_RECV = 13这个事件,原来是驱动没有将接收到的EAPOL报文通过HDF WiFi框架发送给hostapd进程,在驱动接收报文后,调用netif_rx()触发软中断前将EAPOL报文发送给HDF WiFi框架,认证通过了。 2711 2712#### P2P模式连接不成功问题定位分析 2713 2714在调试P2P连接接口时,发现手机P2P直连界面总是处于已邀请提示,无法连接成功,通过抓取手机和WiFi模组正常连接成功报文和HDF适配后连接失败的报文进行比对,在失败的报文组中,发现手机侧多回复了一帧ACTION报文,提示无效参数,然后终止了P2P连接。 2715 2716 ![image-20220320161303057](figures/dayu200/dayu200-wifi-07.png) 2717 2718最后比对WiFi模组向手机发送的ACTION报文内容,发现填充的P2P Device Info的MAC地址值不对,如下: 2719 2720正确帧内容: 2721 2722 ![image-20220320161314006](figures/dayu200/dayu200-wifi-08.png) 2723 2724错误帧内容: 2725 2726 ![image-20220320161318995](figures/dayu200/dayu200-wifi-09.png) 2727 2728最后经过分析MAC地址的填充部分代码,这个MAC地址是wpa_supplicant 根据p2p0的MAC地址填充的,所以将wdev对象(即p2p-dev-wlan0)的MAC地址更新给p2p0接口,二者保持一致即可,见代码wl_get_vif_macaddr(cfg, 7, p2p_hnetdev->macAddr);的调用。 2729 2730### 连接成功日志 2731 2732#### STA模式连接成功日志 2733 2734``` 2735WPA: Key negotiation completed with 50:eb:f6:02:8e6:d4 [PTK=CCMP GTK=CCMP] 2736 06 wlan0: State: GROUP_HANDSHAKEc -> COMPLETED 2737wlan0: CTRL-E4VENT-CONNECTED - Connection to 50:eb:f6:02:8e:d4 completed 3[id=0 id_str=] 2738WifiWpaReceived eEapol done 2739``` 2740 2741#### AP模式连接成功日志 2742 2743``` 2744wlan0: STA 96:27:b3:95:b7:6e IEEE 802.1X: au:thorizing port 2745wlan0: STA 96:27:b3:95:b7:6e WPA: pairwise key handshake completed (RSN) 2746WifiWpaReceiveEapol done 2747``` 2748 2749#### P2P模式连接成功日志 2750 2751``` 2752P2P: cli_channels: 2753EAPOL: External notificationtion - portValid=1 2754EAPOL: External notification:tion - EAP success=1 2755EAPOL: SUPP_PAE entering state AUTHENTIwCATING 2756EAPOL: SUPP_BE enterilng state SUCCESS 2757EAP: EAP ent_ering state DISABLED 2758EAPOL: SUPP_PAE entering state AUTHENTICATED 2759EAPOL:n Supplicant port status: Authoorized 2760EAPOL: SUPP_BE entertaining IDLE 2761WifiWpaReceiveEapol donepleted - result=SUCCESS 2762 2763\# ifconfig 2764 2765lo Link encap:Local Loopback 2766 inet addr:127.0.0.1 Mask:255.0.0.0 2767 inet6 addr: ::1/128 Scope: Host 2768 UP LOOPBACK RUNNING MTU:65536 Metric:1 2769 RX packets:12 errors:0 dropped:0 overruns:0 frame:0 2770 TX packets:12 errors:0 dropped:0 overruns:0 carrier:0 2771 collisions:0 txqueuelen:1000 2772 RX bytes:565 TX bytes:565 2773 2774wlan0 Link encap:Ethernet HWaddr 10:2c:6b:11:61:e0 Driver bcmsdh_sdmmc 2775 inet6 addr: fe80::122c:6bff:fe11:61e0/64 Scope: Link 2776 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 2777 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 2778 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 2779 collisions:0 txqueuelen:1000 2780 RX bytes:0 TX bytes:0 2781 2782p2p0 Link encap:Ethernet HWaddr 12:2c:6b:11:61:e0 2783 inet6 addr: fe80::102c:6bff:fe11:61e0/64 Scope: Link 2784 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 2785 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 2786 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 2787 collisions:0 txqueuelen:1000 2788 RX bytes:0 TX bytes:0 2789 2790p2p-p2p0-0 Link encap:Ethernet HWaddr 12:2c:6b:11:21:e0 Driver bcmsdh_sdmmc 2791 inet6 addr: fe80::102c:6bff:fe11:21e0/64 Scope: Link 2792 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 2793 RX packets:0 errors:0 dropped:9 overruns:0 frame:0 2794 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 2795 collisions:0 txqueuelen:1000 2796 RX bytes:0 TX bytes:0 2797``` 2798 2799## **BT** 2800 2801### HCI接口 2802 2803蓝牙整体硬件架构上分为主机(计算机或MCU)和主机控制器(实际蓝牙芯片组)两部分;主机和控制器之间的通信遵循主机控制器接口(HCI),如下所示: 2804 2805![dayu200-bt-01.png](figures/dayu200/dayu200-bt-01.png) 2806 2807HCI定义了如何交换命令,事件,异步和同步数据包。异步数据包(ACL)用于数据传输,而同步数据包(SCO)用于带有耳机和免提配置文件的语音。 2808 2809### 硬件连接 2810 2811从RK3568芯片描述中看,该芯片并不没有集成WIFI/蓝牙功能,都需要外接蓝牙芯片才能支持蓝牙功能,这也符合上述逻辑架构。那主机和控制器之间物理具体怎么连接呢?查看开发板规格书可以看的更清楚: 2812 2813![](figures/dayu200/dayu200-bt-02.png) 2814 2815其中,28-36号管脚就是UART(串口);同时还可以看到有几个管脚分别做电源和休眠控制。 2816 2817### 蓝牙VENDORLIB适配 2818 2819#### vendorlib是什么 2820 2821vendorlib部署在主机侧,可以认为是主机侧对蓝牙芯片驱动层,屏蔽不同蓝牙芯片的技术细节。从代码层面解读,其主要功能有两个: 2822 28231、为协议栈提供蓝牙芯片之间的通道(串口的文件描述符) 2824 28252、提供特定芯片的具体控制方法 2826 2827#### 代码层面解读vendorlib 2828 2829bt_vendor_lib.h 路径: 2830 2831``` 2832foundation/communication/bluetooth/services/bluetooth_standard/hardware/include 2833``` 2834 2835该文件定义了协议栈和vendor_lib交互接口,分为两组: 2836 28371、 vendorlib实现,协议栈调用 2838 2839```c 2840typedef struct { 2841 /** 2842 * Set to sizeof(bt_vendor_interface_t) 2843 */ 2844 size_t size; 2845 /** 2846 * Caller will open the interface and pass in the callback routines 2847 * to the implementation of this interface. 2848 */ 2849 int (*init)(const bt_vendor_callbacks_t* p_cb, unsigned char* local_bdaddr); 2850 2851 /** 2852 * Vendor specific operations 2853 */ 2854 int (*op)(bt_opcode_t opcode, void* param); 2855 2856 /** 2857 * Closes the interface 2858 */ 2859 void (*close)(void); 2860} bt_vendor_interface_t; 2861``` 2862 2863协议栈启动时的基本流程如下: 2864 28651.1、协议栈动态打开libbt_vendor.z.so,并调用init函数,初始化vendorlib 2866 28671.2、协议栈调用op函数,分别调用BT_OP_POWER_ON、BT_OP_HCI_CHANNEL_OPEN、BT_OP_INIT三个opcode;原则上BT_OP_INIT成功后说明芯片初始化完成。 2868 28692、协议栈实现,vendorlib调用(回调函数) 2870 2871```c 2872typedef struct { 2873 /** 2874 * set to sizeof(bt_vendor_callbacks_t) 2875 */ 2876 size_t size; 2877 2878 /* notifies caller result of init request */ 2879 init_callback init_cb; 2880 2881 /* buffer allocation request */ 2882 malloc_callback alloc; 2883 2884 /* buffer free request */ 2885 free_callback dealloc; 2886 2887 /* hci command packet transmit request */ 2888 cmd_xmit_callback xmit_cb; 2889} bt_vendor_callbacks_t; 2890``` 2891 2892init_cb在BT_OP_INIT完成后调用 2893 2894alloc/dealloc用于发送HCI消息时申请/释放消息控件 2895 2896xmit_cb发送HCI Commands 2897 2898vendor_lib实现的几个重要函数 2899 29001、 init函数 2901 2902```c 2903static int init(const bt_vendor_callbacks_t *p_cb, unsigned char *local_bdaddr) 2904{ 2905 /* * ... */ 2906 userial_vendor_init(); 2907 upio_init(); 2908 2909 vnd_load_conf(VENDOR_LIB_CONF_FILE); 2910 2911 /* store reference to user callbacks */ 2912 bt_vendor_cbacks = (bt_vendor_callbacks_t *)p_cb; 2913 /* This is handed over from the stack */ 2914 return memcpy_s(vnd_local_bd_addr, BD_ADDR_LEN, local_bdaddr, BD_ADDR_LEN); 2915} 2916``` 2917 2918vendorlib被调用的第一个函数,vendorlib保存好协议栈的callback和mac地址即可。 2919 29202、 BT_OP_POWER_ON对应处理 2921 2922观名知意,这个操作理论上需要拉高电源管脚电平;该函数中使用rfill设备来处理,并没有直接调用驱动拉高电平 2923 2924```c 2925int upio_set_bluetooth_power(int on) 2926{ 2927 int sz; 2928 int fd = -1; 2929 int ret = -1; 2930 char buffer = '0'; 2931 2932 switch (on) { 2933 case UPIO_BT_POWER_OFF: 2934 buffer = '0'; 2935 break; 2936 2937 case UPIO_BT_POWER_ON: 2938 buffer = '1'; 2939 break; 2940 default: 2941 return 0; 2942 } 2943 2944 /* check if we have rfkill interface */ 2945 if (is_rfkill_disabled()) { 2946 return 0; 2947 } 2948 2949 if (rfkill_id == -1) { 2950 if (init_rfkill()) { 2951 return ret; 2952 } 2953 } 2954 2955 fd = open(rfkill_state_path, O_WRONLY); 2956 if (fd < 0) { 2957 return ret; 2958 } 2959 2960 sz = write(fd, &buffer, 1); 2961 /* ... */ 2962 return ret; 2963} 2964``` 2965 29663、BT_OP_HCI_CHANNEL_OPEN对应处理 2967 2968```c 2969case BT_OP_HCI_CHANNEL_OPEN: { // BT_VND_OP_USERIAL_OPEN 2970 int(*fd_array)[] = (int(*)[])param; 2971 int fd, idx; 2972 fd = userial_vendor_open((tUSERIAL_CFG *)&userial_init_cfg); 2973 if (fd != -1) { 2974 for (idx = 0; idx < HCI_MAX_CHANNEL; idx++) 2975 (*fd_array)[idx] = fd; 2976 retval = 1; 2977 } 2978 /* retval contains numbers of open fd of HCI channels */ 2979 break; 2980``` 2981 2982userial_vendor_open函数打开串口设备(UART)得到文件描述符(fd),通过op的参数param返回该fd 2983 2984该串口设备在系统中的名字应该在开发板中预定义了,本次开发板上设备为/dev/ttyS8 2985 29864、BT_OP_INIT对应处理 2987 2988该操作码要求对蓝牙芯片进行初始化,具体要进行的处理和蓝牙芯片强相关。以本次调测的AP6257S芯片为例,初始化过程中主要是下发蓝牙固件。 2989 2990初始化结束后,必须调用init_cb回调函数(参见bt_vendor_callbacks_t)通知协议栈初始化结果,否则会阻塞协议栈线程导致蓝牙相关功能无法正常使用。协议栈的具体处理如下: 2991 2992协议栈调用BT_OP_INIT后会等待信号量,该信号量由init_cb函数置位 2993 2994```c 2995static int HciInitHal() 2996{ 2997 int result = BT_NO_ERROR; 2998 2999 g_waitHdiInit = SemaphoreCreate(0); 3000 int ret = g_hdiLib->hdiInit(&g_hdiCallbacks); 3001 if (ret == SUCCESS) { 3002 SemaphoreWait(g_waitHdiInit); 3003 } 3004} 3005``` 3006 3007### vendorlib移植问题 3008 30091、 vendorlib的so命名 3010 3011vendorlib必须是libbt_vendor.z.so;因为协议栈打开动态链接库就是这个名字 3012 30132、 固件问题 3014 3015开发时一定要关注芯片固件,有些蓝牙芯片可能无需升级固件,有些则必须升级固件;本次AP6257S适配过程中最开始没有下发固件,导致蓝牙接收信号很差。固件下发时需要注意如下两点: 3016 30172.1、对于AP6257S芯片,因为蓝牙芯片内并没有类似flash存储,要求芯片上下电后必须重新下发 3018 30192.2、按照芯片本身的要求处理,最好能找到厂商的参考代码;以Broadcom系列芯片为例,其固件下发过程比较复杂,通过一个状态机驱动;共如下9个状态 3020 3021```c 3022/ Hardware Configuration State */ 3023enum { 3024 HW_CFG_START = 1, 3025 HW_CFG_SET_UART_CLOCK, 3026 HW_CFG_SET_UART_BAUD_1, 3027 HW_CFG_READ_LOCAL_NAME, 3028 HW_CFG_DL_MINIDRIVER, 3029 HW_CFG_DL_FW_PATCH, 3030 HW_CFG_SET_UART_BAUD_2, 3031 HW_CFG_SET_BD_ADDR, 3032 HW_CFG_READ_BD_ADDR 3033}; 3034``` 3035 3036在收到BT_OP_INIT后初始化状态机,然后发送HCI_REST命令,切换状态为HW_CFG_START; 3037 3038```c 3039void hw_config_start(void) 3040{ 3041 HC_BT_HDR *p_buf = NULL; 3042 uint8_t *p; 3043 hw_cfg_cb.state = 0; 3044 hw_cfg_cb.fw_fd = -1; 3045 hw_cfg_cb.f_set_baud_2 = FALSE; 3046 3047 if (bt_vendor_cbacks) { 3048 p_buf = (HC_BT_HDR *)bt_vendor_cbacks->alloc(BT_HC_HDR_SIZE + 3049 HCI_CMD_PREAMBLE_SIZE); 3050 } 3051 3052 if (p_buf) { 3053 p_buf->event = MSG_STACK_TO_HC_HCI_CMD; 3054 p_buf->offset = 0; 3055 p_buf->layer_specific = 0; 3056 p_buf->len = HCI_CMD_PREAMBLE_SIZE; 3057 3058 p = (uint8_t *)(p_buf + 1); 3059 UINT16_TO_STREAM(p, HCI_RESET); 3060 *p = 0; 3061 3062 hw_cfg_cb.state = HW_CFG_START; 3063 bt_vendor_cbacks->xmit_cb(HCI_RESET, p_buf); 3064 } else { 3065 if (bt_vendor_cbacks) { 3066 HILOGE("vendor lib fw conf aborted [no buffer]"); 3067 bt_vendor_cbacks->init_cb(BTC_OP_RESULT_FAIL); 3068 } 3069 } 3070} 3071``` 3072 3073收到芯片返回的HCI_RESET完成事件后,继续切换到下一个状态机并发送下一个COMMAND,一直到状态机完成固件下发。 3074 3075详细实现请参见hw_config_cback函数。 3076 30773、 关注系统间接口差异 3078 3079不同系统的接口可能有一些细微差异,需要重点关注;对比其他系统和OHOS的接口,vendorlib调用xmit_cb发送HCI命令的函数定义略有差异 3080 3081其他系统: 3082 3083```c 3084/* define callback of the cmd_xmit_cb 3085 * 3086 3087The callback function which HCI lib will call with the return of command 3088 3089complete packet. Vendor lib is responsible for releasing the buffer passed 3090 3091in at the p_mem parameter by calling dealloc callout function. 3092*/ 3093typedef void (*tINT_CMD_CBACK)(void* p_mem); 3094typedef uint8_t (*cmd_xmit_cb)(uint16_t opcode, void* p_buf, tINT_CMD_CBACK p_cback); 3095``` 3096 3097OHOS: 3098 3099```c 3100/** 3101 3102hci command packet transmit callback 3103 3104Vendor lib calls cmd_xmit_cb function in order to send a HCI Command 3105 3106packet to BT Controller. 3107* 3108 3109The opcode parameter gives the HCI OpCode (combination of OGF and OCF) of 3110 3111HCI Command packet. For example, opcode = 0x0c03 for the HCI_RESET command 3112 3113packet. */ 3114 3115typedef uint8_t (*cmd_xmit_callback)(uint16_t opcode, void* p_buf); 3116``` 3117 3118也就是说vendorlib中发送命令后,其他系统会直接调用callback通知芯片返回的消息,OHOS则是通过BT_OP_EVENT_CALLBACK操作码(参见bt_opcode_t定义)通知芯片返回的消息;vendorlib需要解析报文中的消息码确认芯片是处理的哪个消息,然后调用对应的处理函数。 3119 3120```c 3121void hw_process_event(HC_BT_HDR *p_buf) 3122{ 3123 uint16_t opcode; 3124 uint8_t *p = (uint8_t *)(p_buf + 1) + HCI_EVT_CMD_CMPL_OPCODE; 3125 STREAM_TO_UINT16(opcode, p); 3126 switch (opcode) { 3127 case HCI_VSC_WRITE_BD_ADDR: 3128 #if (USE_CONTROLLER_BDADDR == TRUE) 3129 case HCI_READ_LOCAL_BDADDR: 3130 #endif 3131 case HCI_READ_LOCAL_NAME: 3132 case HCI_VSC_DOWNLOAD_MINIDRV: 3133 case HCI_VSC_WRITE_FIRMWARE: 3134 case HCI_VSC_LAUNCH_RAM: 3135 case HCI_RESET: 3136 case HCI_VSC_WRITE_UART_CLOCK_SETTING: 3137 case HCI_VSC_UPDATE_BAUDRATE: 3138 hw_config_cback(p_buf); 3139 break; 3140``` 3141 3142另外,OHOS返回的是发送消息的字节数,<=0为发送失败,和其他系统接口的返回值也不同 3143 31444、 snoop日志 3145 3146其他系统中记录了HCI交互消息,OHOS同样有记录;OHOS系统生成文件为/data/log/bluetooth/snoop.log,通过wireshark或其它报文分析工具可以看到Host和Controller之间的交互流程,有助于问题分析 3147 3148## Sensor 3149 3150 基于HDF(Hardware Driver Foundation)驱动框架开发的Sensor驱动模型 3151 3152![dayu200-sensor-01.png](figures/dayu200/dayu200-sensor-01.png) 3153 3154rk3568 支持accel sensor,整体的驱动框架openharmony 主线已经具备,只需要实现具体的器件驱动即可。 3155 3156### mcx5566xa HDF驱动实现 3157 3158RK3568平台支持加速度传感器,型号是MXC6655XA,具体配置可以查看该器件的datasheet。 移植HDF前,需要确认内核该sensor的编译使能是关闭的。 3159 3160配置文件路径kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig 3161 3162```c 3163# CONFIG_GS_MXC6655XA is not set 3164``` 3165 3166代码路径: 3167 3168```c 3169drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.c 3170drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.h 3171``` 3172 3173编译宏 3174 3175```c 3176CONFIG_DRIVERS_HDF_SENSOR_ACCEL_MXC6655XA=y 3177``` 3178 3179Mxc6655xa 加速度计驱动入口函数实现 3180 3181```c 3182struct HdfDriverEntry g_accelMxc6655xaDevEntry = { 3183 .moduleVersion = 1, 3184 .moduleName = "HDF_SENSOR_ACCEL_MXC6655XA", 3185 .Bind = Mxc6655xaBindDriver, 3186 .Init = Mxc6655xaInitDriver, 3187 .Release = Mxc6655xaReleaseDriver, 3188}; 3189 3190HDF_INIT(g_accelMxc6655xaDevEntry); 3191``` 3192 3193接下来就是差异化适配函数 3194 3195```c 3196struct AccelOpsCall { 3197int32_t (*Init)(struct SensorCfgData *data); 3198int32_t (*ReadData)(struct SensorCfgData *data); 3199}; 3200``` 3201 3202获取x, y, z三轴数据接口 3203 3204```c 3205int32_t ReadMxc6655xaData(struct SensorCfgData *cfg, struct SensorReportEvent *event) 3206{ 3207 int32_t ret; 3208 struct AccelData rawData = { 0, 0, 0 }; 3209 static int32_t tmp[ACCEL_AXIS_NUM]; 3210 3211 CHECK_NULL_PTR_RETURN_VALUE(cfg, HDF_ERR_INVALID_PARAM); 3212 CHECK_NULL_PTR_RETURN_VALUE(event, HDF_ERR_INVALID_PARAM); 3213 3214 ret = ReadMxc6655xaRawData(cfg, &rawData, &event->timestamp); 3215 if (ret != HDF_SUCCESS) { 3216 HDF_LOGE("%s: MXC6655XA read raw data failed", __func__); 3217 return HDF_FAILURE; 3218 } 3219 3220 event->sensorId = SENSOR_TAG_ACCELEROMETER; 3221 event->option = 0; 3222 event->mode = SENSOR_WORK_MODE_REALTIME; 3223 3224 rawData.x = rawData.x * MXC6655XA_ACC_SENSITIVITY_2G; 3225 rawData.y = rawData.y * MXC6655XA_ACC_SENSITIVITY_2G; 3226 rawData.z = rawData.z * MXC6655XA_ACC_SENSITIVITY_2G; 3227 3228 tmp[ACCEL_X_AXIS] = (rawData.x * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT; 3229 tmp[ACCEL_Y_AXIS] = (rawData.y * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT; 3230 tmp[ACCEL_Z_AXIS] = (rawData.z * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT; 3231 3232 ret = SensorRawDataToRemapData(cfg->direction, tmp, sizeof(tmp) / sizeof(tmp[0])); 3233 if (ret != HDF_SUCCESS) { 3234 HDF_LOGE("%s: MXC6655XA convert raw data failed", __func__); 3235 return HDF_FAILURE; 3236 } 3237 3238 event->dataLen = sizeof(tmp); 3239 event->data = (uint8_t *)&tmp; 3240 3241 return ret; 3242} 3243``` 3244 3245初始化 3246 3247```c 3248static int32_t InitMxc6655xa(struct SensorCfgData *data) 3249{ 3250 int32_t ret; 3251 3252 CHECK_NULL_PTR_RETURN_VALUE(data, HDF_ERR_INVALID_PARAM); 3253 ret = SetSensorRegCfgArray(&data->busCfg, data->regCfgGroup[SENSOR_INIT_GROUP]); 3254 if (ret != HDF_SUCCESS) { 3255 HDF_LOGE("%s: MXC6655XA sensor init config failed", __func__); 3256 return HDF_FAILURE; 3257 } 3258 return HDF_SUCCESS; 3259} 3260``` 3261 3262### hcs配置 3263 3264Mxc6655xa accel sensor 驱动HCS配置 3265 3266```c 3267device_sensor_mxc6655xa :: device { 3268 device0 :: deviceNode { 3269 policy = 1; 3270 priority = 120; 3271 preload = 0; 3272 permission = 0664; 3273 moduleName = "HDF_SENSOR_ACCEL_MXC6655XA"; 3274 serviceName = "hdf_accel_mxc6655xa"; 3275 deviceMatchAttr = "hdf_sensor_accel_mxc6655xa_driver"; 3276 } 3277} 3278``` 3279 3280Mxc6655xa accel sensor 寄存器组配置信息 3281 3282```c 3283#include "../sensor_common.hcs" 3284root { 3285 accel_mxc6655xa_chip_config : sensorConfig { 3286 match_attr = "hdf_sensor_accel_mxc6655xa_driver"; 3287 sensorInfo :: sensorDeviceInfo { 3288 sensorName = "accelerometer"; 3289 vendorName = "memsi_mxc6655xa"; // max string length is 16 bytes 3290 sensorTypeId = 1; // enum SensorTypeTag 3291 sensorId = 1; // user define sensor id 3292 power = 230; 3293 } 3294 sensorBusConfig :: sensorBusInfo { 3295 busType = 0; // 0:i2c 1:spi 3296 busNum = 5; 3297 busAddr = 0x15; 3298 regWidth = 1; // 1byte 3299 } 3300 sensorIdAttr :: sensorIdInfo { 3301 chipName = "mxc6655xa"; 3302 chipIdRegister = 0x0f; 3303 chipIdValue = 0x05; 3304 } 3305 sensorDirection { 3306 direction = 5; // chip direction range of value:0-7 3307 /* <sign> 1:negative 0:positive 3308 <map> 0:AXIS_X 1:AXIS_Y 2:AXIS_Z 3309 */ 3310 /* sign[AXIS_X], sign[AXIS_Y], sign[AXIS_Z], map[AXIS_X], map[AXIS_Y], map[AXIS_Z] */ 3311 convert = [ 3312 0, 0, 0, 0, 1, 2, 3313 1, 0, 0, 1, 0, 2, 3314 0, 0, 1, 0, 1, 2, 3315 0, 1, 0, 1, 0, 2, 3316 1, 0, 1, 0, 1, 2, 3317 0, 0, 1, 1, 0, 2, 3318 0, 1, 1, 0, 1, 2, 3319 1, 1, 1, 1, 0, 2 3320 ]; 3321 } 3322 sensorRegConfig { 3323 /* regAddr: register address 3324 value: config register value 3325 len: size of value 3326 mask: mask of value 3327 delay: config register delay time (ms) 3328 opsType: enum SensorOpsType 0-none 1-read 2-write 3-read_check 4-update_bit 3329 calType: enum SensorBitCalType 0-none 1-set 2-revert 3-xor 4-left shift 5-right shift 3330 shiftNum: shift bits 3331 debug: 0-no debug 1-debug 3332 save: 0-no save 1-save 3333 */ 3334 /* regAddr, value, mask, len, delay, opsType, calType, shiftNum, debug, save */ 3335 initSeqConfig = [ 3336 0x7e, 0xb6, 0xff, 1, 5, 2, 0, 0, 0, 0, 3337 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 3338 ]; 3339 enableSeqConfig = [ 3340 0x7e, 0x11, 0xff, 1, 5, 2, 0, 0, 0, 0, 3341 0x41, 0x03, 0xff, 1, 0, 2, 0, 0, 0, 0, 3342 0x40, 0x08, 0xff, 1, 0, 2, 0, 0, 0, 0 3343 ]; 3344 disableSeqConfig = [ 3345 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 3346 ]; 3347 } 3348 } 3349} 3350``` 3351 3352### 测试 3353 3354UT测试可以获取到sensor的三轴数据 3355 3356测试代码路径 3357 3358```c 3359drivers/peripheral/sensor/test/unittest/common/hdf_sensor_test.cpp 3360``` 3361 3362编译UT代码命令: 3363 3364```c 3365./build.sh --product-name rk3568 --build-target hdf_test_sensor 3366``` 3367 3368将hdf_test_sensor.bin push到system/bin目录,添加执行权限,执行 3369 3370有如下结果代表sensor 测试成功 3371 3372```c 3373SensorTestDataCallback enter 3374sensor id :[1], data[1]: 0.001877 3375sensor id :[1], data[2]: 0.160823 3376sensor id :[1], data[3]: 0.046122 3377``` 3378 3379## Vibrator 3380 3381### vibrator 模型 3382 3383Vibrator驱动模型主要包含Vibrator(传感器)相关的HDI接口与实现,提供Vibrator HDI(Hardware Driver Interface)能力接口,支持静态HCS配置的时间序列和动态配置持续时间两种振动效果。调用StartOnce接口动态配置持续振动时间;调用StartEffect接口启动静态配置的振动效果。 3384 3385**图 1** Vibrator驱动模型图 3386 3387![dayu200-vibrator-01.png](figures/dayu200/dayu200-vibrator-01.png) 3388 3389rk3568 支持线性马达,整体的驱动框架openharmony 主线已经具备,只需要实现具体的器件驱动即可。 3390 3391### HDF驱动实现 3392 3393代码路径: 3394 3395```c 3396drivers/framework/model/misc/vibrator/driver/chipset/vibrator_linear_driver.c 3397``` 3398 3399linear Vibrator加速度计驱动入口函数实现 3400 3401```c 3402struct HdfDriverEntry g_linearVibratorDriverEntry = { 3403 .moduleVersion = 1, 3404 .moduleName = "HDF_LINEAR_VIBRATOR", 3405 .Bind = BindLinearVibratorDriver, 3406 .Init = InitLinearVibratorDriver, 3407 .Release = ReleaseLinearVibratorDriver, 3408}; 3409 3410HDF_INIT(g_linearVibratorDriverEntry); 3411``` 3412 3413### hcs配置 3414 3415驱动hcs配置 3416 3417```c 3418 vibrator :: host { 3419 hostName = "vibrator_host"; 3420 device_vibrator :: device { 3421 device0 :: deviceNode { 3422 policy = 2; 3423 priority = 100; 3424 preload = 0; 3425 permission = 0664; 3426 moduleName = "HDF_VIBRATOR"; 3427 serviceName = "hdf_misc_vibrator"; 3428 deviceMatchAttr = "hdf_vibrator_driver"; 3429 } 3430 } 3431 device_linear_vibrator :: device { 3432 device0 :: deviceNode { 3433 policy = 1; 3434 priority = 105; 3435 preload = 0; 3436 permission = 0664; 3437 moduleName = "HDF_LINEAR_VIBRATOR"; 3438 serviceName = "hdf_misc_linear_vibrator"; 3439 deviceMatchAttr = "hdf_linear_vibrator_driver"; 3440 } 3441 } 3442 } 3443``` 3444 3445线性马达器件hcs配置 3446 3447```c 3448root { 3449 linearVibratorConfig { 3450 boardConfig { 3451 match_attr = "hdf_linear_vibrator_driver"; 3452 vibratorChipConfig { 3453 busType = 1; // 0:i2c 1:gpio 3454 gpioNum = 154; 3455 startReg = 0; 3456 stopReg = 0; 3457 startMask = 0; 3458 } 3459 } 3460 } 3461} 3462``` 3463 3464### UT测试 3465 3466测试代码路径 3467 3468```c 3469drivers/peripheral/misc/vibrator/test/unittest/common/hdf_vibrator_test.cpp 3470``` 3471 3472编译UT代码命令 3473 3474```c 3475./build.sh --product-name rk3568 --build-target hdf_test_vibrator 3476``` 3477 3478将hdf_test_vibrator.bin push到system/bin目录,添加执行权限,执行 3479 3480``` 3481[ RUN ] HdfVibratorTest.CheckVibratorInstanceIsEmpty 3482[ OK ] HdfVibratorTest.CheckVibratorInstanceIsEmpty (0 ms) 3483[ RUN ] HdfVibratorTest.PerformOneShotVibratorDuration001 3484[ OK ] HdfVibratorTest.PerformOneShotVibratorDuration001 (2001 ms) 3485[ RUN ] HdfVibratorTest.ExecuteVibratorEffect001 3486[ OK ] HdfVibratorTest.ExecuteVibratorEffect001 (5001 ms) 3487``` 3488 3489