编辑
2025-01-20
工作知识
0

openharmony有多种音频调试方案,这里介绍和解读一下openharmony的音频方案。

一、参考文档

根据openharmony在社区分享的文章《audio适配方案》,我们可以知道对于openharmony系统适配音频有四种方案,分别如下:

  • adm驱动适配
  • alsalib适配标准alsa
  • hdi to hidl对接
  • hdi对接vender hal
    对于上面四种方案,我相信大部分人第一次看到会有点犯懵,所以这里主要解释一下这四种方案的具体意思。从而理解openharmony是如何处理音频的。这四种方案的图示如下:

image.png

二、ADM适配方法

ADM的全称是audio driver mode,这是openharmony系统自身的一套音频框架,它仿照alsa的基本概念做了改造和裁剪,其基本思想还是基于alsa。所以如下

image.png 对比于alsa,如下

image.png 所以我们可以知道

ADM的的实现如下:

platform-----dai-----codec

这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动

alsa的实现如下:

platform-----dai-----codec

这里platform实现了平台dma/cpu的驱动,dai实现了i2s的驱动,codec实现了具体声卡的驱动

可以发现ADM的本质和ASoC完全相同,但如果我不解释,光看图,或许以为ADM与ASoC不太一致。

其主要原因是openharmony的框架图中,故意模糊了概念,而ASoC的框架更清晰能够一眼看懂

至于其他的,也大同小异

  • alsa的machine对比于ADM的card manager

  • alsa的dapm对于于ADM的sapm

  • alsa的controls对比ADM的control dispatch

  • alsa的pcm interface对比于ADM的stream dispatch

2.1 ADM我们需要做什么

根据上面的分析,我们可以知道ADM是改造的ALSA,所以对于ADM的开发,我们需要遵循ADM的框架,编写HDF driver,实现Codec驱动即可

三、alsa适配方法

对于alsa而言,驱动可以完全使用标准linux的ASoC框架,上层则配置使能alsalib即可,对于openharmony,根据其系统的设计,我们还需要适配openharmony的上层支撑,这里是supportlib的实现。

所以关键点有三个,如下:

3.1 内核走标准alsa

CONFIG_SOUND=y CONFIG_SND=y # CONFIG_DRIVERS_HDF_AUDIO is not set # CONFIG_DRIVERS_HDF_AUDIO_RK3568 is not set

3.2 内核调用库走alsalib

通过配置项drivers_peripheral_audio_feature_alsa_lib来控制代码的执行逻辑,如果是drivers_peripheral_audio_feature_alsa_lib=true,则默认使用alsalib的libasound库

具体代码路径如下:

drivers/peripheral/audio/hdi_service

3.3 系统调用库走alsa的实现

根据上面的方法,已经可以确保系统的音频走alsalib的libasound的了,但是openharmony系统需要能够使用音频,需要对音频的api做一下重实现,这里已经完成了,就是supportlibs,代码仓库位置如下:

drivers/peripheral/audio/supportlibs

这里文件情况如下:

adm_adapter alsa_adapter interfaces

可以知道,对于ADM,默认走adm_adapter,而对于alsa我们走的是alsa_adapter,其中interfaces对上层的抽象,提供了上层需要的api接口

对于上层需要的接口,简单介绍如下:

image.png 至此,openharmony走alsa的理论路径如下(自下向上):

ASoC---->libasound---->supportlibs(alsa_adapter)--→openharmony api

四、hdi to hidl方案

如果不太熟悉android的同学,我们在提到hidl,会不太清楚,再者,在《audio适配方案》中, 里面描述的是HDIL,这个东西没有任何的解释,所以我认为这是华为的人编写文档的时候的一个书写错误,习以为常就好啦。

这里提到的hidl是借鉴了安卓的思路,安卓在对于应用层之间,使用aidl,在对于设备抽象层之间,使用hidl。对于安卓,其实现方式如下(自上到下):

SystemApp--->Android Framework ---> HIDL Binder--->HIDL Service Manager----> HAL

所以openharmony也在借鉴安卓的思路,实现自己的hidl,故我们可以看到如下言论

image.png

对于openharmony的idl,对于文档如下:

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/IDL/idl-guidelines.md

