在基于众达的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
Openharmony在显示drm panel上的实现仅仅实现了dsi接口的显示屏,这就导致了如果客户使用了非dsi的显示,例如edp,hdmi,则没有办法在openharmony上显示。目前来看openharmony基本没有新的补丁合入,所以这一块的工作量得自行完成。本文通过移植simple_panel来实现非dsi的其他驱动的panel实现。
https://gitlab2.kylin.com/sh-product-Embedded/openharmony/drivers_hdf_core
这里面有两笔提交,一笔是simple_panel的c代码提交如下:
https://gitlab2.kylin.com/sh-product-Embedded/openharmony/drivers_hdf_core/-/commit/951647792a9ca86897b3f23f5edd6bf9f82d7aa5
一笔是Makefile和Kconfig使能的提交如下:
https://gitlab2.kylin.com/sh-product-Embedded/openharmony/drivers_hdf_core/-/commit/235164e43ced1c6c8afedcb660b3b52be51b267e
我们知道,内核提供了标准的panel_simple.c的驱动,其文件如下:
drivers/gpu/drm/panel/panel-simple.c
这里我们关注非dsi的实现,如下:
static struct platform_driver panel_simple_platform_driver = { .driver = { .name = "panel-simple", .of_match_table = platform_of_match, }, .probe = panel_simple_platform_probe, .remove = panel_simple_platform_remove, .shutdown = panel_simple_platform_shutdown, };
所以根据内核标准代码,需要实现其基本功能,这里拆分成了panel_simple_common.c和hdf_drm_panel_simple.c
代码写的比较丑陋,但是我对照了基本逻辑,应该是没有大问题的。
这里panel_simple_common.c主要是panel硬件的信息设置,需要对接dts的内容,并提供了panel_simple_loader_protect函数,如下
而hdf_drm_panel_simple.c主要是对drm负责的panel注册,主要是drm_panel_init和drm_panel_add,这里也需要填充drm_panel_init所需要的回调drm_panel_funcs。如下
综上,我们可以知道,这里的只是通过hdf实现了simple_panel的驱动而已。
对于Makefile,这里如下设置
ifeq ($(CONFIG_DRIVERS_HDF_SIMPLE_PANEL), y) obj-$(CONFIG_DRIVERS_HDF_SIMPLE_PANEL) += \ $(DISPLAY_ROOT_DIR)/panel/panel_simple_common.o else obj-$(CONFIG_ARCH_ROCKCHIP) += \ $(DISPLAY_ROOT_DIR)/panel/ili9881_st_5p5.o endif
这里因为实现了两次panel_simple_loader_protect,所以代码是互斥的,所以通过内核配置CONFIG_DRIVERS_HDF_SIMPLE_PANEL将其隔开
根据上面的讲述,我们可以完成hcs的驱动移植,这里通过修改hcs来使得hcs生效,如下:
diff --git a/rk3568/hdf_config/khdf/device_info/device_info.hcs b/rk3568/hdf_config/khdf/device_info/device_info.hcs index 16b6abf..267aa13 100644 --- a/rk3568/hdf_config/khdf/device_info/device_info.hcs +++ b/rk3568/hdf_config/khdf/device_info/device_info.hcs @@ -241,7 +241,7 @@ policy = 0; priority = 197; preload = 0; - moduleName = "HDF_DRMPANEL"; + moduleName = "HDF_DRM_PANEL_SIMPLE"; } } device_hdf_disp :: device { @@ -253,7 +253,7 @@ serviceName = "hdf_disp"; } } - device_hi35xx_disp :: device { + device_hi35xx_disp :: device { device0 :: deviceNode { policy = 0; priority = 195; @@ -284,7 +284,7 @@ policy = 0; priority = 100; preload = 0; - moduleName = "LCD_ILI9881_ST_5P5"; + moduleName = "PANEL_SIMPLE_COMMON"; } } device_pwm_bl :: device {
为什么这样改,因为hcs的设定中,host是唯一的,所以display只能选择"HDF_DRM_PANEL_SIMPLE"或者"HDF_DRMPANEL"
同样的驱动LCD_ILI9881_ST_5P5和PANEL_SIMPLE_COMMON也互斥,所以只能二选一
如果对hcs生效有疑问的,可以查看文章Openharmony hcs文件编译不生效的问题