自OpenHarmony 问世后受到了社会各界的广泛关注,OpenHarmony 的生态系统在如火如荼的发展。
酷派作为一家积极拥抱变化的公司,经过一段时间的探索与实践,成功实现将OpenHarmony 系统接入到展讯平台上,我们相信这是一个重要的里程碑,标志着我们在推动OpenHarmony 的发展环节取得了重要进展。
本文基于的软硬件信息如下:
1.展讯 T606 Android 13(Kernel5.4)2.OpenHarmony v3.2.2 Release
本文受限于篇幅,将着重介绍GPU Mesa 开源方案在展讯平台上的适配。
官方图形子系统的架构如下图:
GPU 的适配主要关注框架层的Render Service (渲染服务),代码仓位于: //foundation/graphic/graphic_2d/。
在介绍详细的适配过程之前,我们先看下整体开发适配的架构图,如下:
我们在图形栈上的Mesa 适配工作主要包括用户层的Mesa 3D 以及Kernel 层的GPU 驱动 (panfrost)这两个方面。
此外需要注意的是,适配之前需要先将OpenHarmony 图形系统的gpu 配置项使能,如下:
"subsystem": "graphic",
"components": [
{
"component": "graphic_standard",
"features": [
"graphic_standard_feature_ace_enable_gpu = true",
"graphic_standard_feature_rs_enable_eglimage = true"
]
}
]
T606 采用Mali-G57 GPU,OpenHarmony v3.2.2 的三方mesa3d 不支持,需要从上游版本移植,Mesa 22.2 开始支持。
一. 方案选择
我们认为有三个方案可选:
方案一:基于原有版本,参考Mesa上游合入Mali-G57 支持;
方案二:自行升级Mesa 版本至少到22.2,并添加OpenHarmony 支持;
方案三:直接使用4.0 的Mesa3d 仓,2023.8.30已经升级到22.4;
考虑到方案一相对方案二可能有较多的工作量,我们选择了方案二。不过,在经历方案二的一些波折后,我们发现官方已经升级了mesa3d,所以索性我们就直接切到方案三上,其实方案三的问题也不少。
二. 编译Mesa
我们的编译主机是Ubuntu 18.04, meson 版本是0.61.5。
首先我们希望Mesa 能跟随系统一起编译。可是,我们发现ohos/BUILD.gn 有如下部分:
action("mesa3d_build") {
inputs = deps_inputs
script = "build_mesa3d.py"
有个输入依赖deps_inputs,是在ohos/dependency_inputs.gni 里定义的:
21 declare_args() {
22 deps_inputs = [
23 "../CODEOWNERS",
24 "../OAT.xml",
25 "../VERSION",
26 "../meson.build",
27 "../meson_options.txt",
...
6976 "../subprojects/libelf.wrap",
6977 "../subprojects/perfetto.wrap",
6978 "../subprojects/zlib.wrap",
6979 ]
6980 }
好家伙,接近7000行,看上去是包含所有的源码文件。那升级Mesa 版本肯定有一堆文件变动,难道我们要手动编辑这个gni文件?或是做个脚本统计?没必要。我们放弃这种编译方式,直接使用build_ohos.py,并把生成的库文件按ohos_prebuilt_shared_library
打包到镜像里。
为了解耦system 和vendor,我们把所有的库文件都放到vendor 侧:
run_build_cmd += '-Dplatforms=ohos -Degl-native-platform=ohos -Ddri-drivers= -Dgallium-drivers=panfrost \
- -Dvulkan-drivers= -Dgbm=enabled -Degl=enabled -Dcpp_rtti=false -Dglx=disabled -Dtools=panfrost -Ddri-search-path=/system/lib '
+ -Dvulkan-drivers= -Dgbm=enabled -Degl=enabled -Dgles1=enabled -Dgles2=enabled -Dcpp_rtti=false -Dglx=disabled -Dtools= -Ddri-search-path=/vendor/lib64/chipsetsdk '
同时,gn 打包的安装路径要保持一致否则会找不到库:
install_images = [ chipset_base_dir ]
relative_install_dir = "chipsetsdk"
另外,编译过程生成的临时目录build-ohos,里面不仅仅存放着obj,还有一些重要文件,这是后话,先卖个关子,这正印证了没有实践就没有发言权。
编译过程会遇到一些错误,有的是本地环境导致,比如:
../src/util/u_endian.h:97:3: error: "UTIL_ARCH_LITTLE_ENDIAN and/or UTIL_ARCH_BIG_ENDIAN were unset."
# error "UTIL_ARCH_LITTLE_ENDIAN and/or UTIL_ARCH_BIG_ENDIAN were unset."
可以参考defined(ANDROID)
分支添加大小端定义即可:
--- a/src/util/u_endian.h
+++ b/src/util/u_endian.h
@@ -89,6 +89,10 @@
#endif
+#define UTIL_ARCH_LITTLE_ENDIAN 1
+#define UTIL_ARCH_BIG_ENDIAN 0
编译成功后会生成如下so:
build-ohos/install/lib/libEGL.so.1.0.0
build-ohos/install/lib/libgbm.so.1.0.0
build-ohos/install/lib/libGLESv2.so.2.0.0
build-ohos/install/lib/libGLESv1_CM.so.1.1.0
build-ohos/install/lib/libglapi.so.0.0.0
build-ohos/src/gallium/targets/dri/libgallium_dri.so
build-ohos/install/lib/dri/panfrost_dri.so
ARM 官方本身提供了kbase 驱动,但由于其闭源,且采用私有so 进行交互的方式,所以无法应用到OpenHarmony 上。
在展讯平台上,我们选择了Panfrost 这一ARM Mali 系列的GPU 开源驱动,panfrost 主要用于Midgard 和Bifrost 架构的GPU。与kbase 不同,panfrost 接入了Linux drm 框架,从原生层面提供了Linux GPU 驱动体验。
由于我们本次适配的展讯平台是基于Kernel 5.4,在5.4 版本上自带的panfrost 还没有支持Mali-G57,所以需要从上游移植panfrost。
关于panfrost 的移植过程可以分为三部分:
1.移植对Mali-G57 平台的支持,从上游backport patch set drm/panfrost: Valhall (JM) support。2.移植上游支持Mali-G57 之前,对其他平台提供的支持。避免丢失某些 Mali-G57 依赖的其他平台的特性。比如 bifrost 平台,以及其他一些 quirk。3.移植对Mali-G57 平台的漏洞修复。
Panfrost 无法解析基于Kbase 的dts,需要手动修改gpu-supply,interrupt-names 等项目。
Dts 中,除了基础的 compatible,寄存器,电源,中断,Sprd 还自定义了一些项目,比如动态调频的控制器和频率,GPU 状态寄存器,时钟等。为了让 GPU 正常工作,需要正确地设置时钟,并对一些必要的寄存器初始化。
在驱动层面,Sprd 在 kbase 驱动的基础上,封装了三组基于平台的接口:
1.平台初始化和退出2.power manager callback。这些接口注册后,kbase 会在合适的时候调用,比如 suspend/resume。3.动态调频接口,通过嵌入到 kbase 调频流程中发挥作用。
这部分是 sprd 针对 kbase 驱动单独添加的内容。如需要相关功能,需要移植到 panfrost 中。这里介绍一种能够启动 GPU 的最小化方案:移植平台初始化代码。
平台初始化代码主要负责频率获取,时钟使能。频率和时钟的信息都定义在 dts 中,最终解析结果如下:
gpu_dvfs_ctx.clk_gpu_i <&aonapb_gate CLK_GPU_EB>
gpu_dvfs_ctx.clk_gpu_core_eb <&g1_pll CLK_RPLL_26M>
gpu_dvfs_ctx.clk_gpu_mem_eb <&g5l_pll CLK_TGPLL_76M8>
gpu_dvfs_ctx.clk_gpu_sys_eb <&g5l_pll CLK_TGPLL_153M6>
gpu_dvfs_ctx.gpu_clk_src [
<&g5l_pll CLK_TGPLL_384M>
<&g5l_pll CLK_TGPLL_512M>
<&g5r_pll CLK_GPLL>
<&g5r_pll CLK_GPLL_850M> // clk_set_rate(..., 650000_000)
]
gpu_dvfs_ctx.freq_list = [
384000
512000
614400
650000
]
将平台初始化代码移植到panfrost ,基本可以使 GPU 正常工作。但是因为缺失了电源和频率模块,相关功能都不可用。我们尝试通过 /sys/class/devfreq
获取 GPU 频率,但只获取到一个错误的值(26M),跟踪相关代码发现 panfrost 将时钟绑定到了<&aonapb_gate CLK_GPU_EB>
上,但真正用于GPU 工作的是后四个clock,即 384M,512M,614M,650M。
在调试Mesa 前需用三方modetest 测试图形显示是否正常。
Step1: 测试前需要先停掉render_service
service_control stop render_service
Step2: 使用atomic 方式来测试sprd 的图形驱动
# 指定 mode: connector_id=92, crtc_id=83, mode=720x1520
# 指定 plane: plane_id=31, crtc_id=83, weight*height=720*1520
modetest -a -M sprd -s 92@83:720x1520 -P 31@83:720x1520
正常的出图效果如下:
下面来看我们遇到的问题,在Mesa 适配完刷机后我们就发现系统在反复重启,内核能看到如下错误:
[ 32.857012]c7 [ T1615] CPU: 7 PID: 1615 Comm: render_service Tainted: G S
主要是render_service 配置了critical 字段:
// graphic.cfg
"services" : [{
"name" : "render_service",
"path" : ["/system/bin/render_service"],
"critical" : [1, 5, 60],
参考官方对critical 字段的说明:
其实就是服务重启次数过多了就重启系统,为了便于调试,我们可以设置为0 先不使能。
抓取到的render_service crash 栈如下:
能看出来渲染主线程Init
挂掉了,在添加日志后发现死在opengl_wrapper 的glGetString()
,这个接口按理说是openGL 提供的标准接口,所以对于堆栈挂在这个接口上,我们一开始并没有太多的头绪。
随着通过不断加日志debug 梳理上下文的调用关系,最终定位是由于一个比较隐蔽的平台差异性问题导致,这里受限于篇幅就不详细展开了。
如下图,正常运行时的render_service crashCnt 是0,status 是running:
我们还未来得及欣喜,却又遇到如下crash:
死在Mesa 的libEGL 上,没有什么能阻挡我们前进的步伐,我们再次跟踪Mesa,也修复了此问题。
调试过程中hilog 可以使用-D
过滤日志:
-D , --domain= Show specific domain/domains logs with format: domain1,domain2,doman3 Don’t show specific domain/domains logs with format: ^domain1,domain2,doman3 Max domain count is 5.
图形domain ID 定义如下:
29 // The "0xD001400" is the domain ID for graphic module that alloted by the OS.
30 constexpr OHOS::HiviewDFX::HiLogLabel LABEL_RS = { LOG_CORE, 0xD001400, "OHOS::RS" };
31 constexpr OHOS::HiviewDFX::HiLogLabel LABEL_ROSEN = { LOG_CORE, 0xD001400, "OHOS::ROSEN" };
使用如下命令启动渲染服务时只抓图形log并存于本地:
hdc shell "hilog -D 0xD001400 & service_control start render_service" > hilog-rs.txt
另外,可以用一个gl_test 来验证,正常情况如下图,屏幕左上角会出现一个三角形:
性能分析
我们在移植结束后遇到了这样的一个问题,现象是:拖动桌面图标时界面卡顿。
熟悉Android 性能问题分析的小伙伴都知道,Android 提供了抓取trace 的atrace 工具,那么在OpenHarmony 平台上是否也有现成的抓取trace 功能呢?
其实OpenHarmony 提供了名为bytrace 的工具,借助这个工具我们可以抓取一份Ftrace ,命令如下:
echo > /sys/kernel/debug/tracing/trace
echo 90096 > /sys/kernel/debug/tracing/saved_cmdlines_size
bytrace -t 20 -b 90096 --overwrite ohos zimage zmedia zcamera zaudio ability distributeddatamgr graphic freq irq mdfs workq mmc idle notification sync pagecache ace app > /data/Launcher.ftrace
echo > /sys/kernel/debug/tracing/trace
Ftrace 的抓取步骤和Android 平台是一样的,导出的Ftrace 文件用前端工具打开。
如下图所示:
很快,我们在Launcher 所在进程区域发现了异常,那就是当Launcher 界面卡顿时,应用的绘制和渲染侧都是空白的(红圈处),所以自然而然也就没有帧送到屏幕上。
那么对于应用侧没有出帧的情况,根据我们以往的经验,通常是由于主线程被某个事务阻塞,导致没有办法响应Vsync 的callback。
结合Hilog 观测到如下片段持续高频的输出:
[js_progress.cpp(SetCircularStyle)-(1)] circular Style error. now use default scaleWidth
此时可以认定是由于Launcher 在调用框架接口SetCircularStyle
时陷入了循环调用中,另一方面也说明了应用自身的容错能力存在问题。
接下来的工作主要是跟着代码搜寻SetCircularStyle
这一函数调用场景及排查报错的原因,最终我们解决了该问题,受限于篇幅不详细展开。
在移植适配Mesa 到展讯平台的过程中,除了文中提到的问题,我们还遇到了许多棘手的问题,有图形领域相关的,也有不少平台兼容性的问题。
在解决这些问题的过程中,我们深感图形领域的错综复杂。未来,我们将不断地补齐短板,继续探索深耕图形领域,为OpenHarmony 的生态发展贡献我们的绵薄之力。
为了能让大家更好的学习鸿蒙 (Harmony OS) 开发技术,这边特意整理了《鸿蒙 (Harmony OS)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
HarmonyOS 概念:https://qr21.cn/FV7h05
如何快速入门:https://qr21.cn/FV7h05
开发基础知识:https://qr21.cn/FV7h05
基于ArkTS 开发:https://qr21.cn/FV7h05