目前实现的是应用和服务之间的通信方案,对比于安卓的aidl。

所以可以通过查找代码,根据audio的实现可以知道,如果需要实现hidl,则需要实现hidl的服务,类比于安卓的hidl service manager。该仓库应该在

drivers/peripheral/codec/hal/idl_service

但是目前看来,没有良好的实现和落地,还需要进一步发展。此方案不容易落地。未来可能是openharmony努力的目标。

五、hdi对接vendor hal方案

此方案的意图在于OEM自行根据HDI的接口自己实现,哪有一个操作系统说我提供上层api,下层自己实现的说法,故不现实。

根据此说法,应该实现路径如下(自低到上):

alsa/自己实现audio框架(openharmony不干)--->自己实现vendor hal(openharmony不干)--->vendor hal对接hdi api(openharmony上层api)

如果底层使用alsa,则第三章的方案中的alsalib和supportlibs需要自行实现成vendor hal,所以这属于一个异想天开的方案,当前阶段不必理会。

编辑
2025-01-20
工作知识
0

我们基于RK3568在众达板卡上开发了Openharmony,相关仓库通过repo管理,故代码可以直接repo拉取和编译,下文说明拉取方法和编译步骤

一、仓库地址

Openharmony的代码仓库地址在:https://gitlab2.kylin.com/sh-product-Embedded/openharmony,这些仓库包含我们基于Openharmony的修改内容

二、repo获取仓库

拉取命令如下

repo init -u https://gitlab2.kylin.com/sh-product-Embedded/openharmony/manifest.git -b OpenHarmony-4.0-Release -g ohos:standard --no-repo-verify

拉取时,如果出现如下错误,则可以两个方向排除

image.png

2.1 服务器上网认证

服务器需要认证,建议找服务器的owner私下聊天沟通一下,认证网络后,才能拉到gitee/openharmony的仓库代码

2.2 gitlab2证书异常

这种情况下是应该https/http拉取代码的ca证书不信任导致,这种情况需要设置宏如下:

export GIT_SSL_NO_VERIFY=1

三、同步代码

在repo init之后,就可以开始同步代码,如下命令

repo sync -c repo forall -c 'git lfs pull'

注意repo拉取git lfs可能因为网络问题出错,如果失败可以多尝试几次

代码同步之后可以发现如下几个目录正常即可

device/board/allgo/ device/board/allgo/mbrc3568a/kernel/linux_sdk/ vendor/allgo/

四、下载prebuilts

代码拉取完成后,需要拉取prebuilts如下:

build/prebuilts_download.sh

五、编译

编译之前需要添加白名单如下:

cd $sdk_path/build patch -p1 < device/board/allgo/mbrc3568a/build_whitelist_for_mbrc3568a.patch

然后执行编译脚本如下

./build.sh --product-name mbrc3568a --ccache --no-prebuilt-sdk

六、镜像位置

编译完成之后,镜像位置在:

out/mbrc3568a/packages/phone/images/
编辑
2025-01-20
工作知识
0

在编译Openharmony的时候,经常出现类似如下错误:exceptions.ohos_exception.OHOSException: subsystem name config incorrect in '/root/tf/new_openharmony/vendor/hihope/dayu210/ohos.build', build file subsystem name is product_hihope,configured subsystem name is product_dayu210。这是因为白名单设置问题导致,所以需要修改白名单,如下是思路

一、相关文件

对于白名单文件,涉及到的是compile_standard_whitelist.json,此文件在build仓库目录下。

二、"bundle_subsystem_error"

"bundle_subsystem_error"是用于检测是否存在子系统,在hb/services/loader.py中将其获取到bundle_subsystem_allow_list

bundle_subsystem_allow_list = compile_standard_allow_info.get("bundle_subsystem_error", [])

在hb/util/loader/load_ohos_build.py中会根据build_files来遍历subsystem名字,如果不匹配则报错。逻辑如下

if _subsystem_name != self._subsystem_name: is_allow = False for file_path in self._bundle_subsystem_allow_list: if _build_file.endswith(file_path): is_allow = True break if is_allow: print("warning: subsystem name config incorrect in '{}', build file subsystem name is {}," "configured subsystem name is {}.".format( _build_file, _subsystem_name, self._subsystem_name)) else: raise OHOSException("subsystem name config incorrect in '{}', build file subsystem name is {}," "configured subsystem name is {}.".format( _build_file, _subsystem_name, self._subsystem_name), 2014)

