最近在调试openharmony的显示问题上面受阻,原因是众达的硬件上是hdmi接的rk628转接,然后再接的lvds显示器,而openharmony实现的显示panel比较简单,直接dsi panel。所以针对于此,需要将众达的显示panel相关驱动好好梳理出来
针对众达设备的场景,我们知道其重点在于drm panel和drm bridge。这里解释一下:
drm:内核drm显示框架,用于抽象
panel:屏的意思,在drm内抽象用于表示这是一个屏幕,例如hdmi,lvds,edp等
bridge:桥的意思,很多总线概念(pcie)都有,桥接显示器的意思,这里hdmi转lvds接的屏,肯定有bridge的概念
对于众达rk3568而言,我这里图示如下:
对于我们而言,我们需要初始化hdmi驱动,并且把hdmi作为bridge使能,然后找到下一个bridge为rk628,然后在rk628上找到真正的panel。
对于实现此问题的场景,此函数应该重点讲解,它是内核提供的标准api,用于找到下一个panel或bridge。
函数的定义如下:
int drm_of_find_panel_or_bridge(struct device *dev, int port, int endpoint, struct drm_panel **panel, struct drm_bridge **bridge);
这里需要解释的是:
驱动通过此函数能够从设备树获得到下一个bridge或panel的节点,从而触发component_ops的bind回调
为了把事情说清楚,这里有必要简单介绍一下component,详细的介绍建议阅读代码学习,网上知识不多,我也是个人理解
component是组件框架,通常存在在比较大的内核框架中实现,component框架分master和component,两者没有明显区别,只是master会按照match来遍历所有组件,如果都注册了就触发bind。而组件是通过返回-517也就是-DPROBE_DEFER来延迟探测。
这里我们把显示框架"display-subsystem"作为了master,而其他的panel和bridge都以component来注册。示例如下:
ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
对于设备树,我们针对drm_of_find_panel_or_bridge的函数,需要按照实际硬件连接的方式准备port和endpoint,我整理如下:
hdmi: port@1 { reg = <1>; hdmi_out_hdmirx: endpoint { remote-endpoint = <&hdmirx_in_hdmi>; status = "okay"; }; }; rk628_hdmirx: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; hdmirx_in_hdmi: endpoint { remote-endpoint = <&hdmi_out_hdmirx>; }; }; port@1 { reg = <1>; hdmirx_out_post_process: endpoint { remote-endpoint = <&post_process_in_hdmirx>; }; }; }; 抽象rk628_post_process ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; post_process_in_hdmirx: endpoint { remote-endpoint = <&hdmirx_out_post_process>; }; }; port@1 { reg = <1>; post_process_out_lvds: endpoint { remote-endpoint = <&lvds_in_post_process>; }; }; }; rk628_lvds: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; lvds_in_post_process: endpoint { remote-endpoint = <&post_process_out_lvds>; }; }; port@1 { reg = <1>; lvds_out_panel: endpoint { remote-endpoint = <&panel_in_lvds>; }; }; }; simple_panel: port { panel_in_lvds: endpoint { remote-endpoint = <&lvds_out_panel>; }; };
上面代码我梳理一下如下:
hdmi---->hdmirx_in_hdmi---->post_process_in_hdmirx---->lvds_in_post_process---->panel_in_lvds
根据上面的代码梳理,我们可以知道了其调用流程,这里总结一下如下:
component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
添加了组件,等待调用bind
dw_hdmi_bind中drm_bridge_attach到drm中,并通过of_drm_find_bridge找下一个bridge,这里下一个设备是hdmirx_in_hdmi
drm_bridge_add添加bridge设备,attach执行回调
在attach中,调用
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &hdmirx→bridge)
然后drm_bridge_attach上
这里1和-1是指的从port1上遍历找endpoint,这里是post_process_in_hdmirx
同样的
drm_bridge_add添加bridge设备,attach执行回调
在attach中,调用
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &pp→bridge)
然后drm_bridge_attach上
这里1和-1是指的从port1上遍历找endpoint,这里是lvds_in_post_process
这里直接在probe函数中寻找panel设备
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &lvds->panel, NULL)
然后调用drm_bridge_add添加即可。
因为这个panel_simple驱动就是drm panel的驱动,所以它负责初始化panel drm_panel_init,添加panel drm_panel_add
根据Openharmony的display的驱动来看,通过ili9881_st_5p5.c驱动直接初始化了dsi的硬件,然后通过hdf_drm_panel.c来进行drm_panel_init和drm_panel_add
可以知道,Openharmony没有涉及bridge的概念,所以根据众达的显示panel流程,我们理想中的Openharmony的hdf实现应该是如下:
我们可以跳过1,2,3,4四个步骤,我们只实现步骤5,让驱动在component的bind的时候能够将drm bridge or panel路径走通。此方法可以参考文章《Openharmony-rk3568 移植panel_simple_common》
根据上面的分析解剖,我们知道,如果想要完全按照hdf的思想来设计hdf驱动,我们针对rk628需要写很多个驱动才能实现hdf的基本功能。
我们再基于一个原理:
drm是显示框架的抽象,它有crtc,plane,connector,encoder,frambuffer,panel和bridge。通常情况下,我们通过libdrm下发调用,不会涉及到panel和bridge,它与crtc是割裂开的。所以我们基于drm的panel和bridge也不是一定需要用hdf实现,可以借用linux的实现方式。
而crtc及其他,根据代码追踪,hdf_disp并没有去实现drm的一整套功能。
所以hdf_disp的驱动实际上是不完整的,它只实现了panel,bridge甚至都没有实现,所以我的建议是display驱动可以暂时使用linux标准。
关于hdf_disp调用drm的参考文章《Openharmony-RK3588-drm_plane设置》
针对上面的文章《Openharmony-RK3588-drm_plane设置》分析可以知道,hdf的display并不关键,所以可以完全使用linux的实现,方法如下:
基于drm的显示我们需要如下操作
对于已经编译的内核,我们修改arch/arm64/configs/rockchip_linux_defconfig
文件,对于未编译的内核,我们修改kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig
我们在最底下添加如下:
# CONFIG_DRIVERS_HDF_DISP is not set CONFIG_DRM_PANEL_SIMPLE=y
对于设备树,我们参考《Openharmony RK3568图层设置》修改,添加如下:
&vp0 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER0 | 1 << ROCKCHIP_VOP2_ESMART0 | 1 << ROCKCHIP_VOP2_SMART0)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART0>; cursor-win-id = <ROCKCHIP_VOP2_CLUSTER0>; };
至此,相关修改全部完成。编译内核即可验证,这样情况下基于openharmony的内核的众达rk3568设备已经可以正常显示了。
在基于众达的rk3568板子上,使用非hdf的方式点亮了板卡之后,这里再提供一种办法,可以使用hdf的方式点亮众达rk3568的显示
移植panel_simple_common的hdf驱动可以参考文档《Openharmony-rk3568 移植panel_simple_common》
声明plane的方式可以参考文档《Openharmony RK3568图层设置》
我们在做完上述的操作之后,根据panel代码逻辑的分析,应该是可以成功的,关于分析panel代码的参考文档《Openharmony之众达rk3568内核显示panel代码梳理》
但是发现内核一直死循环,无法继续往下走,日志如下:
[ 23.289028] rockchip-vop2 fe040000.vop: [drm:vop2_bind] vp0 assign plane mask: 0x15, primary plane phy id: 2 [ 23.289095] rockchip-vop2 fe040000.vop: [drm:vop2_bind] vp1 assign plane mask: 0x0, primary plane phy id: -1 [ 23.289149] rockchip-vop2 fe040000.vop: [drm:vop2_bind] vp2 assign plane mask: 0x0, primary plane phy id: -1 [ 23.289545] rockchip-vop2 fe040000.vop: [drm:vop2_create_crtc] Cluster0-win0 as cursor plane for vp0 [ 23.289962] [drm] failed to init overlay plane Cluster0-win1 [ 23.290154] [drm] failed to init overlay plane Cluster1-win1 [ 23.290448] rockchip-drm display-subsystem: bound fe040000.vop (ops vop2_component_ops) [ 23.291207] dwhdmi-rockchip fe0a0000.hdmi: Detected HDMI TX controller v2.11a with HDCP (DWC HDMI 2.0 TX PHY) [ 23.294038] dwhdmi-rockchip fe0a0000.hdmi: registered DesignWare HDMI I2C bus driver [ 23.295241] [I/HDF_AUDIO_HDMI] [HdmiCodecProbe][line:33]: entry [ 23.299447] rk628-hdmirx rk628-hdmirx: failed to attach bridge ret=-517 [ 23.299473] Failed to attach bridge with dw-hdmi ret=-517
这里可以看到,dw-hdmi的bridge初始化失败了,也就是component组件的bind失败了。而且返回的是-EPROBE_DEFER。根据component往上反馈来追溯代码,这里代码追溯到如下:
static int rockchip_drm_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct component_match *match = NULL; int ret; ret = rockchip_drm_platform_of_probe(dev); if (ret) return ret; match = rockchip_drm_match_add(dev); if (IS_ERR(match)) return PTR_ERR(match); ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); if (ret < 0) { rockchip_drm_match_remove(dev); return ret; } ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (ret) return ret; return 0; }
从上述代码可以知道, component_master_add_with_match会接收component的返回值,这里ret应该返回-EPROBE_DEFER。而rockchip_drm_platform_probe接收到-EPROBE_DEFER后会延迟探测,这就导致rockchip_drm_platform_probe会被重入。
另一个方面我们知道,linux的device driver的probe是在一个线程中完成的,如果rockchip display subsystem的驱动无法正常的probe,那它就阻塞了其他驱动的probe。
根据上述代码的分析,基本问题也就定位到了,那就是rockchip drm platform的驱动导致内核进入死循环了,而进入死循环的原因是hdf编写的panel_simple_common驱动没有在它之前加载。
根据上述的推理,我们有如下的方式解决问题:
下面逐一分析
我们根据书籍《Openharmony架构、内核、驱动及应用开发全栈》的第七章第五节的内容可以知道如下:
hdf最开始是通过调用DeviceManagerInit来启动hdf框架,DeviceManagerInit的代码如下:
static int __init DeviceManagerInit(void) { int ret; ret = DeviceManagerStart(); if (ret < 0) { HDF_LOGE("%s start failed %d", __func__, ret); } else { HDF_LOGD("%s start success", __func__); } return ret; } late_initcall(DeviceManagerInit);
根据上述代码我们可以知道,hdf肯定是late_initcall,而rk的drm platform驱动是module_init,如下:
module_init(rockchip_drm_init); module_exit(rockchip_drm_fini);
有过内核开发的基础都清楚,此时hdf肯定加载在module_init之后。
再根据《Openharmony架构、内核、驱动及应用开发全栈》中提到的加载驱动流程如下:
我们可以知道在DeviceManagerInit之后,hdf还有一段分发流程,我们根据hcs的描述如下:
display :: host { hostName = "display_host"; device_hdf_drm_panel :: device { device0 :: deviceNode { policy = 0; priority = 197; preload = 0; moduleName = "HDF_DRM_PANEL_SIMPLE"; } } device_lcd :: device { device3 :: deviceNode { policy = 0; priority = 100; preload = 0; moduleName = "PANEL_SIMPLE_COMMON"; }
可以知道display先从host初始化"HDF_DRM_PANEL_SIMPLE",然后在从devicenode初始化"PANEL_SIMPLE_COMMON"。而这块的顺序我们不是很好控制了。
如果我们修改整个hdf的初始化流程,可能对hdf框架带来不好的影响,一方面是hdf本身不成熟,另一方面我们对hdf理解也不深刻。提前hdf是否带来问题心里没办法拍板。
这里我们需要明确的是,rockchip的drm platform驱动会绑定多个vp,在dayu200上,除了dsi还有hdmi设备,即使hdf编写的dsi的panel初始化比较晚,但是hdmi这块完全没有走hdf,所以是不会造成死循环的。
不会产生死循环,只要-EPROBE_DEFER能等得到驱动完成初始化,那就没问题。
我们对内核框架代码是十分清楚的,drm platform驱动的推迟只不过是导致系统黑屏时间长而已,那我们可以推迟moduleinit吗?
结论是不建议
根据上述问题,推迟rockchip drm驱动的方法也不可行,主要原因和4.1同理,怕对hdf带来不好的影响
既然上面两个方法都不建议处理,再鉴于咱们对内核比较熟悉,我们可以对rockchip drm platform driver进行简单改造。让其component match过程放在kthread中,如下
--- a/rockchip_drm_drv.c 2024-05-28 20:38:23.259111826 +0800 +++ b/rockchip_drm_drv.c 2024-06-04 17:18:50.965436190 +0800 @@ -35,6 +35,7 @@ #include "rockchip_drm_fbdev.h" #include "rockchip_drm_gem.h" #include "rockchip_drm_logo.h" +#include <linux/kthread.h> #include "../drm_crtc_internal.h" @@ -46,6 +47,7 @@ static bool is_support_iommu = true; static struct drm_driver rockchip_drm_driver; +struct task_struct *match_thread = NULL; void drm_mode_convert_to_split_mode(struct drm_display_mode *mode) { @@ -1773,30 +1775,42 @@ return 0; } -static int rockchip_drm_platform_probe(struct platform_device *pdev) +static int thread_component_match(void *data) { - struct device *dev = &pdev->dev; - struct component_match *match = NULL; - int ret; - - ret = rockchip_drm_platform_of_probe(dev); - if (ret) - return ret; - - match = rockchip_drm_match_add(dev); - if (IS_ERR(match)) - return PTR_ERR(match); + struct platform_device *pdev = data; + struct device *dev = &pdev->dev; + struct component_match *match = NULL; + int ret; + + while (!kthread_should_stop()) { + ret = rockchip_drm_platform_of_probe(dev); + if (ret) + return ret; + + match = rockchip_drm_match_add(dev); + if (IS_ERR(match)) + return PTR_ERR(match); + + ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); + if (ret < 0) { + rockchip_drm_match_remove(dev); + msleep(2000); + }else if(ret == 0){ + break; + } - ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); - if (ret < 0) { - rockchip_drm_match_remove(dev); - return ret; + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) + return ret; } + return 0; +} - ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); - if (ret) - return ret; - +static int rockchip_drm_platform_probe(struct platform_device *pdev) +{ + match_thread = kthread_run(thread_component_match, pdev, "tangfeng"); + if (IS_ERR(match_thread)) + return PTR_ERR(match_thread); return 0; } @@ -1806,6 +1820,9 @@ rockchip_drm_match_remove(&pdev->dev); + if (match_thread) + kthread_stop(match_thread); + return 0; }
上面代码比较仓促,这里match_thread应该放驱动私有结构体。
根据上面的改造,我们可以确保如果panel未准备好,thread会sleep 2秒,直到panel准备好之后,thread正常退出。这能够有效的解决死循环的问题
根据上述的分析,我们可以通过线程化来解决此问题,在内核中,很多事情都可以线程化,只是取决于驱动的使用场景。从实际意义上来说,将rockchip drm platform驱动线程化没有多大的影响,如果sleep那里调教的比较好,对实际显示的影响(黑屏时间)几乎不会多大(会有影响,但不大)。
而且,根据文章《Openharmony之众达rk3568内核显示panel代码梳理》的第四章可以知道,单独写panel_simple驱动在hdf的设计上来说实际上是一种偷懒的行为,所以我们不必要较真此方法的实用性。
根据众达3568的panel流程,完整的实现hdf 驱动才是这个问题的正解:如下
我们基于Openharmony开发,需要针对repo做一个周编译脚本,这样能够在每周固化系统版本。如下是脚本的使用方法
可以通过git获取此仓库如下:
git clone https://gitlab2.kylin.com/sh-product-Embedded/openharmony/weekly_build_script.git
阶段性版本固化
git clone git@gitlab2.kylin.com:sh-product-Embedded/openharmony/weekly_build_script.git -b B01-kylin-ZD-3568
当获取到此仓库之后,仅需要在仓库目录下运行run.sh即可,如下
cd weekly_build_script && ./run.sh
等待大概2小时后,可以获取到update.img,在如下目录:
out/kylin/update-xxxxxxxx.img
此脚本主要完成如下四个动作
其中同步代码和拉取prebuilts可能因为网络而失败,所以是通过while1来循环
而编译openharmony可能会失败,所以重编译次数为5次,如下:
if ((c>5)) ;then break ; fi
Openharmony在RK3588上不是很成熟,SDK编译经常失败,这里记录编译出的问题
此问题是openharmony内的BUILD.gn需要添加exec_script时,需要在core/gn/ohos_exec_script_allowlist.gni声明,针对此问题, 可以如下
diff --git a/core/gn/ohos_exec_script_allowlist.gni b/core/gn/ohos_exec_script_allowlist.gni index 0a986f49..813976a9 100644 --- a/core/gn/ohos_exec_script_allowlist.gni +++ b/core/gn/ohos_exec_script_allowlist.gni @@ -60,6 +60,7 @@ ohos_exec_script_config = { + "//device/board/hihope/dayu210/camera/BUILD.gn",
错误日志如下:
ninja: error: '../../drivers/peripheral/camera/vdi_base/v4l2/src/camera_dump.cpp', needed by 'obj/drivers/peripheral/camera/vdi_base/v4l2/src/camera_host_vdi_impl_1.0/camera_dump.o', missing and no known rule to make it
原因是dayu210的提交和openharmony sdk不匹配,故根据当前版本设置如下:
diff --git a/dayu210/camera/vdi_impl/v4l2/BUILD.gn b/dayu210/camera/vdi_impl/v4l2/BUILD.gn index 55b0018..c892d56 100755 --- a/dayu210/camera/vdi_impl/v4l2/BUILD.gn +++ b/dayu210/camera/vdi_impl/v4l2/BUILD.gn @@ -139,7 +139,7 @@ if (product_name == "rk3568_mini_system") { host_sources = [ "$camera_path/../v4l2/src/camera_device/camera_device_vdi_impl.cpp", - "$camera_path/../v4l2/src/camera_dump.cpp", + "$camera_path/dump/src/camera_dump.cpp" "$camera_path/../v4l2/src/camera_host/camera_host_config.cpp", @@ -158,6 +158,7 @@ if (product_name == "rk3568_mini_system") { ] host_includes = [ + "$camera_path/dump/include", "$camera_path/../../interfaces/include",
错误日志如下:
Exception: //device/soc/rockchip/rk3588/hardware/display:display_composer_vendor depend part //third_party/libdrm:libdrm, need set part deps libdrm info to device_rk3588.
这里根据文章《Openharmony 4.0 SDK移植RK3588之白名单》可以知道是"third_deps_bundle_not_add"设置问题,这里display_composer_vendor需要添加设置一下,如下
diff --git a/compile_standard_whitelist.json b/compile_standard_whitelist.json index ce4ca863..57fe46b5 100644 --- a/compile_standard_whitelist.json +++ b/compile_standard_whitelist.json "third_deps_bundle_not_add": [ "//arkcompiler/toolchain/tooling:libark_ecma_debugger_test", @@ -455,9 +529,13 @@ + "//device/soc/rockchip/rk3588/hardware/display:display_composer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:libdisplay_buffer_vendor", + "//device/soc/rockchip/rk3588/hardware/display:libhigbm_vendor",
此问题是虚函数声明了函数实现,但是继承的类没有实现,所以原因是openharmony4.1的改动在rk3588的hdi上没有更新,所以我们需要更新rk3588的hdi,新增这些虚函数定义,如下:
diff --git a/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.cpp b/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.cpp index 0affeb9..70c7456 100755 --- a/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.cpp +++ b/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.cpp @@ -116,6 +116,36 @@ int32_t DisplayBufferVdiImpl::IsSupportedAlloc(const std::vector<VerifyAllocInfo return HDF_ERR_NOT_SUPPORT; } +int32_t DisplayBufferVdiImpl::RegisterBuffer(const BufferHandle& handle) +{ + DISPLAY_LOGE("%s is not supported", __func__); + return DISPLAY_NOT_SUPPORT; +} + +int32_t DisplayBufferVdiImpl::SetMetadata(const BufferHandle& handle, uint32_t key, const std::vector<uint8_t>& value) +{ + DISPLAY_LOGE("%s is not supported", __func__); + return DISPLAY_NOT_SUPPORT; +} + +int32_t DisplayBufferVdiImpl::GetMetadata(const BufferHandle& handle, uint32_t key, std::vector<uint8_t>& value) +{ + DISPLAY_LOGE("%s is not supported", __func__); + return DISPLAY_NOT_SUPPORT; +} + +int32_t DisplayBufferVdiImpl::ListMetadataKeys(const BufferHandle& handle, std::vector<uint32_t>& keys) +{ + DISPLAY_LOGE("%s is not supported", __func__); + return DISPLAY_NOT_SUPPORT; +} + +int32_t DisplayBufferVdiImpl::EraseMetadataKey(const BufferHandle& handle, uint32_t key) +{ + DISPLAY_LOGE("%s is not supported", __func__); + return DISPLAY_NOT_SUPPORT; +} + diff --git a/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.h b/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.h index 9cb70a4..8e22852 100755 --- a/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.h +++ b/rk3588/hardware/display/src/display_gralloc/display_buffer_vdi_impl.h @@ -38,6 +38,11 @@ public: virtual int32_t InvalidateCache(const BufferHandle& handle) const override; virtual int32_t IsSupportedAlloc(const std::vector<VerifyAllocInfo>& infos, std::vector<bool>& supporteds) const override; + virtual int32_t RegisterBuffer(const BufferHandle& handle) override; + virtual int32_t SetMetadata(const BufferHandle& handle, uint32_t key, const std::vector<uint8_t>& value) override; + virtual int32_t GetMetadata(const BufferHandle& handle, uint32_t key, std::vector<uint8_t>& value) override; + virtual int32_t ListMetadataKeys(const BufferHandle& handle, std::vector<uint32_t>& keys) override; + virtual int32_t EraseMetadataKey(const BufferHandle& handle, uint32_t key) override; }; } // namespace DISPLAY } // namespace HDI
对于openharmony的rk3588代码分支,安装烧录之后发现默认不亮,这里原因是dayu210的提交人员做了一个定制内容修改,我们的代码需要屏蔽掉,从而使得openharmony的hdi能够正确的找到drm的plane。具体如下:
代码位置:device/soc/rockchip/rk3588/hardware/display/src/display_device/
这里面存在两个动态库,如下
libdisplay_composer_vdi_impl.z.so作为composer_service服务的接口层,作为特定芯片的接口向下的调用
ohos_shared_library("libdisplay_composer_vdi_impl") { sources = [ "src/display_device/display_composer_vdi_impl.cpp" ] include_dirs = [ "src/display_device", "${root_path}/drivers/peripheral/display/utils/include", "${root_path}/drivers/interface/display/composer", "${root_path}/drivers/peripheral/display/composer/hdi_service/include", "${root_path}/drivers/interface/display/composer/hdifd_parcelable", ] deps = [ ":display_composer_vendor" ] external_deps = [ "c_utils:utils", "drivers_interface_display:display_composer_idl_headers", "graphic_chipsetsdk:buffer_handle", "hilog:libhilog", "ipc:ipc_single", ] install_enable = true install_images = [ chipset_base_dir ] subsystem_name = "hdf" part_name = "rockchip_products" }
libdisplay_composer_vendor.z.so作为芯片层的显示框架的适配,实际调用libdrm的接口,如下
ohos_shared_library("display_composer_vendor") { sources = [ "src/display_device/drm_connector.cpp", "src/display_device/drm_crtc.cpp", "src/display_device/drm_device.cpp", "src/display_device/drm_display.cpp", "src/display_device/drm_encoder.cpp", "src/display_device/drm_plane.cpp", "src/display_device/drm_vsync_worker.cpp", "src/display_device/hdi_composer.cpp", "src/display_device/hdi_device_interface.cpp", "src/display_device/hdi_display.cpp", "src/display_device/hdi_drm_composition.cpp", "src/display_device/hdi_drm_layer.cpp", "src/display_device/hdi_gfx_composition.cpp", "src/display_device/hdi_layer.cpp", "src/display_device/hdi_netlink_monitor.cpp", "src/display_device/hdi_session.cpp", ] output_name = "display_composer_vendor" include_dirs = [ "src/display_gralloc", "src/display_device", "${root_path}/drivers/peripheral/display/buffer/hdi_service/include", "${root_path}/drivers/peripheral/display/utils/include", "${root_path}/drivers/peripheral/base", "${root_path}/drivers/interface/display/composer", "${root_path}/foundation/communication/ipc/interfaces/innerkits/ipc_core/include", "${root_path}/device/soc/rockchip/rk3588/hardware/rga/include", "${root_path}/drivers/interface/display/composer/hdifd_parcelable", ] deps = [ ":libdisplay_buffer_vdi_impl", "${root_path}/device/soc/rockchip/rk3588/hardware/rga:librga", "${root_path}/third_party/libdrm:libdrm", ] cflags_cc = [ "-Wno-error=unused-function", "-Wno-error=missing-braces", "-Wno-error=#warnings", ] external_deps = [ "c_utils:utils", "drivers_interface_display:display_buffer_idl_headers", "drivers_interface_display:display_composer_idl_headers", "hdf_core:libhdf_utils", "hilog:libhilog", "hitrace:hitrace_meter", ] install_enable = true install_images = [ chipset_base_dir ] subsystem_name = "hdf" part_name = "rockchip_products" }
对于rk3588的代码,我们编译出来发现hdmi不亮,所以需要跟踪这里的代码,这里值得留意的是
src/display_device/drm_device.cpp。
drm_device.cpp作为drm设备的创建动作,是基于图形的最先操作的,例如打开drm设备,查询crtc,plane,encoder,connector,和property。主要如下:
此函数打开card0显卡设备,从而获取drmFd的fd值,后续可作为drmMode函数的参数
std::shared_ptr<HdiDeviceInterface> DrmDevice::Create() { DISPLAY_LOGD(); if (mDrmFd == nullptr) { const std::string name("rockchip"); int drmFd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); // drmOpen(name.c_str(), nullptr); if (drmFd < 0) { DISPLAY_LOGE("drm file:%{public}s open failed %{public}s", name.c_str(), strerror(errno)); return nullptr; } DISPLAY_LOGD("the drm fd is %{public}d", drmFd); mDrmFd = std::make_shared<HdiFd>(drmFd); } if (mInstance == nullptr) { mInstance = std::make_shared<DrmDevice>(); } return mInstance; }
此函数作为drm格式的转换函数,因为openharmony上层的graphic_2d会有默认显示格式,而libdrm底层有可支持的显示格式,对于libdrm而言,可以通过drmModeAddFB2函数传入drm支持的图片格式的format值,对于openharmony而言,是当前系统支持的格式,我们可以如下知道openharmony支持如下图像格式:
typedef enum { PIXEL_FMT_CLUT8 = 0, /**< CLUT8 format */ PIXEL_FMT_CLUT1, /**< CLUT1 format */ PIXEL_FMT_CLUT4, /**< CLUT4 format */ PIXEL_FMT_RGB_565, /**< RGB565 format */ PIXEL_FMT_RGBA_5658, /**< RGBA5658 format */ PIXEL_FMT_RGBX_4444, /**< RGBX4444 format */ PIXEL_FMT_RGBA_4444, /**< RGBA4444 format */ PIXEL_FMT_RGB_444, /**< RGB444 format */ PIXEL_FMT_RGBX_5551, /**< RGBX5551 format */ PIXEL_FMT_RGBA_5551, /**< RGBA5551 format */ PIXEL_FMT_RGB_555, /**< RGB555 format */ PIXEL_FMT_RGBX_8888, /**< RGBX8888 format */ PIXEL_FMT_RGBA_8888, /**< RGBA8888 format */ PIXEL_FMT_RGB_888, /**< RGB888 format */ PIXEL_FMT_BGR_565, /**< BGR565 format */ PIXEL_FMT_BGRX_4444, /**< BGRX4444 format */ PIXEL_FMT_BGRA_4444, /**< BGRA4444 format */ PIXEL_FMT_BGRX_5551, /**< BGRX5551 format */ PIXEL_FMT_BGRA_5551, /**< BGRA5551 format */ PIXEL_FMT_BGRX_8888, /**< BGRX8888 format */ PIXEL_FMT_BGRA_8888, /**< BGRA8888 format */ PIXEL_FMT_YUV_422_I, /**< YUV422 interleaved format */ PIXEL_FMT_YCBCR_422_SP, /**< YCBCR422 semi-planar format */ PIXEL_FMT_YCRCB_422_SP, /**< YCRCB422 semi-planar format */ PIXEL_FMT_YCBCR_420_SP, /**< YCBCR420 semi-planar format */ PIXEL_FMT_YCRCB_420_SP, /**< YCRCB420 semi-planar format */ PIXEL_FMT_YCBCR_422_P, /**< YCBCR422 planar format */ PIXEL_FMT_YCRCB_422_P, /**< YCRCB422 planar format */ PIXEL_FMT_YCBCR_420_P, /**< YCBCR420 planar format */ PIXEL_FMT_YCRCB_420_P, /**< YCRCB420 planar format */ PIXEL_FMT_YUYV_422_PKG, /**< YUYV422 packed format */ PIXEL_FMT_UYVY_422_PKG, /**< UYVY422 packed format */ PIXEL_FMT_YVYU_422_PKG, /**< YVYU422 packed format */ PIXEL_FMT_VYUY_422_PKG, /**< VYUY422 packed format */ PIXEL_FMT_VENDER_MASK = 0X7FFF0000, /**< vendor mask format */ PIXEL_FMT_BUTT = 0X7FFFFFFF /**< Invalid pixel format */ } PixelFormat;
而对于libdrm的格式可以在此文件查到:third_party/libdrm/include/drm/drm_fourcc.h
故代码实现如下:
uint32_t DrmDevice::ConvertToDrmFormat(PixelFormat fmtIn) { static const PixelFormatConvertTbl convertTable[] = { {DRM_FORMAT_XBGR8888, PIXEL_FMT_RGBX_8888}, {DRM_FORMAT_ABGR8888, PIXEL_FMT_RGBA_8888}, {DRM_FORMAT_RGB888, PIXEL_FMT_RGB_888}, {DRM_FORMAT_RGB565, PIXEL_FMT_BGR_565}, {DRM_FORMAT_BGRX4444, PIXEL_FMT_BGRX_4444}, {DRM_FORMAT_BGRA4444, PIXEL_FMT_BGRA_4444}, {DRM_FORMAT_RGBA4444, PIXEL_FMT_RGBA_4444}, {DRM_FORMAT_RGBX4444, PIXEL_FMT_RGBX_4444}, {DRM_FORMAT_BGRX5551, PIXEL_FMT_BGRX_5551}, {DRM_FORMAT_BGRA5551, PIXEL_FMT_BGRA_5551}, {DRM_FORMAT_BGRX8888, PIXEL_FMT_BGRX_8888}, {DRM_FORMAT_ARGB8888, PIXEL_FMT_BGRA_8888}, {DRM_FORMAT_NV12, PIXEL_FMT_YCBCR_420_SP}, {DRM_FORMAT_NV21, PIXEL_FMT_YCRCB_420_SP}, {DRM_FORMAT_YUV420, PIXEL_FMT_YCBCR_420_P}, {DRM_FORMAT_YVU420, PIXEL_FMT_YCRCB_420_P}, {DRM_FORMAT_NV16, PIXEL_FMT_YCBCR_422_SP}, {DRM_FORMAT_NV61, PIXEL_FMT_YCRCB_422_SP}, {DRM_FORMAT_YUV422, PIXEL_FMT_YCBCR_422_P}, {DRM_FORMAT_YVU422, PIXEL_FMT_YCRCB_422_P}, }; uint32_t fmtOut = 0; for (uint32_t i = 0; i < sizeof(convertTable) / sizeof(convertTable[0]); i++) { if (convertTable[i].pixFormat == fmtIn) { fmtOut = convertTable[i].drmFormat; } } DISPLAY_LOGD("fmtIn %{public}d, outFmt %{public}d", fmtIn, fmtOut); return fmtOut; }
此函数通过遍历drm的property来将其保存在变量prop中。其调用流程如下:
drmModeObjectGetProperties--->drmModeGetProperty--->drmModeFreeProperty-→drmModeFreeObjectProperties
通过上述流程,可提取drmmode的如下结构体数据
typedef struct _drmModeProperty { uint32_t prop_id; uint32_t flags; char name[DRM_PROP_NAME_LEN]; int count_values; uint64_t *values; /* store the blob lengths */ int count_enums; struct drm_mode_property_enum *enums; int count_blobs; uint32_t *blob_ids; /* store the blob IDs */ } drmModePropertyRes, *drmModePropertyPtr;
并将其存放在DrmProperty结构体中,如下
struct DrmProperty { uint32_t propId; uint64_t value; uint32_t type; uint32_t flags; std::string name; std::vector<uint64_t> values; std::vector<DrmPropertyEnum> enums; std::vector<uint32_t> blob_ids; };
故,此函数的代码实现如下:
int32_t DrmDevice::GetProperty(uint32_t objId, uint32_t objType, const std::string &name, DrmProperty &prop) { drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(GetDrmFd(), objId, objType); DISPLAY_CHK_RETURN((!props), DISPLAY_FAILURE, DISPLAY_LOGE("can not get properties")); bool found = false; for (uint32_t i = 0; i < props->count_props; i++) { drmModePropertyPtr p = drmModeGetProperty(GetDrmFd(), props->props[i]); if (strcmp(p->name, name.c_str()) == 0) { found = true; prop.propId = p->prop_id; prop.value = props->prop_values[i]; prop.name = p->name; prop.flags = p->flags; for (int i = 0; i < p->count_values; ++i) { prop.values.push_back(p->values[i]); } for (int i = 0; i < p->count_enums; ++i) { prop.enums.push_back(DrmPropertyEnum(&p->enums[i])); } for (int i = 0; i < p->count_blobs; ++i) { prop.blob_ids.push_back(p->blob_ids[i]); } if (prop.flags & DRM_MODE_PROP_RANGE) { prop.type = static_cast<uint32_t>(DrmPropertyType::DRM_PROPERTY_TYPE_INT); } else if (prop.flags & DRM_MODE_PROP_ENUM) { prop.type = static_cast<uint32_t>(DrmPropertyType::DRM_PROPERTY_TYPE_ENUM); } else if (prop.flags & DRM_MODE_PROP_OBJECT) { prop.type = static_cast<uint32_t>(DrmPropertyType::DRM_PROPERTY_TYPE_OBJECT); } else if (prop.flags & DRM_MODE_PROP_BLOB) { prop.type = static_cast<uint32_t>(DrmPropertyType::DRM_PROPERTY_TYPE_BLOB); } else if (prop.flags & DRM_MODE_PROP_BITMASK) { prop.type = static_cast<uint32_t>(DrmPropertyType::DRM_PROPERTY_TYPE_BITMASK); } } drmModeFreeProperty(p); } drmModeFreeObjectProperties(props); return found ? DISPLAY_SUCCESS : DISPLAY_NOT_SUPPORT; }
此函数初始化drm的设置,主要设置并检查此程序是否为第一个drm的gpu渲染程序(drmSetMaster/drmIsMaster)和设置drm默认使用atomic接口(drmSetClientCap),主要实现如下
int32_t DrmDevice::Init() { int ret = drmSetClientCap(GetDrmFd(), DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, DISPLAY_LOGE("DRM_CLIENT_CAP_UNIVERSAL_PLANES set failed %{public}s", strerror(errno))); ret = drmSetClientCap(GetDrmFd(), DRM_CLIENT_CAP_ATOMIC, 1); DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, DISPLAY_LOGE("DRM_CLIENT_CAP_ATOMIC set failed %{public}s", strerror(errno))); ret = drmSetMaster(GetDrmFd()); DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, DISPLAY_LOGE("can not set to master errno : %{public}d", errno)); DISPLAY_LOGE("chenyf master"); ret = drmIsMaster(GetDrmFd()); DISPLAY_CHK_RETURN((!ret), DISPLAY_FAILURE, DISPLAY_LOGE("is not master : %{public}d", errno)); return DISPLAY_SUCCESS; }
此函数作为显示器热插拔时候使用drm分配crtc,从而支持插拔connector时可以正常显示。
bool DrmDevice::HandleHotplug(uint32_t dispId, bool plugIn) { uint32_t find = 0; uint32_t connectorId; for (auto &dispConnectorIdMap : dispConnectorIdMaps_) { if (dispConnectorIdMap.first == dispId) { connectorId = dispConnectorIdMap.second; find = 1; break; } } if (find) { for (auto &connectorPair : mConnectors) { auto connector = connectorPair.second; if (connectorId == connector->GetId()) { if (connector->HandleHotplug(mEncoders, mCrtcs, plugIn) == true) { connector->Init(*this); return true; } } } } return false; }
此函数主要用于发现display的crtc,encoder,connector,plane,并触发DrmDisplay的初始化操作。
std::unordered_map<uint32_t, std::shared_ptr<HdiDisplay>> DrmDevice::DiscoveryDisplay() { uint32_t dispId; uint32_t connectorId; dispConnectorIdMaps_.clear(); mDisplays.clear(); drmModeResPtr res = drmModeGetResources(GetDrmFd()); DISPLAY_CHK_RETURN((res == nullptr), mDisplays, DISPLAY_LOGE("can not get drm resource")); // discovery all drm resource FindAllCrtc(res); FindAllEncoder(res); FindAllConnector(res); FindAllPlane(); DISPLAY_LOGD(); // travel all connector for (auto &connectorPair : mConnectors) { auto connector = connectorPair.second; uint32_t crtcId = 0; int32_t ret = connector->PickIdleCrtcId(mEncoders, mCrtcs, crtcId); if (ret != DISPLAY_SUCCESS) { continue; } DISPLAY_LOGD(); auto crtcIter = mCrtcs.find(crtcId); if (crtcIter == mCrtcs.end()) { DISPLAY_LOGE("can not find crtc for the id %{public}d", connector->GetId()); continue; } DISPLAY_LOGD(); auto crtc = crtcIter->second; DISPLAY_LOGD("crtc %{public}p", crtc.get()); // create the display std::shared_ptr<HdiDisplay> display = std::make_shared<DrmDisplay>(connector, crtc, mInstance); DISPLAY_LOGD(); display->Init(); dispId = display->GetId(); connectorId = connector->GetId(); mDisplays.emplace(dispId, std::move(display)); dispConnectorIdMaps_.emplace(dispId, connectorId); } DISPLAY_LOGD("find display size %{public}zd", mDisplays.size()); return mDisplays; }
对于dayu210的仓库代码,修改如下:
diff --git a/rk3588/hardware/display/src/display_device/drm_device.cpp b/rk3588/hardware/display/src/display_device/drm_device.cpp index 18ef587..146dc6b 100755 --- a/rk3588/hardware/display/src/display_device/drm_device.cpp +++ b/rk3588/hardware/display/src/display_device/drm_device.cpp @@ -276,8 +276,6 @@ std::vector<std::shared_ptr<DrmPlane>> DrmDevice::GetDrmPlane(uint32_t pipe, uin { std::vector<std::shared_ptr<DrmPlane>> planes; for (const auto &plane : mPlanes) { - if (plane->GetId() != 105) - continue; if (plane->IsIdle() && ((1 << pipe) & plane->GetPossibleCrtcs()) && (type == plane->GetType())) { planes.push_back(plane); }
可以发现,dayu210的官方提交会固定在plane105上,但是rk3588s的板子的plane不存在105。所以rk3588s的openharmony系统无法正常显示。将其注释即可正常通过DrmDevice::GetDrmPlane获取到rk3588s平台的所有plane。此时openharmony即可正常通过drm hdi正常分配plane来用于显示了。
获取平台所有的plane和crtc以及connector如下命令
# cat /sys/kernel/debug/dri/0/state plane[54]: Esmart0-win0 crtc=video_port0 fb=241 allocated by = IPC_0_764 refcount=2 format=AB24 little-endian (0x34324241) modifier=0x0 size=1920x1200 layers: size[0]=1920x1200 pitch[0]=7680 offset[0]=0 obj[0]: name=0 refcount=3 start=00000000 size=9216000 imported=no crtc-pos=1920x1200+0+0 src-pos=1920.000000x1200.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[76]: Esmart1-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[98]: Esmart2-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[120]: Esmart3-win0 crtc=video_port3 fb=233 allocated by = IPC_0_764 refcount=2 format=AB24 little-endian (0x34324241) modifier=0x0 size=800x1280 layers: size[0]=800x1280 pitch[0]=3200 offset[0]=0 obj[0]: name=0 refcount=3 start=00000000 size=4096000 imported=no crtc-pos=800x1280+0+0 src-pos=800.000000x1280.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[142]: Cluster0-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[156]: Cluster1-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[170]: Cluster2-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range plane[184]: Cluster3-win0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.000000x0.000000+0.000000+0.000000 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range crtc[68]: video_port0 enable=1 active=1 self_refresh_active=0 planes_changed=1 mode_changed=0 active_changed=0 connectors_changed=0 color_mgmt_changed=0 plane_mask=1 connector_mask=2 encoder_mask=2 mode: "1920x1200": 60 154000 1920 1968 2000 2080 1200 1203 1209 1235 0x48 0x5 crtc[90]: video_port1 enable=0 active=0 self_refresh_active=0 planes_changed=0 mode_changed=0 active_changed=0 connectors_changed=0 color_mgmt_changed=0 plane_mask=0 connector_mask=0 encoder_mask=0 mode: "": 0 0 0 0 0 0 0 0 0 0 0x0 0x0 crtc[112]: video_port2 enable=0 active=0 self_refresh_active=0 planes_changed=0 mode_changed=0 active_changed=0 connectors_changed=0 color_mgmt_changed=0 plane_mask=0 connector_mask=0 encoder_mask=0 mode: "": 0 0 0 0 0 0 0 0 0 0 0x0 0x0 crtc[134]: video_port3 enable=1 active=1 self_refresh_active=0 planes_changed=1 mode_changed=0 active_changed=0 connectors_changed=0 color_mgmt_changed=0 plane_mask=8 connector_mask=4 encoder_mask=4 mode: "800x1280": 60 72600 800 832 846 872 1280 1360 1368 1388 0x48 0xa connector[203]: Writeback-1 crtc=(null) self_refresh_aware=0 connector[205]: HDMI-A-1 crtc=video_port0 self_refresh_aware=0 connector[219]: DSI-1 crtc=video_port3 self_refresh_aware=0 connector[222]: DP-1 crtc=(null) self_refresh_aware=0