openharmony有多种音频调试方案,这里介绍和解读一下openharmony的音频方案。
根据openharmony在社区分享的文章《audio适配方案》,我们可以知道对于openharmony系统适配音频有四种方案,分别如下:
ADM的全称是audio driver mode,这是openharmony系统自身的一套音频框架,它仿照alsa的基本概念做了改造和裁剪,其基本思想还是基于alsa。所以如下
对比于alsa,如下
所以我们可以知道
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
根据上面的分析,我们可以知道ADM是改造的ALSA,所以对于ADM的开发,我们需要遵循ADM的框架,编写HDF driver,实现Codec驱动即可
对于alsa而言,驱动可以完全使用标准linux的ASoC框架,上层则配置使能alsalib即可,对于openharmony,根据其系统的设计,我们还需要适配openharmony的上层支撑,这里是supportlib的实现。
所以关键点有三个,如下:
CONFIG_SOUND=y CONFIG_SND=y # CONFIG_DRIVERS_HDF_AUDIO is not set # CONFIG_DRIVERS_HDF_AUDIO_RK3568 is not set
通过配置项drivers_peripheral_audio_feature_alsa_lib
来控制代码的执行逻辑,如果是drivers_peripheral_audio_feature_alsa_lib=true
,则默认使用alsalib的libasound库
具体代码路径如下:
drivers/peripheral/audio/hdi_service
根据上面的方法,已经可以确保系统的音频走alsalib的libasound的了,但是openharmony系统需要能够使用音频,需要对音频的api做一下重实现,这里已经完成了,就是supportlibs,代码仓库位置如下:
drivers/peripheral/audio/supportlibs
这里文件情况如下:
adm_adapter alsa_adapter interfaces
可以知道,对于ADM,默认走adm_adapter,而对于alsa我们走的是alsa_adapter,其中interfaces对上层的抽象,提供了上层需要的api接口
对于上层需要的接口,简单介绍如下:
至此,openharmony走alsa的理论路径如下(自下向上):
ASoC---->libasound---->supportlibs(alsa_adapter)--→openharmony api
如果不太熟悉android的同学,我们在提到hidl,会不太清楚,再者,在《audio适配方案》中, 里面描述的是HDIL,这个东西没有任何的解释,所以我认为这是华为的人编写文档的时候的一个书写错误,习以为常就好啦。
这里提到的hidl是借鉴了安卓的思路,安卓在对于应用层之间,使用aidl,在对于设备抽象层之间,使用hidl。对于安卓,其实现方式如下(自上到下):
SystemApp--->Android Framework ---> HIDL Binder--->HIDL Service Manager----> HAL
所以openharmony也在借鉴安卓的思路,实现自己的hidl,故我们可以看到如下言论
对于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努力的目标。
此方案的意图在于OEM自行根据HDI的接口自己实现,哪有一个操作系统说我提供上层api,下层自己实现的说法,故不现实。
根据此说法,应该实现路径如下(自低到上):
alsa/自己实现audio框架(openharmony不干)--->自己实现vendor hal(openharmony不干)--->vendor hal对接hdi api(openharmony上层api)
如果底层使用alsa,则第三章的方案中的alsalib和supportlibs需要自行实现成vendor hal,所以这属于一个异想天开的方案,当前阶段不必理会。
我们基于RK3568在众达板卡上开发了Openharmony,相关仓库通过repo管理,故代码可以直接repo拉取和编译,下文说明拉取方法和编译步骤
Openharmony的代码仓库地址在:https://gitlab2.kylin.com/sh-product-Embedded/openharmony
,这些仓库包含我们基于Openharmony的修改内容
拉取命令如下
repo init -u https://gitlab2.kylin.com/sh-product-Embedded/openharmony/manifest.git -b OpenHarmony-4.0-Release -g ohos:standard --no-repo-verify
拉取时,如果出现如下错误,则可以两个方向排除
服务器需要认证,建议找服务器的owner私下聊天沟通一下,认证网络后,才能拉到gitee/openharmony的仓库代码
这种情况下是应该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如下:
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/
在编译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"是用于检测是否存在子系统,在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"
这里涉及子系统和组件的白名单,如果需要编译相关的组件,则需要添加,我这里添加如下:
@@ -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,所以如果不匹配就会出现
"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)
"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)
"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)
"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)
"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=
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的电即可正常输出
我们适配的板子,没办法使用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