针对此,我们需要将我们的3588仓库添加到compile_standard_whitelist.json中,如下

+ "device/board/hihope/dayu210/ohos.build", + "vendor/hihope/dayu210/ohos.build"

三、"subsystem_components"

这里涉及子系统和组件的白名单,如果需要编译相关的组件,则需要添加,我这里添加如下:

@@ -32,6 +45,15 @@ "//device/soc/rockchip/rk3568/hardware/omx_il/component/video/enc:libomxvpu_enc", "//device/soc/rockchip/rk3568/hardware/omx_il/core:libOMX_Core", "//device/soc/rockchip/rk3568/hardware/omx_il/libOMXPlugin:libOMX_Pluginhw", + "//device/soc/rockchip/rk3568/hardware/npu:libnpu_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/codec:libcodec_oem_interface", + "//device/soc/rockchip/rk3588/hardware/display:display_composer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:display_gfx", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_buffer_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_buffer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_composer_vdi_impl", + "//device/soc/rockchip/rk3588/hardware/display:libhigbm_vendor", + "//device/soc/rockchip/rk3588/hardware/mpp/mpp-develop/mpp/legacy:rockchip_vpu",

out/rk3588/build_configs/parts_info/part_subsystem.json中的内容会在编译的时候解析,如果不匹配则按照如下逻辑出错

for component in components_name: if component['component'] in list(subsystem_compoents_whitelist_info.keys()): continue overrided_components_name = '{}_{}'.format(component['component'], 'override') if component['component'] in list(part_subsystem_component_info.keys()) \ or overrided_components_name in list(part_subsystem_component_info.keys()): if subsystem_name in list(part_subsystem_component_info.values()): continue if subsystem_name == component['component']: continue name = subsystem_name message = "find subsystem {} failed, please check it in {}.".format(subsystem_name, config_path) else: name = component['component'] message = "find component {} failed, please check it in {}.".format(component['component'], config_path) if name in subsystem_components_list: print(f"Warning: {message}") else: raise Exception(message)

这里涉及两方面,一个是vendor/hihope/dayu210/config.json,这是Openharmony产品需要打开的功能配置,会生成part_subsystem.json

另一个是仓库本身的bundle.json,这里会声明此仓库属于哪个subsystem和哪个component,所以如果不匹配就会出现

3.1 "gn_part_or_subsystem_error"

"gn_part_or_subsystem_error"用来判断build_configs/parts_info/part_subsystem.json的subsystems name和gn的BUILD.gn的subsystem是否对应,如果不对应,则按照下面逻辑报错

if subsystems_name is None or subsystems_name == '' or subsystems_name != args.subsystem_name: message = f"subsystem name or part name is incorrect, " \ f"target is {args.target_path}, subsystem name is {args.subsystem_name}, " \ f"part name is {args.part_name}" if args.target_path in bundle_file_allow_list: print(f"[0/0] warning: {message}") else: raise Exception(message)

3.2 "deps_added_external_part_module"

"deps_added_external_part_module"是依赖模块的白名单检测,对应BUILD.gn的deps,如果依赖存在异常,则如下逻辑报错

for dep in args.deps: dep_path = get_path_from_label(dep) if dep_path.find('third_party/rust/crates') != -1: continue if dep_path.find('third_party') != -1: dep_part = get_dep_part(dep_path, third_party_info) tips_info = "{} depend part {}, need set part deps {} info to".format( args.target_path, dep, dep_part) check_third_party_deps(args, dep_part, parts_deps_info, tips_info, third_deps_allow_list) continue match_flag = False for pattern in part_pattern: if dep_path.startswith(pattern): match_flag = True break if match_flag is False: message = "deps validation part_name: '{}', target: '{}', dep: '{}' failed!!!".format( args.part_name, args.target_path, dep) if args.target_path in deps_allow_list: print(f"[0/0] WARNING:{message}") else: raise Exception(message)

3.3 "external_deps_added_self_part_module"

