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
这下图层正常了。问题解决