在基于众达的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 驱动才是这个问题的正解:如下