"external_deps_added_self_part_module"是外部依赖的白名单检测,对应BUILD.gn的external_deps,如果依赖存在异常,则如下逻辑报错

if external_part_name == args.part_name: message = "{} in target {} is dependency within part {}, Need to used deps".format( external_part_name, args.target_path, args.part_name) if args.target_path in added_self_part_allow_list: print(f"[0/0] WARNING: {message}") return else: raise Exception(message)

3.4 "external_deps_bundle_not_add"

"external_deps_bundle_not_add"同上一致,但是他会按照如下逻辑报错

part_deps_info = parts_deps_info.get(args.part_name) if not part_deps_info: _warning_info = "{} {}.".format(_tips_info, args.part_name) elif not part_deps_info.get('components') or \ not external_part_name in part_deps_info.get('components'): _warning_info = "{} {}.".format(_tips_info, part_deps_info.get('build_config_file')) else: _warning_info = "" if _warning_info != "": if args.target_path in bundle_not_add_allow_list: print(f"[0/0] WARNING: {_warning_info}") else: raise Exception(_warning_info)

3.5 "third_deps_bundle_not_add"

"third_deps_bundle_not_add"是第三方依赖白名单,如果白名单没有第三方依赖库,则按照如下逻辑报错

def check_third_party_deps(args, dep_part, parts_deps_info, _tips_info, third_deps_allow_list): """check whether the three-party dependency is in the part declaration""" if args.part_name == dep_part: return part_deps_info = parts_deps_info.get(args.part_name) if not part_deps_info: _warning_info = f"{_tips_info} {args.part_name}." elif not part_deps_info.get('third_party') or \ not dep_part in part_deps_info.get('third_party'): _warning_info = f"{_tips_info} {part_deps_info.get('build_config_file')}." else: _warning_info = "" if _warning_info != "": if args.target_path in third_deps_allow_list: print(f"[0/0] WARNING: {_warning_info}") else: raise Exception(_warning_info) return

四、总结

至此,我们可以知道,如果我们需要添加新的平台,我们需要将其仓库都添加到白名单中。这样Openharmony才能正常编译

五、参考链接

https://forums.openharmony.cn/forum.php?mod=viewthread&tid=1864&extra=

编辑
2025-01-20
工作知识
0

Openharmony4.0的版本在内核上不支持rk8602的regulator驱动,这就导致两路cpu大核无法正常上电,npu无法正常上电,因为rk860x给cpu和npu提供了真实的regulator。

一、设备树

在rk3588s上,我们可以通过设备树知道cpub0和cpub1以及npu的供电情况如下:

&rknpu { rknpu-supply = <&vdd_npu_s0>; mem-supply = <&vdd_npu_mem_s0>; }; &cpu_b0 { cpu-supply = <&vdd_cpu_big0_s0>; mem-supply = <&vdd_cpu_big0_mem_s0>; }; &cpu_b2 { cpu-supply = <&vdd_cpu_big1_s0>; mem-supply = <&vdd_cpu_big1_mem_s0>; };

对于此,我们可以知道npu和cpu上都需要两路实际的电源供应,对于驱动,则需要如下获取regulator的引用节点

rknpu_supply = devm_regulator_get(&pdev->dev, "rknpu"); cpu_supply = devm_regulator_get(&pdev->dev, "cpu"); mem_supply = devm_regulator_get(&pdev->dev, "mem");

然后再通过上电时机将此路电源打开

ret = regulator_enable(rknpu_supply); ret = regulator_enable(cpu_supply); ret = regulator_enable(mem_supply);

基于此,我们可以知道,对于rk3588s而言,这类regulartor是核心的上电要求,所以我们不能忽略,如下可以找到regulator的设备树定义

