我们在适配众达的硬件的时候,发现Openharmony系统开机无法显示logo,遂排查,这里提供解决过程
logo无法显示的问题,是内核开机logo驱动可能出错,我们需要找出核心的代码,如下
drivers/gpu/drm/rockchip/rockchip_drm_logo.c
根据我们之前梳理的众达显示的文章《Openharmony之众达rk3568内核显示panel代码梳理》,我们梳理代码逻辑如下:
rockchip_drm_bind--->rockchip_drm_show_logo---->of_parse_display_resource--→find_sub_dev_by_bridge
根据上面代码的梳理,我们可以知道find_sub_dev_by_bridge是可能出现错误的地方,根据原始代码逻辑如下:
static struct rockchip_drm_sub_dev * find_sub_dev_by_bridge(struct drm_device *drm_dev, struct device_node *node) { struct device_node *np_encoder, *np_connector = NULL; struct rockchip_drm_sub_dev *sub_dev = NULL; struct device_node *port, *endpoint; np_encoder = of_graph_get_remote_port_parent(node); if (!np_encoder || !of_device_is_available(np_encoder)) goto err_put_encoder; port = of_graph_get_port_by_id(np_encoder, 1); if (!port) { dev_err(drm_dev->dev, "can't found port point!\n"); goto err_put_encoder; } for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if (!of_device_is_available(np_connector)) { of_node_put(np_connector); np_connector = NULL; continue; } else { break; } } if (!np_connector) { dev_err(drm_dev->dev, "can't found available connector node!\n"); goto err_put_port; } sub_dev = rockchip_drm_get_sub_dev(np_connector); if (!sub_dev) goto err_put_port; of_node_put(np_connector); err_put_port: of_node_put(port); err_put_encoder: of_node_put(np_encoder); return sub_dev; }
根据分析上面代码的关键,我们可以知道,默认情况下,作为hdmi设备,我们将其作为bridge时,我们只寻找他的下一个remote port,然后再通过rockchip_drm_get_sub_dev拿到sub_dev
而现有的代码两点不满足
根据《Openharmony之众达rk3568内核显示panel代码梳理》第一章的概念中解释的,现在众达的显示架构下,我们是hdmi--->rk628-→panel。所以单独一个for_each_child_of_node是找不到最终的设备,代码如下
for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if (!of_device_is_available(np_connector)) { of_node_put(np_connector); np_connector = NULL; continue; } else { break; } }
根据上述的代码,我们可以知道,它可以从port中编译endpoint,然后根据endpoint找到对应的节点np。在实际情况中,它找到的是rk628_hdmirx,这不符合我们的期望。
从rockchip_drm_get_sub_dev的分析我们知道, 我们需要在最后一个bridge的时候,register一个drm sub dev设备,这样才能通过rockchip_drm_get_sub_dev拿到这个设备。代码如下
sub_dev = rockchip_drm_get_sub_dev(np_connector);
根据2.1的分析,我们拿到的np_connector是rk628_hdmirx,但是很明显,他并不是连接panel的哪个connector,真实的应该是rk628_lvds,所以我们应该分析rk628_lvds.c驱动,代码如下
static int rk628_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct rk628_lvds *lvds = bridge_to_lvds(bridge); struct drm_connector *connector = &lvds->connector; struct drm_device *drm = bridge->dev; int ret; if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) return 0; ret = drm_connector_init(drm, connector, &rk628_lvds_connector_funcs, DRM_MODE_CONNECTOR_LVDS); if (ret) { dev_err(lvds->dev, "Failed to initialize connector with drm\n"); return ret; } drm_connector_helper_add(connector, &rk628_lvds_connector_helper_funcs); drm_connector_attach_encoder(connector, bridge->encoder); return 0; }
可以发现,这里没有注册sub dev,所以需要修改
关于上面的解析,我们需要修改两处代码
对于2.1的问题,我们需要借用递归的思路寻找下一个bridge,所以修改代码如下
again: for_each_child_of_node(port, endpoint) { np_connector = of_graph_get_remote_port_parent(endpoint); if (!np_connector) { dev_err(drm_dev->dev, "can't found connector node, please init!\n"); goto err_put_port; } if(of_device_is_available(np_connector)){ if (strcmp(np_connector->name, "panel") != 0) { port = of_graph_get_port_by_id(np_connector, 1); if(port == NULL){ break; } else { np_encoder = np_connector; goto again; } } else { np_connector = np_encoder; break; } }else{ of_node_put(np_connector); np_connector = NULL; continue; } }
此时我们找到的下一个bridge为rk628_hdmirx的时候,还是会继续往下寻找,知道找到最后一个名为panel的节点
根据3.1的递归,我们可以正确的找到rk628_lvds,所以需要在驱动中添加注册drm sub dev,如下
static int rk628_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct rk628_lvds *lvds = bridge_to_lvds(bridge); struct drm_connector *connector = &lvds->connector; struct drm_device *drm = bridge->dev; int ret; if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) return 0; ret = drm_connector_init(drm, connector, &rk628_lvds_connector_funcs, DRM_MODE_CONNECTOR_LVDS); if (ret) { dev_err(lvds->dev, "Failed to initialize connector with drm\n"); return ret; } drm_connector_helper_add(connector, &rk628_lvds_connector_helper_funcs); drm_connector_attach_encoder(connector, bridge->encoder); lvds->sub_dev.connector = &lvds->connector; lvds->sub_dev.of_node = lvds->dev->of_node; rockchip_drm_register_sub_dev(&lvds->sub_dev); return 0; }
根据分析代码,我们需要添加日志从而确定问题情况,如下是调试日志
printk("kylin: %s %d port=%pOF\n", __func__, __LINE__, port);
根据此,我们可以从启动日志找到如下信息
[ 4.490238] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/hdmirx [ 4.490288] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/hdmirx/ports/port@1 [ 4.490325] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/post-process [ 4.490367] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/post-process/ports/port@1 [ 4.490403] kylin: find_sub_dev_by_bridge 93 np_connector=/i2c@fe5d0000/rk628@50/lvds [ 4.490443] kylin: find_sub_dev_by_bridge 106 port=/i2c@fe5d0000/rk628@50/lvds/ports/port@1 [ 4.490474] kylin: find_sub_dev_by_bridge 93 np_connector=/panel
此时我们正确的找到了panel节点,
我们知道我们的屏幕是panel-simple驱动的,所以我们需要留意get_timings回调如下
static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, .get_timings = panel_simple_get_timings, };
这里面当timing的数量为1的时候,默认设置了perfered,如下
static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, struct drm_connector *connector) { struct drm_display_mode *mode; unsigned int i, num = 0; for (i = 0; i < panel->desc->num_timings; i++) { const struct display_timing *dt = &panel->desc->timings[i]; struct videomode vm; videomode_from_timing(dt, &vm); mode = drm_mode_create(connector->dev); if (!mode) { dev_err(panel->base.dev, "failed to add mode %ux%u\n", dt->hactive.typ, dt->vactive.typ); continue; } drm_display_mode_from_videomode(&vm, mode); mode->type |= DRM_MODE_TYPE_DRIVER; if (panel->desc->num_timings == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); num++; } return num; }
此时我们需要在fill bmp数据之前找到我们正确的mode,需要添加如下:
if (!found) { list_for_each_entry(mode, &connector->modes, head) { if (mode->type & DRM_MODE_TYPE_PREFERRED) { found = 1; break; } } }
其关键代码如下:
if (setup_initial_state(drm_dev, state, set)) { drm_framebuffer_put(set->fb); INIT_LIST_HEAD(&set->head); list_add_tail(&set->head, &mode_unset_list); continue; }
至此,开机logo即正常显示。
最近在开发Openharmony系统,因为Openharmony涉及的目录特别多,而且需要来回的切换,常用的cd和cd -已经不满足基本的开发环境要求了,于是借鉴了栈的思想,写了一个脚本,能够提高大家的开发效率
我们知道栈的结构是先进后出,也就意味着越新的元素我们就越经常使用。这个概念恰好契合了我们在cd目录的场景上。所以我们切换目录如果是栈的形式保存了目录结构,这样就非常方便我们开发了。基于此思想,我开始了此工具的开发
我们知道,linux有几个基本的命令叫做pushd和popd,这是以栈的思想打开文件目录。但是pushd和popd要经常配合dirs来运行,所以不是很方便,原因如下:
pushd和popd以及dir的常用命令如下
pushd /tmp popd dirs pushd +1
基于上述现状上,我没办法直接使用popd和pushd命令,所以我自己编写了一个较为易用的脚本,使用方法如下:
将脚本命名为p,放入/usr/local/bin/下,赋予执行权限,并在环境中设置如下:
alias p='source p'
p dir
例如:
# p /root/ [0]:~ [1]:~/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test [2]:
p
例如:
root@chroot:~# p root@chroot:~/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test#
p -h
例如
root@chroot:/sys# p -h [0]:/sys [1]:/tmp [2]:/root [3]:/root/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test
p 3
这里选择序号为3的目录栈进行推出
[0]:~/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test [1]:/sys [2]:/tmp
p -c
这样目录栈就没有其他文件夹了
#!/bin/bash dir=$1 BLK='\033[0;30m' # Black RED='\033[0;31m' # Red GRN='\033[0;32m' # Green BLU='\033[0;34m' # Blue CYA='\033[0;36m' # Cyan WHI='\033[0;37m' # White YEL='\033[0;33m' # Yellow PUR='\033[0;35m' # Purple NC='\033[0m' # No Color function fault_log() { echo -e "${RED}$1${NC}" } function info_log() { echo -e "${GRN}$1${NC}" } function dir_list() { array=() dir_name=$(dirs -l) dir_num=$(echo ${dir_name}| awk '{print NF}') i=0 for name in ${dir_name} do array[i]=$name let i=i+1 done output_message="" for i in ${!array[@]} do info_log "${RED}[${i}]${GRN}:${array[i]}" done } function help() { array=() dir_name=$(dirs) dir_num=$(echo ${dir_name}| awk '{print NF}') i=0 for name in ${dir_name} do array[i]=$name let i=i+1 done output_message="" #for i in ${!array[@]} for((i=0;i<3;i++)) do output_message="${output_message}${RED}[${i}]${GRN}:${array[i]} " done info_log "${output_message}" } function push_dir() { if [[ "$1" =~ "+" ]] then num=$(dirs -l | xargs | tr ' ' '\n' | wc -l) if [ ${num} == 1 ] then fault_log "pushd: 目录栈为空" return fi fi pushd $1 > /dev/null if [ $? -ne 0 ] then fault_log "pushd error" fi help } function pop_dir() { popd > /dev/null 2>&1 if [ $? == 1 ] then fault_log "popd: 目录栈为空" fi } case "${dir}" in "") pop_dir ;; "-h") dir_list ;; "-c") dirs -c ;; [0-9]) [ -d ${dir} ] && info_log "存在数字命名的文件夹" [ -d ${dir} ] && push_dir ${dir} [ ! -d ${dir} ] && push_dir "+${dir}" ;; *) [ ! -d ${dir} ] && info_log "没有这样的目录" [ -d ${dir} ] && push_dir ${dir} ;; esac
OH有两种类型的开发者,一个是应用开发者,一个是设备开发者,对于我们来说,我们既是应用开发者又是设备开发者
对于OH的应用开发者,目前逐渐推荐使用Stage模型开发程序。Stage的模型如下
根据文中意思:一个应用程序提供两种类型组件,1是UIAbility,2是ExtensionAbility。UI用作交互,Ex用作能力扩展。
这里ExAbility可以是OH默认提供的基本功能,也可以是自己封装的第三方库。
当然,OH的应用开发者默认要使用ArkTS来开发ArkUI,所以学习ArkTS应该是当前第一步骤
对于不同的设备,我们应该默认按照标准系统移植方式开发,一方面我们本身是Linux系统厂商,不会也没必要基于轻量(MCU)系统开发,和小型(嵌入式设备)系统开发。包括当前OH提供的api10,都是基于标准系统进行扩展的。这里可以大胆笃定OH默认的发展方向和市场前景在于Linux标准系统侧,与传统Linux桌面系统厂商竞争。
开发标准系统需要自行定义板级配置和为其编写HDF抽象层驱动,主要如文中介绍
标准驱动架构图如下
这里可以知道,标准系统长期应该仍基于Linux的LTS进行开发,OH做的是OSAL层抽象。相关具体的代码位置如下
所以我们的职责目前应该是在LTS的Linux版本,例如4.19和5.10上,搞定OH的OSAL层,这也就搞定了OH的底层
当然,OH的设备开发者默认需要在具体的硬件设备上支持,所以按照OH的SDK的编译构建流程来适配指定的开发板应该是第一步骤
根据OH的整体设计,可以发现不仅包括了应用层和内核层,还有框架层和系统服务层,这两层应该怎么办呢
首先,根据OH当前社区发展现状可以知道,OH目前大部分的代码仍是鸿蒙团队和鸿蒙扶持的相关团队在开发
其次,根据和鸿蒙沟通的情况,可以知道OH商务路线策略是扶持多个发行版厂商,而不是建立大一统的开源开放策略,目前鸿蒙内部团队的代码优于开发社区版本1个版本
并且,OH官网的适配样例绝大部分仍处于应用设计层次,并没有涉及系统的层次,参考《OpenHarmony样例展示视频合集》
根据上面两方面信息,可以知道,作为其他公司开发鸿蒙的框架层和系统服务层,有如下缺点
Openharmony的boot_linux.img 是标准的ext2格式,他需要通过syslinux来标准启动,但是鉴于有些uboot并没有默认打开syslinux,而使用默认的fit格式启动,所以本文介绍将原来的ext2的boot.img转换成fit格式的boot,从而使得系统加载更兼容
关于fit镜像的说明,可以参考如下文档FIT镜像说明
为了打包fit格式的boot镜像,我们需要编写its文件,本文给一个示例文件如下:
/dts-v1/; / { description = "U-Boot FIT source file for arm"; images { fdt { data = /incbin/("rk3568-evb1-ddr4-v10-linux.dtb"); type = "flat_dt"; arch = "arm64"; compression = "none"; load = <0xffffff00>; hash { algo = "sha256"; }; }; kernel { data = /incbin/("Image"); type = "kernel"; arch = "arm64"; os = "linux"; compression = "none"; entry = <0xffffff01>; load = <0xffffff01>; hash { algo = "sha256"; }; }; ramdisk { data = /incbin/("ramdisk.img"); type = "ramdisk"; arch = "arm64"; os = "linux"; compression = "none"; entry = <0x00000000>; load = <0x00000000>; hash { algo = "sha256"; }; }; resource { data = /incbin/("resource.img"); type = "multi"; arch = "arm64"; compression = "none"; hash { algo = "sha256"; }; }; }; configurations { default = "conf"; conf { rollback-index = <0x00>; fdt = "fdt"; kernel = "kernel"; multi = "resource"; ramdisk = "ramdisk"; signature { algo = "sha256,rsa2048"; padding = "pss"; key-name-hint = "dev"; sign-images = "fdt", "kernel", "multi", "ramdisk"; }; }; }; };
根据上述的its的描述,我们需要如下文件
# "rk3568-evb1-ddr4-v10-linux.dtb" # "Image" # "ramdisk.img" # "resource.img"
对于openharmony,我们需要找到的dtb的位置在如下
out/kernel/OBJ/linux-5.10/arch/arm64/boot/dts/rockchip/xxxx.dtb
此时我们复制文件即可
cp out/kernel/OBJ/linux-5.10/arch/arm64/boot/dts/rockchip/xxxx.dtb fit/
对于openharmony,我们需要找到的image的位置如下
out/kernel/OBJ/linux-5.10/arch/arm64/boot/Image
此时我们复制文件即可
cp out/kernel/OBJ/linux-5.10/arch/arm64/boot/Image fit/
对于openharmony,我们需要找到ramdisk的位置如下
out/rk3568/packages/phone/images/ramdisk.img
此时我们复制文件即可
cp out/rk3568/packages/phone/images/ramdisk.img fit/
对于openharmony,我们需要找到resource的位置如下
out/kernel/OBJ/linux-5.10/resource.img
此时我们复制文件即可
cp out/kernel/OBJ/linux-5.10/resource.img fit/
根据上述操作,我们可以开始通过mkimage制作镜像,先安装mkimage命令
apt install u-boot-tools
此时系统具备mkimage二进制,开始制作命令如下
mkimage -f boot.its -E -p 0x800 boot.img
制作时的日志信息如下:
FIT description: U-Boot FIT source file for arm Created: Fri May 24 03:34:16 2024 Image 0 (fdt) Description: unavailable Created: Fri May 24 03:34:16 2024 Type: Flat Device Tree Compression: uncompressed Data Size: 143744 Bytes = 140.38 KiB = 0.14 MiB Architecture: AArch64 Load Address: 0xffffff00 Hash algo: sha256 Hash value: 9df414ede45c3a2417bbcc4b8de9510aba9e2600f5a160a6a3b721c761400e31 Image 1 (kernel) Description: unavailable Created: Fri May 24 03:34:16 2024 Type: Kernel Image Compression: uncompressed Data Size: 24385552 Bytes = 23814.02 KiB = 23.26 MiB Architecture: AArch64 OS: Linux Load Address: 0xffffff01 Entry Point: 0xffffff01 Hash algo: sha256 Hash value: 4f3ad02eb1c101976b8b3dea5bce379efb0fd00a61a646b336c725d388dd4bea Image 2 (ramdisk) Description: unavailable Created: Fri May 24 03:34:16 2024 Type: RAMDisk Image Compression: uncompressed Data Size: 2509910 Bytes = 2451.08 KiB = 2.39 MiB Architecture: AArch64 OS: Linux Load Address: 0x00000000 Entry Point: 0x00000000 Hash algo: sha256 Hash value: 027f5c7aa5ca3a2dde9d0bb744d3a508321308c1f47cb34b4053317e39fe6ecc Image 3 (resource) Description: unavailable Created: Fri May 24 03:34:16 2024 Type: Multi-File Image Compression: uncompressed Data Size: 12588544 Bytes = 12293.50 KiB = 12.01 MiB Hash algo: sha256 Hash value: bcd3ccc6ac2747de83f27d320bd3c3aed18762bb5f3045fa84ff926345e2a125 Default Configuration: 'conf' Configuration 0 (conf) Description: unavailable Kernel: kernel Init Ramdisk: ramdisk FDT: fdt
至此,我们可以获得boot.img用于fit启动
我们在调试openharmony内核的时候,通常来看编译速度比正常内核都慢,主要是btf配置的影响,这里记录此问题,在我们调试的时候,如果不需要btf,可以关掉CONFIG_DEBUG_INFO_BTF配置,这样内核编译速度快点
BTF可能不熟悉的比较陌生,但是说起BPF就不陌生了,其实BTF是BPF的type format而已,而BPF一个工具,用于网络数据包过滤、性能监控、安全性应用等。BTF 格式的调试信息使得 BPF 程序可以轻松地获取内核数据结构的布局和类型信息,从而使 BPF 程序能够更深入地访问和操作内核数据
这个配置就是为内核添加btf信息,从而使得应用可以调用bpf程序来观测内核
pahole是一个工具,在内核编译的时候会使用pahole来打包btf。真正使得内核编译很慢的原因是pahole工具
禁用CONFIG_DEBUG_INFO_BTF可以加快内核编译速度