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文件编译不生效的问题
Openharmony 4.0的代码编译rk3588代码后,出现开机logo图层仍存在,从而导致系统显示闪烁。最后此问题的原因在于drm_plane.cpp和drm_plane.h的图层设置逻辑异常。如下是问题分析
问题现象如下:
# cat /sys/kernel/debug/dri/0/framebuffer framebuffer[232]: allocated by = IPC_1_798 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=4 start=00000000 size=9216000 imported=no framebuffer[244]: allocated by = IPC_1_798vop2_win_data 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=4 start=00000000 size=4096000 imported=no framebuffer[243]: allocated by = IPC_1_798 refcount=1 format=AB24 little-endian (0x34324241) modifier=0x0 size=1920x1200 layers: size[0]=1920x1200 pitch[0]=7680 offset[0]=0 obj[0]: name=0 refcount=4 start=00000000 size=9216000 imported=no framebuffer[242]: allocated by = IPC_1_798 refcount=1 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 framebuffer[238]: allocated by = [fbcon] refcount=1 format=XR24 little-endian (0x34325258) modifier=0x0 size=1920x1280 layers: size[0]=1920x1280 pitch[0]=7680 offset[0]=0 obj[0]: name=0 refcount=1 start=00000000 size=9830400 imported=no framebuffer[233]: allocated by = kworker/u16:3 refcount=1 format=RG16 little-endian (0x36314752) modifier=0x0 size=500x501 layers: size[0]=500x501 pitch[0]=1000 offset[0]=765952 obj[0]: name=0 refcount=1 start=00000000 size=1269760 imported=no framebuffer[231]: allocated by = kworker/u16:3 refcount=1 format=RG16 little-endian (0x36314752) modifier=0x0 size=500x501 layers: size[0]=500x501 pitch[0]=1000 offset[0]=765952 obj[0]: name=0 refcount=1 start=00000000 size=1269760 imported=no
通过上述信息可以发现,系统起来之后IPC_1_798作为Openharmony的compose进程占用了fb 232/244/243/242
控制台fbcon占用了fb 238。这都是没有问题的,但是有问题的是kworker/u16:3占用了233/231
这里值得注意的是kworker/u16:3就是内核绘制开机logo的线程,因为当前环境上是双屏,hdmi+dsi显示,则这里有两个kworker,fb233在hdmi上fb231在dsi上。
理论上开机之后,fb不应该被开机logo这样的kworker占用。
根据drm的设计,fb是挂在plane下的,也就是说,对于openharmony的drm上层,对这个kworker的plane的设置存在问题。所以应该怀疑rk3588的plane设置存在异常。
关于设备树,可以知道哪些vp使用哪些plane。如下
/* vp0 & vp1 splice for 8K output */ &vp0 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER0 | 1 << ROCKCHIP_VOP2_ESMART0)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART0>; assigned-clocks = <&cru ACLK_VOP>; assigned-clock-rates = <800000000>; }; &vp1 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER1 | 1 << ROCKCHIP_VOP2_ESMART1)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART1>; }; &vp2 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER2 | 1 << ROCKCHIP_VOP2_ESMART2)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART2>; }; &vp3 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER3 | 1 << ROCKCHIP_VOP2_ESMART3)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART3>; };
可以知道,这里vp0使用了cluster0/esmart0,vp1使用了cluster1/esmart1, vp2使用了cluster2/esmart2,vp3使用了cluster3/esmart3。
对于内核,在drivers/gpu/drm/rockchip/rockchip_drm_vop2.c的vop2_crtc_create_plane_mask_property对应了宏的关系如下:
static const struct drm_prop_enum_list props[] = { { ROCKCHIP_VOP2_CLUSTER0, "Cluster0" }, { ROCKCHIP_VOP2_CLUSTER1, "Cluster1" }, { ROCKCHIP_VOP2_ESMART0, "Esmart0" }, { ROCKCHIP_VOP2_ESMART1, "Esmart1" }, { ROCKCHIP_VOP2_SMART0, "Smart0" }, { ROCKCHIP_VOP2_SMART1, "Smart1" }, { ROCKCHIP_VOP2_CLUSTER2, "Cluster2" }, { ROCKCHIP_VOP2_CLUSTER3, "Cluster3" }, { ROCKCHIP_VOP2_ESMART2, "Esmart2" }, { ROCKCHIP_VOP2_ESMART3, "Esmart3" }, };
需要留意的宏如下:
#define ROCKCHIP_VOP2_CLUSTER0 0 #define ROCKCHIP_VOP2_CLUSTER1 1 #define ROCKCHIP_VOP2_ESMART0 2 #define ROCKCHIP_VOP2_ESMART1 3 #define ROCKCHIP_VOP2_SMART0 4 #define ROCKCHIP_VOP2_SMART1 5 #define ROCKCHIP_VOP2_CLUSTER2 6 #define ROCKCHIP_VOP2_CLUSTER3 7 #define ROCKCHIP_VOP2_ESMART2 8 #define ROCKCHIP_VOP2_ESMART3 9
随即代码如下:
prop = drm_property_create_bitmask(vop2->drm_dev, DRM_MODE_PROP_IMMUTABLE, "PLANE_MASK", props, ARRAY_SIZE(props), 0xffffffff); if (!prop) { DRM_DEV_ERROR(vop2->dev, "create plane_mask prop for vp%d failed\n", vp->id); return -ENOMEM; } vp->plane_mask_prop = prop; drm_object_attach_property(&crtc->base, vp->plane_mask_prop, plane_mask);
可以知道,通过drm的ioctl可以读取"PLANE_MASK"来知道plane的图层设置。
根据rk的vop init过程,可以知道,vop的NAME来自vop2_win_data.name字段,则如下:
static const struct vop2_win_data rk3588_vop_win_data[] = { 其对应如下: .name = "Cluster0-win0", .name = "Cluster0-win1", .name = "Cluster1-win0", .name = "Cluster1-win1", .name = "Cluster2-win0", .name = "Cluster2-win1", .name = "Cluster3-win0", .name = "Cluster3-win1", .name = "Esmart0-win0", .name = "Esmart2-win0", .name = "Esmart1-win0", .name = "Esmart3-win0",
这些name都会被注册为drm的name属性,如下
static int vop2_plane_create_name_property(struct vop2 *vop2, struct vop2_win *win) { struct drm_prop_enum_list *props = vop2->plane_name_list; struct drm_property *prop; uint64_t bits = BIT_ULL(win->plane_id); prop = drm_property_create_bitmask(vop2->drm_dev, DRM_MODE_PROP_IMMUTABLE, "NAME", props, vop2->registered_num_wins, bits); if (!prop) { DRM_DEV_ERROR(vop2->dev, "create Name prop for %s failed\n", win->name); return -ENOMEM; } win->name_prop = prop; drm_object_attach_property(&win->base.base, win->name_prop, bits); return 0; }
针对此,可以知道内核这边rk3588系列如何设置plane,对于rk3588来说,vop在此芯片上存在12个plane,对于esmart来说,默认都是win0,而对于cluster来说,可以选择win0和win1。
对于图层的选择,我们只需要针对不同的plane的bitmask来选择图层,也就是cluster/smart/esmart。具体在哪个win上,可以由应用和内核选择。
对于openharmony的代码,主要体现在:
src/display_device/drm_crtc.cpp src/display_device/drm_plane.cpp src/display_device/drm_plane.h
对于crtc,我们需要知道有哪些planemask,如下:
可以发现,planemask的设置与内核是一致的,我们通过确定planemask来选择对应图层给应用使用
struct PlaneMaskName planeMaskNames[] = { { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER0_MASK, "Cluster0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER1_MASK, "Cluster1" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER2_MASK, "Cluster2" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER3_MASK, "Cluster3" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_MASK, "Esmart0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_MASK, "Esmart1" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART2_MASK, "Esmart2" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART3_MASK, "Esmart3" }, { DrmPlaneType::DRM_PLANE_TYPE_Unknown, "unknown" }, };
如下遍历PLANE_MASK属性,用来与内核同步设置,这里因为上述和内核是一致的,所以遍历后的mPlaneMask和上述PlaneMaskName其实内容一致的。
ret = drmDevice.GetCrtcProperty(*this, "PLANE_MASK", prop); if (ret != DISPLAY_SUCCESS) { DISPLAY_LOGE("Failed to get plane_mask property"); } else { for (int i = 0; i < static_cast<int>(ARRAY_SIZE(planeMaskNames)); i++) { for (auto &drmEnum : prop.enums) { if (!strncmp(drmEnum.name.c_str(), (const char*)planeMaskNames[i].name, strlen(drmEnum.name.c_str())) && (prop.value & (1LL << drmEnum.value)) > 0) { mPlaneMask |= static_cast<int>(planeMaskNames[i].mask); DISPLAY_LOGI("crtc id %{public}d, plane name %{public}s value %{public}llx", GetId(), (const char*)planeMaskNames[i].name, (long long)planeMaskNames[i].mask); } } } }
而对于plane的定义,如下
struct PlaneTypeName planeTypeNames[] = { { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER0_WIN0, "Cluster0-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER0_WIN1, "Cluster0-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER1_WIN0, "Cluster1-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER1_WIN1, "Cluster1-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_WIN0, "Esmart0-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_WIN1, "Esmart0-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_WIN2, "Esmart0-win2" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_WIN3, "Esmart0-win3" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_WIN0, "Esmart1-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_WIN1, "Esmart1-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_WIN2, "Esmart1-win2" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_WIN3, "Esmart1-win3" }, { DrmPlaneType::DRM_PLANE_TYPE_Unknown, "unknown" }, };
这里可以发现,上层使用的plane的12个和内核描述的12个不相符,内核有cluster2和cluster3,且内核的esmart只有win0,所以如下函数在于内核匹配时出现了不一致的情况
ret = drmDevice.GetPlaneProperty(*this, "NAME", prop); DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get pane crtc prop id")); for (int i = 0; i < static_cast<int>ARRAY_SIZE(planeTypeNames); i++) { uint32_t find_name = 0; for (auto &drmEnum : prop.enums) { if (!strncmp(drmEnum.name.c_str(), (const char*)planeTypeNames[i].name, strlen(planeTypeNames[i].name))) { find_name = (1LL << drmEnum.value); } } if (find_name) { DISPLAY_LOGI("find plane id %{public}d, type %{public}x %{public}s", GetId(), planeTypeNames[i].type, planeTypeNames[i].name); mWinType = planeTypeNames[i].type; mName = planeTypeNames[i].name; break; } }
所以,对于planeTypeNames的描述错误,我们为了和实际硬件完全一致,则修改如下:
struct PlaneTypeName planeTypeNames[] = { { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER0_WIN0, "Cluster0-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER0_WIN1, "Cluster0-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER1_WIN0, "Cluster1-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER1_WIN1, "Cluster1-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER2_WIN0, "Cluster2-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER2_WIN1, "Cluster2-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER3_WIN0, "Cluster3-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER3_WIN1, "Cluster3-win1" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART0_WIN0, "Esmart0-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_WIN0, "Esmart1-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART2_WIN0, "Esmart2-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_ESMART3_WIN0, "Esmart3-win0" }, { DrmPlaneType::DRM_PLANE_TYPE_Unknown, "unknown" }, };
至此,还未完成,Openharmony对于Plane type的宏定义将两个概念放在同一个enum中,这导致匹配是计算plane是否在planemask中存在错误。如下代码根据mask选择plane
int32_t HdiDrmComposition::FindPlaneAndApply(drmModeAtomicReqPtr pset) { int32_t ret = 0; for (uint32_t i = 0; i < mCompLayers.size(); i++) { HdiDrmLayer *layer = static_cast<HdiDrmLayer *>(mCompLayers[i]); HdiLayer *hlayer = mCompLayers[i]; for (uint32_t j = 0; j < mPlanes.size(); j++) { auto &drmPlane = mPlanes[j]; if (drmPlane->GetPipe() != 0 && drmPlane->GetPipe() != (1 << mCrtc->GetPipe())) { DISPLAY_LOGI("plane %{public}d used pipe %{public}d crtc pipe %{public}d", drmPlane->GetId(), drmPlane->GetPipe(), mCrtc->GetPipe()); continue; } /* Check whether the plane belond to the crtc */ if (!(static_cast<int>(drmPlane->GetWinType()) & mCrtc->GetPlaneMask())) { continue; } DISPLAY_LOGD("use plane %{public}d WinType %{public}x crtc %{public}d PlaneMask %{public}x", drmPlane->GetId(), drmPlane->GetWinType(), mCrtc->GetId(), mCrtc->GetPlaneMask()); if (drmPlane->GetCrtcId() == mCrtc->GetId() || drmPlane->GetCrtcId() == 0) { ret = ApplyPlane(*layer, *hlayer, *drmPlane, pset); if (ret != DISPLAY_SUCCESS) { DISPLAY_LOGE("apply plane failed"); break; } /* mark the plane is used by crtc */ drmPlane->BindToPipe(1 << mCrtc->GetPipe()); break; } } } return DISPLAY_SUCCESS; }
这里函数留意的是
if (!(static_cast<int>(drmPlane->GetWinType()) & mCrtc->GetPlaneMask())) { continue; }
可以知道,这里检查的GetWinType和GetPlaneMask都是来自于enum class DrmPlaneType,所以这个DrmPlaneType不能瞎设置。我们必须保障其planemask和plane对应,也就是按位相与为true。具体如下:
一个是plane枚举,如下
// Cluster 0 DRM_PLANE_TYPE_CLUSTER0_WIN0 = 1 << 0, DRM_PLANE_TYPE_CLUSTER0_WIN1 = 1 << 1, // Cluster 1 DRM_PLANE_TYPE_CLUSTER1_WIN0 = 1 << 2, DRM_PLANE_TYPE_CLUSTER1_WIN1 = 1 << 3, // Cluster 2 DRM_PLANE_TYPE_CLUSTER2_WIN0 = 1 << 4, DRM_PLANE_TYPE_CLUSTER2_WIN1 = 1 << 5, // Cluster 3 DRM_PLANE_TYPE_CLUSTER3_WIN0 = 1 << 6, DRM_PLANE_TYPE_CLUSTER3_WIN1 = 1 << 7, // Esmart 0 DRM_PLANE_TYPE_ESMART0_WIN0 = 1 << 8, DRM_PLANE_TYPE_ESMART0_WIN1 = 1 << 10, DRM_PLANE_TYPE_ESMART0_WIN2 = 1 << 16, DRM_PLANE_TYPE_ESMART0_WIN3 = 1 << 20, // Esmart 1 DRM_PLANE_TYPE_ESMART1_WIN0 = 1 << 12, DRM_PLANE_TYPE_ESMART1_WIN1 = 1 << 13, DRM_PLANE_TYPE_ESMART1_WIN2 = 1 << 14, DRM_PLANE_TYPE_ESMART1_WIN3 = 1 << 15, // Esmart 2 DRM_PLANE_TYPE_ESMART2_WIN0 = 1 << 9, DRM_PLANE_TYPE_ESMART2_WIN1 = 1 << 17, DRM_PLANE_TYPE_ESMART2_WIN2 = 1 << 18, DRM_PLANE_TYPE_ESMART2_WIN3 = 1 << 19, // Esmart 3 DRM_PLANE_TYPE_ESMART3_WIN0 = 1 << 11, DRM_PLANE_TYPE_ESMART3_WIN1 = 1 << 21, DRM_PLANE_TYPE_ESMART3_WIN2 = 1 << 22, DRM_PLANE_TYPE_ESMART3_WIN3 = 1 << 23,
另一个是planemask的枚举
// Cluster mask DRM_PLANE_TYPE_CLUSTER0_MASK= 0x1, DRM_PLANE_TYPE_CLUSTER1_MASK= 0x2, DRM_PLANE_TYPE_CLUSTER2_MASK= 0x40, DRM_PLANE_TYPE_CLUSTER3_MASK= 0x80, DRM_PLANE_TYPE_CLUSTER_MASK = 0xff, // Esmart mask DRM_PLANE_TYPE_ESMART0_MASK = 0x04, DRM_PLANE_TYPE_ESMART1_MASK = 0x08, DRM_PLANE_TYPE_ESMART2_MASK = 0x100, DRM_PLANE_TYPE_ESMART3_MASK = 0x200, DRM_PLANE_TYPE_ESMART_MASK = 0xffff00, DRM_PLANE_TYPE_Unknown = 0xffffffff,
对于planemask,如上分析可以知道,与内核是一致的。但是对于plane的枚举,我们发现其是bit1的左移设置。但是却发现,cluster0_mask 只代表0x1,但是cluster0_win0是0x1,而cluster_win1是0x2。这导致FindPlaneAndApply只会选择cluster0_win0。
其他宏也有这样的逻辑错误。这里不一一列举。
对于这样的问题,主要原因是plane的枚举设置太随意了。所以我们保持着改动最小代码的前提下,如下设置枚举
enum class DrmPlaneType { // Cluster 0 DRM_PLANE_TYPE_CLUSTER0_WIN0 = 1 << 10 | 0x1, DRM_PLANE_TYPE_CLUSTER0_WIN1 = 1 << 11 | 0x1, // Cluster 1 DRM_PLANE_TYPE_CLUSTER1_WIN0 = 1 << 12 | 0x2, DRM_PLANE_TYPE_CLUSTER1_WIN1 = 1 << 13 | 0x2, // Cluster 2 DRM_PLANE_TYPE_CLUSTER2_WIN0 = 1 << 14 | 0x40, DRM_PLANE_TYPE_CLUSTER2_WIN1 = 1 << 15 | 0x40, // Cluster 3 DRM_PLANE_TYPE_CLUSTER3_WIN0 = 1 << 16 | 0x80, DRM_PLANE_TYPE_CLUSTER3_WIN1 = 1 << 17 | 0x80, // Esmart 0 DRM_PLANE_TYPE_ESMART0_WIN0 = 1 << 18 | 0x04, // Esmart 1 DRM_PLANE_TYPE_ESMART1_WIN0 = 1 << 19 | 0x08, // Esmart 2 DRM_PLANE_TYPE_ESMART2_WIN0 = 1 << 20 | 0x100, // Esmart 3 DRM_PLANE_TYPE_ESMART3_WIN0 = 1 << 21 | 0x200, // Cluster mask DRM_PLANE_TYPE_CLUSTER0_MASK= 0x1, DRM_PLANE_TYPE_CLUSTER1_MASK= 0x2, DRM_PLANE_TYPE_CLUSTER2_MASK= 0x40, DRM_PLANE_TYPE_CLUSTER3_MASK= 0x80, DRM_PLANE_TYPE_CLUSTER_MASK = 0xff, // Esmart mask DRM_PLANE_TYPE_ESMART0_MASK = 0x04, DRM_PLANE_TYPE_ESMART1_MASK = 0x08, DRM_PLANE_TYPE_ESMART2_MASK = 0x100, DRM_PLANE_TYPE_ESMART3_MASK = 0x200, DRM_PLANE_TYPE_ESMART_MASK = 0xffff00, DRM_PLANE_TYPE_Unknown = 0xffffffff, };
对于plane的枚举,我们将其放在bit10-bit21,对于planemask的枚举同内核设置一致,则bit0-bit9。这样子两个位&时不会出现逻辑异常。则如果设置planemask为cluster0,则plane可以选择cluster0_win0和cluster_win1。从而代码能够正常的RemoveUnusePlane和FindPlaneAndApply。
修改后,现象如下:
# cat /sys/kernel/debug/dri/0/framebuffer framebuffer[236]: allocated by = IPC_1_614 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 framebuffer[228]: allocated by = IPC_1_614 refcount=1 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 framebuffer[233]: allocated by = [fbcon] refcount=1 format=XR24 little-endian (0x34325258) modifier=0x0 size=1920x1200 layers: size[0]=1920x1200 pitch[0]=7680 offset[0]=0 obj[0]: name=0 refcount=1 start=00000000 size=9216000 imported=no
这下图层正常了。问题解决
关于rk3568的图层设置,这边review了一下openharmony的代码,发现其仍有出入,这里文档纠正rk3568图层设置的出入。
关于图层的修改可以参考在rk3568上的修改,参考:Openharmony RK3588图层设置
图层的修改相关文件如下
修改: drm_crtc.cpp 修改: drm_plane.cpp 修改: drm_plane.h
我们可以知道,内核设置planemask如下:
所以对于openharmony而言,我们需要设置与内核移植,应该如下:
修改的diff如下:
diff --git a/rk3568/hardware/display/src/display_device/drm_crtc.cpp b/rk3568/hardware/display/src/display_device/drm_crtc.cpp index fd41973..73bd856 100755 --- a/rk3568/hardware/display/src/display_device/drm_crtc.cpp +++ b/rk3568/hardware/display/src/display_device/drm_crtc.cpp @@ -27,6 +27,10 @@ struct PlaneMaskName planeMaskNames[] = { { DrmPlaneType::DRM_PLANE_TYPE_ESMART1_MASK, "Esmart1" }, { DrmPlaneType::DRM_PLANE_TYPE_SMART0_MASK, "Smart0" }, { DrmPlaneType::DRM_PLANE_TYPE_SMART1_MASK, "Smart1" }, + { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER2_MASK, "Cluster2" }, + { DrmPlaneType::DRM_PLANE_TYPE_CLUSTER3_MASK, "Cluster3" }, + { DrmPlaneType::DRM_PLANE_TYPE_ESMART2_MASK, "Esmart2" }, + { DrmPlaneType::DRM_PLANE_TYPE_ESMART3_MASK, "Esmart3" }, { DrmPlaneType::DRM_PLANE_TYPE_Unknown, "unknown" }, };
我们可以知道,内核设置plane的类型如下:
对于的phy_id如下
而openharmony设置了多余的没有意义的planetyle,我们应该去除无意义的type,结果如下
对于openharmony来说,通过FindPlaneAndApply来找到plane来实现枚举,FindPlaneAndApply代码如下
int32_t HdiDrmComposition::FindPlaneAndApply(drmModeAtomicReqPtr pset) { int32_t ret = 0; for (uint32_t i = 0; i < mCompLayers.size(); i++) { HdiDrmLayer *layer = static_cast<HdiDrmLayer *>(mCompLayers[i]); HdiLayer *hlayer = mCompLayers[i]; for (uint32_t j = 0; j < mPlanes.size(); j++) { auto &drmPlane = mPlanes[j]; if (drmPlane->GetPipe() != 0 && drmPlane->GetPipe() != (1 << mCrtc->GetPipe())) { DISPLAY_LOGD("plane %{public}d used pipe %{public}d crtc pipe %{public}d", drmPlane->GetId(), drmPlane->GetPipe(), mCrtc->GetPipe()); continue; } /* Check whether the plane belond to the crtc */ if (!(static_cast<int>(drmPlane->GetWinType()) & mCrtc->GetPlaneMask())) { continue; } DISPLAY_LOGD("use plane %{public}d WinType %{public}x crtc %{public}d PlaneMask %{public}x", drmPlane->GetId(), drmPlane->GetWinType(), mCrtc->GetId(), mCrtc->GetPlaneMask()); if (drmPlane->GetCrtcId() == mCrtc->GetId() || drmPlane->GetCrtcId() == 0) { ret = ApplyPlane(*layer, *hlayer, *drmPlane, pset); if (ret != DISPLAY_SUCCESS) { DISPLAY_LOGE("apply plane failed"); break; } /* mark the plane is used by crtc */ drmPlane->BindToPipe(1 << mCrtc->GetPipe()); break; } } } return DISPLAY_SUCCESS; }
这时候,我们需要留意如下:
/* Check whether the plane belond to the crtc */ if (!(static_cast<int>(drmPlane->GetWinType()) & mCrtc->GetPlaneMask())) { continue; }
这里原因是此判断导致无法找到正确的plane,从而不会applyplane
所以我们正确的代码应该如下;
enum class DrmPlaneType { DRM_PLANE_TYPE_CLUSTER0_WIN0 = 1 << 10 | 0x1, DRM_PLANE_TYPE_CLUSTER0_WIN1 = 1 << 11 | 0x1, DRM_PLANE_TYPE_CLUSTER1_WIN0 = 1 << 12 | 0x2, DRM_PLANE_TYPE_CLUSTER1_WIN1 = 1 << 13 | 0x2, DRM_PLANE_TYPE_ESMART0_WIN0 = 1 << 14 | 0x4, DRM_PLANE_TYPE_ESMART1_WIN0 = 1 << 15 | 0x8, DRM_PLANE_TYPE_SMART0_WIN0 = 1 << 16 | 0x10, DRM_PLANE_TYPE_SMART1_WIN0 = 1 << 17 | 0x20, DRM_PLANE_TYPE_CLUSTER0_MASK = 0x1, DRM_PLANE_TYPE_CLUSTER1_MASK = 0x2, DRM_PLANE_TYPE_CLUSTER2_MASK = 0x40, DRM_PLANE_TYPE_CLUSTER3_MASK = 0x80, DRM_PLANE_TYPE_CLUSTER_MASK = 0xc3, DRM_PLANE_TYPE_ESMART0_MASK = 0x4, DRM_PLANE_TYPE_ESMART1_MASK = 0x8, DRM_PLANE_TYPE_ESMART2_MASK = 0x100, DRM_PLANE_TYPE_ESMART3_MASK = 0x200, DRM_PLANE_TYPE_ESMART_MASK = 0x30c, DRM_PLANE_TYPE_SMART0_MASK = 0x10, DRM_PLANE_TYPE_SMART1_MASK = 0x20, DRM_PLANE_TYPE_SMART_MASK = 0x30, DRM_PLANE_TYPE_Unknown = 0xffffffff, };
这样planetype和planemask能够正常的通过位与判断了。
修改的diff如下:
diff --git a/rk3568/hardware/display/src/display_device/drm_plane.h b/rk3568/hardware/display/src/display_device/drm_plane.h index d0bd9c9..47a7b8c 100644 --- a/rk3568/hardware/display/src/display_device/drm_plane.h +++ b/rk3568/hardware/display/src/display_device/drm_plane.h @@ -34,39 +34,36 @@ enum class DrmPropertyType { }; enum class DrmPlaneType { - DRM_PLANE_TYPE_CLUSTER0_WIN0 = 1 << 0, - DRM_PLANE_TYPE_CLUSTER0_WIN1 = 1 << 1, - - DRM_PLANE_TYPE_CLUSTER1_WIN0 = 1 << 2, - DRM_PLANE_TYPE_CLUSTER1_WIN1 = 1 << 3, - - DRM_PLANE_TYPE_ESMART0_WIN0 = 1 << 4, - DRM_PLANE_TYPE_ESMART0_WIN1 = 1 << 5, - DRM_PLANE_TYPE_ESMART0_WIN2 = 1 << 6, - DRM_PLANE_TYPE_ESMART0_WIN3 = 1 << 7, - - DRM_PLANE_TYPE_ESMART1_WIN0 = 1 << 8, - DRM_PLANE_TYPE_ESMART1_WIN1 = 1 << 9, - DRM_PLANE_TYPE_ESMART1_WIN2 = 1 << 10, - DRM_PLANE_TYPE_ESMART1_WIN3 = 1 << 11, - - DRM_PLANE_TYPE_SMART0_WIN0 = 1 << 12, - DRM_PLANE_TYPE_SMART0_WIN1 = 1 << 13, - DRM_PLANE_TYPE_SMART0_WIN2 = 1 << 14, - DRM_PLANE_TYPE_SMART0_WIN3 = 1 << 15, - - DRM_PLANE_TYPE_SMART1_WIN0 = 1 << 16, - DRM_PLANE_TYPE_SMART1_WIN1 = 1 << 17, - DRM_PLANE_TYPE_SMART1_WIN2 = 1 << 18, - DRM_PLANE_TYPE_SMART1_WIN3 = 1 << 19, - - DRM_PLANE_TYPE_CLUSTER0_MASK = 0x3, - DRM_PLANE_TYPE_CLUSTER1_MASK = 0xc, - DRM_PLANE_TYPE_CLUSTER_MASK = 0xf, - DRM_PLANE_TYPE_ESMART0_MASK = 0xf0, - DRM_PLANE_TYPE_ESMART1_MASK = 0xf00, - DRM_PLANE_TYPE_SMART0_MASK = 0xf000, - DRM_PLANE_TYPE_SMART1_MASK = 0xf0000, + DRM_PLANE_TYPE_CLUSTER0_WIN0 = 1 << 10 | 0x1, + DRM_PLANE_TYPE_CLUSTER0_WIN1 = 1 << 11 | 0x1, + + DRM_PLANE_TYPE_CLUSTER1_WIN0 = 1 << 12 | 0x2, + DRM_PLANE_TYPE_CLUSTER1_WIN1 = 1 << 13 | 0x2, + + DRM_PLANE_TYPE_ESMART0_WIN0 = 1 << 14 | 0x4, + + DRM_PLANE_TYPE_ESMART1_WIN0 = 1 << 15 | 0x8, + + DRM_PLANE_TYPE_SMART0_WIN0 = 1 << 16 | 0x10, + + DRM_PLANE_TYPE_SMART1_WIN0 = 1 << 17 | 0x20, + + DRM_PLANE_TYPE_CLUSTER0_MASK = 0x1, + DRM_PLANE_TYPE_CLUSTER1_MASK = 0x2, + DRM_PLANE_TYPE_CLUSTER2_MASK = 0x40, + DRM_PLANE_TYPE_CLUSTER3_MASK = 0x80, + DRM_PLANE_TYPE_CLUSTER_MASK = 0xc3, + + DRM_PLANE_TYPE_ESMART0_MASK = 0x4, + DRM_PLANE_TYPE_ESMART1_MASK = 0x8, + DRM_PLANE_TYPE_ESMART2_MASK = 0x100, + DRM_PLANE_TYPE_ESMART3_MASK = 0x200, + DRM_PLANE_TYPE_ESMART_MASK = 0x30c, + + DRM_PLANE_TYPE_SMART0_MASK = 0x10, + DRM_PLANE_TYPE_SMART1_MASK = 0x20, + DRM_PLANE_TYPE_SMART_MASK = 0x30, + DRM_PLANE_TYPE_Unknown = 0xffffffff, };
至此,rk3568上的display的plane设置已经完成正确了。
为了vp0正常能够设置planemask和planetype,需要在设备树显式声明plane如下:
代码如下:
&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 5.10内核的时候,开机有概率卡死,这里定位了和hdmi的irq有关系。这里给出解决办法
此问题是开机时直接卡死
问题日志如下:
[ 61.871358] rcu: INFO: rcu_sched detected stalls on CPUs/tasks: [ 61.871405] rcu: 0-...0: (6 ticks this GP) idle=3ea/1/0x4000000000000000 softirq=65/67 fqs=6000 [ 61.871426] (detected by 2, t=18002 jiffies, g=-1099, q=60) [ 61.871441] Task dump for CPU 0: [ 61.871456] task:irq/46-fe0a0000 state:R running task stack: 0 pid: 157 ppid: 2 flags:0x0000002a [ 61.871485] Call trace: [ 61.871511] __switch_to+0x138/0x164 [ 61.871534] schedule+0x50/0xb0
通过上面可以发现,这里出问题的是中断46号导致了cpu0出现了rcu 超时检测,所以定位此问题肯定是hdmi的irq导致的
根据此时的日志分析,我们可以知道,正常情况下hdmi的中断通常不可能会出现无法响应,所以我们查看中断内做的事情如下:
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); if (intr_stat) { hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); return IRQ_WAKE_THREAD; } hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); if (hdcp_stat) { hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); return IRQ_WAKE_THREAD; }
这里以write举例,其实现如下:
static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) { regmap_write(hdmi->regm, offset << hdmi->reg_shift, val); }
这里的regmap的实际为如下:
hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config); iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
所以可以知道,这里直接访问的是hdmi的寄存器地址。
根据上面的分析,我们可以知道的结论是hdmi的寄存器访问hung住了,这通常是hdmi的phy没有良好的初始化导致的,但是我们可以知道,这是在系统启动过程,这也就意味着,此时的hdmi的phy的probe有概率存在异常,导致hdmi产生的中断信号,但是mmio的地址无法正常的读写。
说清楚点,也就是,openharmony的hdmi的bridge驱动适配的不是很好,导致hdmi概率启动失败。
关于这一点问题,我们在4.19内核上,每次开机过程中的irq都是能正常的读写mmio映射的地址,这从而佐证了openharmony的内核的hdmi驱动存在问题
根据上面的分析,我们两种思路解决:
此方案需要重构dw_hdmi.c的所有相关代码,目前来看设计工作量较大。花费时间过多,目前本人没精力处理。
根据3.1的结论,我们如果正向解决openharmony的内核问题,花费时间较多,所以为了赶项目计划节点,规避方法也是可行的。
首先,我们知道hdmi再接收到中断的时候,openharmony的内核的mmio地址是无法访问的
其次,我们知道hdmi的结构体有变量bridge_is_on,如下定义
bool bridge_is_on; /* indicates the bridge is on */
根据上面可以知道,bridge power on的时候这个变量起来。所以我们可以在中断中防御式的编程,如果hdmi没有power on,我们不处理中断。
并不是所有情况可以规避,这里能够规避的前提条件是这里的中断是需要写清零的,如下:
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
这也就意味着,如果我没有处理,也就不会写清零,顶多导致我中断延迟处理了而已。
针对此问题,我们利用hdmi本身是否bridge_is_on的标志位来过滤中断的入口函数,从而做到规避hdmi异常中断的问题,如下
diff --git a/dw-hdmi.c b/dw-hdmi.c index 1b25fdd..e4e13b0 100644 --- a/dw-hdmi.c +++ b/dw-hdmi.c @@ -3763,6 +3763,10 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) u8 intr_stat, hdcp_stat; irqreturn_t ret = IRQ_NONE; + if (!hdmi->bridge_is_on){ + return ret; + } + if (hdmi->i2c) ret = dw_hdmi_i2c_irq(hdmi); @@ -3816,6 +3820,11 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat, hdcp_stat; enum drm_connector_status status = connector_status_unknown; + if (!hdmi->bridge_is_on){ + printk("kylin: dw_hdmi not power on\n"); + return IRQ_HANDLED; + } + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0);
openharmony的hdi层有一个测试程序,可以在系统没有起来的时候,测试使用hdi接口是否能够正常调用屏幕并让其正常显示,这个测试程序是hello_composer,本文章给出操作方法,将hello_composer编译出来,安装进操作系统来测试
openharmony提供了rosen图形框架,框架的实现如下图所示
在SDK的目录foundation/graphic/graphic_2d/rosen/samples/composer上,存在hello_compose测试程序,可以将其编译出来测试rosen图形框架是否正常运行。从而排除display栈的相关问题。主要操作如下
对于BUILD.gn,需要将hello_composer的编译依赖配置更新,如下是补丁
diff --git a/rosen/samples/composer/BUILD.gn b/rosen/samples/composer/BUILD.gn index df741b676..4d7d59a1d 100644 --- a/rosen/samples/composer/BUILD.gn +++ b/rosen/samples/composer/BUILD.gn @@ -20,6 +20,7 @@ config("hello_composer_config") { cflags = [ "-Wall", "-Werror", + "-Wno-unused-but-set-variable", "-g3", ] } @@ -55,6 +56,7 @@ ohos_executable("hello_composer") { ] external_deps = [ + "c_utils:utils", "eventhandler:libeventhandler", "hilog:libhilog", ]
hello_composer.cpp源码在openharmony4.0上存在接口异常,需要修改,如下是补丁
diff --git a/rosen/samples/composer/hello_composer.cpp b/rosen/samples/composer/hello_composer.cpp index a907af7db..c64274f1f 100644 --- a/rosen/samples/composer/hello_composer.cpp +++ b/rosen/samples/composer/hello_composer.cpp @@ -74,7 +74,7 @@ void HelloComposer::Run(const std::vector<std::string> &runArgs) sleep(1); std::shared_ptr<OHOS::AppExecFwk::EventRunner> runner = OHOS::AppExecFwk::EventRunner::Create(false); mainThreadHandler_ = std::make_shared<OHOS::AppExecFwk::EventHandler>(runner); - g_receiver = new VSyncReceiver(vsyncConnection, mainThreadHandler_); + g_receiver = new VSyncReceiver(vsyncConnection, nullptr, mainThreadHandler_); g_receiver->Init(); mainThreadHandler_->PostTask(std::bind(&HelloComposer::RequestSync, this)); runner->Run();
添加hello_composer的编译配置
diff --git a/bundle.json b/bundle.json index f82b846d0..6c1ae5eef 100755 --- a/bundle.json +++ b/bundle.json @@ -96,6 +96,7 @@ "//foundation/graphic/graphic_2d/rosen/modules/composer:libcomposer", "//foundation/graphic/graphic_2d/rosen/modules/composer/native_vsync:libnative_vsync", "//foundation/graphic/graphic_2d/rosen/modules/2d_graphics:2d_graphics", + "//foundation/graphic/graphic_2d/rosen/samples/composer:hello_composer", "//foundation/graphic/graphic_2d/rosen/modules/effect/effectChain:libeffectchain", "//foundation/graphic/graphic_2d/rosen/modules/effect/color_picker:color_picker", "//foundation/graphic/graphic_2d/rosen/modules/effect/skia_effectChain:skeffectchain",
对于hello_composer可以第一次修改需要更新build.args,所以需要如下命令编译
./build.sh --product-name dayu210 --ccache -T hello_composer
如果第一次编译通过之后,可以通过--fast-rebuild跳过gn阶段,从而提升编译速度
./build.sh --product-name dayu210 --ccache -T hello_composer --fast-rebuild
当然,也可以通过ninja直接编译,如下命令
prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3588/ hello_composer
编译通过之后,二进制存放在如下位置
out/rk3588/graphic/graphic_2d/hello_composer
对于二进制,我们可以两种方法进行推送到机器测试,一个是利用hdc,一个是打包到vendor,如下
第一个方法是使用hdc命令,则如下
hdc -t 9b01005932503033320045da20422900 file send hello_composer /data/
这里connectkey是自己的设备的key,可以如下查询得知
hdc list targets
第二个方法是打包到vendor分区,如下
mount vendor.img vendor cp out/rk3588/graphic/graphic_2d/hello_composer vendor/bin/ umount vendor
此时烧录vendor.img即可
如下运行即可测试hdi是否正常
./hello_composer
如果正常,则出现如下图片
OpenHarmony图形HDI基础适配及点屏:
https://forums.openharmony.cn/forum.php?mod=viewthread&tid=807
解决hello_composer编译失败问题:
https://gitee.com/openharmony/graphic_graphic_2d/pulls/7838/files