vdd_cpu_big0_s0: vdd_cpu_big0_mem_s0: rk8602@42 { compatible = "rockchip,rk8602"; reg = <0x42>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_cpu_big0_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <1050000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_cpu_big1_s0: vdd_cpu_big1_mem_s0: rk8603@43 { compatible = "rockchip,rk8603"; reg = <0x43>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_cpu_big1_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <1050000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_npu_s0: vdd_npu_mem_s0: rk8602@42 { compatible = "rockchip,rk8602"; reg = <0x42>; vin-supply = <&vcc5v0_sys>; regulator-compatible = "rk860x-reg"; regulator-name = "vdd_npu_s0"; regulator-min-microvolt = <550000>; regulator-max-microvolt = <950000>; regulator-ramp-delay = <2300>; rockchip,suspend-voltage-selector = <1>; regulator-boot-on; regulator-always-on; regulator-state-mem { regulator-off-in-suspend; }; };

二、驱动

根据上述可以知道,我们必须要使能这个regulator电,并且此电的设备树节点是rk8602,rk8603.故需要移植驱动

针对此,可以找到驱动文件drivers/regulator/rk860x-regulator.c,其相关的compatible如下

static const struct of_device_id rk860x_dt_ids[] = { { .compatible = "rockchip,rk8600", .data = (void *)RK860X_CHIP_ID_00 }, { .compatible = "rockchip,rk8601", .data = (void *)RK860X_CHIP_ID_01 }, { .compatible = "rockchip,rk8602", .data = (void *)RK860X_CHIP_ID_02 }, { .compatible = "rockchip,rk8603", .data = (void *)RK860X_CHIP_ID_03 }, { } };

这里可以知道,一份驱动对应了多个compatible,可以实现多个节点的不同配置。这样我们可以确定移植此驱动即可。

对于vendor/drivers/regulator/Makefile 添加如下

obj-$(CONFIG_REGULATOR_RK860X) += rk860x-regulator.o

对于vendor/drivers/regulator/Kconfig 添加如下

config REGULATOR_RK860X tristate "Rockchip RK860X Regulator" depends on I2C select REGMAP_I2C help This driver supports Rockchip RK860X Digitally Programmable Buck Regulator. The RK860X is a step-down switching voltage regulator that delivers a digitally programmable output from an input voltage supply of 2.5V to 5.5V. The output voltage is programmed through an I2C interface.

对于arch/arm64/configs/rockchip_linux_defconfig添加如下

CONFIG_REGULATOR_RK860X=y

至此,两路cpu大核和npu的电即可正常输出

编辑
2025-01-20
工作知识
0

我们适配的板子,没办法使用hdc命令,这导致我们调试很不方便,这里提供了修改方法,打开了内核的设备树配置,从而上层能够使用hdc,本文用作记录方法。

一、设备树

我们知道usb需要支持org,那么我们需要将其功能正常打开,这里typec对于usb3,所以如下修改:

diff --git a/rk3588/kernel/arch/arm64/boot/dts/rockchip/rk3588-tianqi-linux.dts b/rk3588/kernel/arch/arm64/boot/dts/rockchip/rk3588-tianqi-linux.dts index cbfdae3..c1c4403 100755 --- a/rk3588/kernel/arch/arm64/boot/dts/rockchip/rk3588-tianqi-linux.dts +++ b/rk3588/kernel/arch/arm64/boot/dts/rockchip/rk3588-tianqi-linux.dts @@ -61,20 +61,22 @@ }; &usbdrd3_0 { - status = "disable"; + status = "okay"; }; &usbdrd_dwc3_0 { dr_mode = "otg"; - status = "disable"; + status = "okay"; + usb-role-switch; }; &usbhost3_0 { - status = "disabled"; + status = "okay"; }; &usbhost_dwc3_0 { - status = "disabled"; + status = "okay"; };

这里可以很清楚的知道需要打开typec对应的usb即可。这里usb-role-switch对照代码看一下就明白了,没什么好讲的。

二、编译

根据板子的配置,我们可以跳到内核目录下,直接编译即可

cd out/kernel/src_tmp/linux-5.10 && ./make-ohos.sh TQ3588

三、烧录

这里直接烧录boot_linux分区即可,如果不想手动做上述操作,可以这个位置(10.3.3.110)拿一下内核

/home/develop/product/embedded/01-openharmony/01-version/2024-11-05-天启板子支持hdc/boot_linux.img

四、使用

这样,hdc能够正常的list

C:\>hdc list targets 9b01005932503033320045da20422900

hdc能够正常进入

C:\>hdc shell # ls / bin data etc lost+found storage tmp chip_prod dev init mnt sys updater chipset eng_chipset lib module_update sys_prod vendor config eng_system lib64 proc system