编辑
2025-01-22
记录知识
0

关于rk3568的图层设置,这边review了一下openharmony的代码,发现其仍有出入,这里文档纠正rk3568图层设置的出入。

一、参考文档

关于图层的修改可以参考在rk3568上的修改,参考:Openharmony RK3588图层设置

二、相关文件

图层的修改相关文件如下

修改: drm_crtc.cpp 修改: drm_plane.cpp 修改: drm_plane.h

三、主要问题

3.1 planeMaskNames与内核设置不匹配

我们可以知道,内核设置planemask如下:

image.png 所以对于openharmony而言,我们需要设置与内核移植,应该如下:

image.png 修改的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" }, };

3.2 多余的planetype

我们可以知道,内核设置plane的类型如下:

image.png 对于的phy_id如下

image.png 而openharmony设置了多余的没有意义的planetyle,我们应该去除无意义的type,结果如下

image.png

3.3 错误的枚举

对于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的plane

为了vp0正常能够设置planemask和planetype,需要在设备树显式声明plane如下: image.png

代码如下:

&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>; };
编辑
2025-01-22
记录知识
0

我们在使用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的寄存器地址。

2.1 hdmi的寄存器无法访问

根据上面的分析,我们可以知道的结论是hdmi的寄存器访问hung住了,这通常是hdmi的phy没有良好的初始化导致的,但是我们可以知道,这是在系统启动过程,这也就意味着,此时的hdmi的phy的probe有概率存在异常,导致hdmi产生的中断信号,但是mmio的地址无法正常的读写。

说清楚点,也就是,openharmony的hdmi的bridge驱动适配的不是很好,导致hdmi概率启动失败。

关于这一点问题,我们在4.19内核上,每次开机过程中的irq都是能正常的读写mmio映射的地址,这从而佐证了openharmony的内核的hdmi驱动存在问题

三、解决

根据上面的分析,我们两种思路解决:

3.1 正向解决,修改hdmi的驱动

此方案需要重构dw_hdmi.c的所有相关代码,目前来看设计工作量较大。花费时间过多,目前本人没精力处理。

3.2 规避方法,hdmi完全启动时才处理中断

根据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,我们不处理中断。

3.3 为什么可以规避

并不是所有情况可以规避,这里能够规避的前提条件是这里的中断是需要写清零的,如下:

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);
编辑
2025-01-22
记录知识
0

openharmony的hdi层有一个测试程序,可以在系统没有起来的时候,测试使用hdi接口是否能够正常调用屏幕并让其正常显示,这个测试程序是hello_composer,本文章给出操作方法,将hello_composer编译出来,安装进操作系统来测试

一、Openharmony图形栈

openharmony提供了rosen图形框架,框架的实现如下图所示

image.png 在SDK的目录foundation/graphic/graphic_2d/rosen/samples/composer上,存在hello_compose测试程序,可以将其编译出来测试rosen图形框架是否正常运行。从而排除display栈的相关问题。主要操作如下

二、代码修改

2.1 BUILD.gn

对于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", ]

2.2 hello_composer.cpp

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();

2.3 bundle.json

添加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

如果正常,则出现如下图片

image.png

五、参考链接

OpenHarmony图形HDI基础适配及点屏:

https://forums.openharmony.cn/forum.php?mod=viewthread&tid=807

解决hello_composer编译失败问题:

https://gitee.com/openharmony/graphic_graphic_2d/pulls/7838/files

编辑
2025-01-20
记录知识
0

我们在开发openharmony的时候,hcs的修改后编译内核,经常发现hcs不生效,本文基于解析hcs的编译逻辑确定问题,从而得出正确编译hcs的方法。从某种意义上这是其实是openharmony的本身bug。

一、hcs介绍

参考链接如下:

OpenHarmony-HDF驱动框架介绍及加载过程

https://forums.openharmony.cn/forum.php?mod=viewthread&tid=766

OHOS HDF 图谱-1-驱动配置信息树状图

https://ost.51cto.com/posts/12284

从上述文章我们可以知道

1.1 类似于dts

hcs模仿了dts的基本功能,对openharmony的设备驱动进行了抽象,简单的hcs配置如下:

display :: host { hostName = "display_host"; device_hdf_drm_panel :: device { device0 :: deviceNode { policy = 0; priority = 197; preload = 0; moduleName = "HDF_DRM_PANEL_SIMPLE"; } } }

这里描述了display模块,其对应的某个驱动为HDF_DRM_PANEL_SIMPLE

1.2 类似Linux总线设备驱动框架

我们知道,在标准linux内核中,设备树很好的融入了总线设备驱动框架,对于设备和驱动,我们只需要在驱动写好我们的注册结构体即可,示例如下:

static struct mipi_dsi_driver panel_simple_dsi_driver = { .driver = { .name = "panel-simple-dsi", .of_match_table = dsi_of_match, }, .probe = panel_simple_dsi_probe, .remove = panel_simple_dsi_remove, .shutdown = panel_simple_dsi_shutdown, };

hcs模仿了linux的做法,所以我们可以对应的做如下的结构体的注册,示例如下:

struct HdfDriverEntry g_hdfDrmPanelSimpleEntry = { .moduleVersion = 1, .moduleName = "HDF_DRM_PANEL_SIMPLE", .Init = HdfDrmPanelSimpleEntryInit, };

这里注意的是,dts里面设备和驱动匹配成功了调用的是probe回调,而openharmony的hcs注册成功了调用的是Init回调

1.3 hcb的存在

对于dts,我们可以知道通过dtc命令可以汇编和反汇编dts和dtb,这样对我们调试dts来说十分方便,同样的openharmony也借用了这样的概念。

openharmony通过hc-gen命令可以对hcs进行汇编和反汇编,示例如下

编译hcs:

$sdk/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -b hdf.hcs -o hdf.hcb

这里将hdf.hcs编译成了hdf.hcb

反编译hdf_hcs.hcb:

$sdk/rk3568/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -d hdf_hcs.hcb -o hdf.hcs 这里将hdf_hcs.hcb反汇编成了hdf.hcs。这里实际会输出hdf.d.hcs

1.4 解析hcb

我们知道设备树是通过标准的libfdt库来解析dtb的,类似的,我们可以知道hcb是通过华为开发的hcs parser库来解析,hcs parser库位置如下:

drivers/hdf_core/framework/utils/src/hcs_parser/

解析流程图如下

image.png

二、hcs的内核编译过程

为了分析hcs的解析过程,我对kernel的make过程进行了分析,如下:

2.1 hdf的Makefile

内核为了能够正常引入hcs,在hdf框架的Makefile有如下代码

HCS_DIR := ../../../../../$(PRODUCT_PATH)/hdf_config/khdf ifeq ($(wildcard $(CURRENT_DIR)/$(HCS_DIR)),) HCS_DIR := ../../../../../$(PRODUCT_PATH)/hdf_config endif ifeq ($(CONFIG_DRIVERS_HDF), y) ifeq ($(wildcard $(CURRENT_DIR)/$(HCS_DIR)),) HCS_ABS_DIR := $(abspath $(CURRENT_DIR)/$(HCS_DIR)) $(error miss hcs config in $(HCS_ABS_DIR) for small system\ or $(HCS_ABS_DIR)/khdf for standrad system) endif ifeq ($(CONFIG_DRIVERS_HDF_TEST), y) obj-$(CONFIG_DRIVERS_HDF_TEST) += test/ obj-$(CONFIG_DRIVERS_HDF_TEST) += $(HCS_DIR)/hdf_test/ $(warning HCS_DIR=$(HCS_DIR)) else obj-$(CONFIG_DRIVERS_HDF) += $(HCS_DIR)/ $(warning HCS_DIR=$(HCS_DIR)) endif endif

如上我们可以知道,内核编译hcs取决于两个配置:CONFIG_DRIVERS_HDF和CONFIG_DRIVERS_HDF_TEST

于是我们查看defconfig如下

arch/arm64/configs/rockchip_linux_defconfig

可以知道上面两个配置均打开

所以得出结论是:我们hcs的编译目录为:

obj-$(CONFIG_DRIVERS_HDF_TEST) += $(HCS_DIR)/hdf_test/

这个目录的路径如下:

vendor/hihope/rk3568/hdf_config/khdf/hdf_test/

2.2 hcs的Makefile

根据hdf的Makefile我们可以知道真正起作用的hcs路径,现在开始分析hcs的Makefile核心代码如下:

$(obj)/$(HCS_OBJ): $(CONFIG_GEN_HEX_SRC) $(Q)$(CC) $(c_flags) -c -o $@ $< $(Q)rm -f $< $(CONFIG_GEN_HEX_SRC): $(LOCAL_HCS_ROOT)/%_hcs_hex.c: $(HCS_DIR)/%.hcs | $(HC_GEN) $(Q)echo gen hdf built-in config $(Q)if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi $(Q)$(HC_GEN) $(HCB_FLAGS) -o $(subst _hex.c,,$(@)) $< $(CONFIG_GEN_SRCS): $(CONFIG_OUT_DIR)%.c: $(HCS_DIR)/%.hcs | $(HC_GEN) $(Q)echo gen hdf driver config $(Q)if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi $(Q)$(HC_GEN) -t -o $@ $< $(HC_GEN): $(HIDE)make -C $(HC_GEN_DIR) BUILD_DIR=$(dir $@) $(obj)/$(HCS_MACRO_OBJ): $(HCS_MACRO_SRC) $(HCS_DEP) $(Q)$(CC) $(c_flags) -c -o $@ $< $(HCS_DEP): $(HC_GEN) $(Q)echo gen hdf built-in config macro $(Q)$(HC_GEN) -m -o $(HCS_MACRO_GEN_FILE) $(HCS_FILE) obj-$(CONFIG_DRIVERS_HDF) += $(HCS_OBJ) \ $(HCS_MACRO_OBJ)

我们可以知道,如果CONFIG_DRIVERS_HDF置位,则会编译HCS_OBJ和HCS_MACRO_OBJ

这里的核心代码如下:

$(Q)$(HC_GEN) $(HCB_FLAGS) -o $(subst _hex.c,,$(@)) $< $(Q)$(CC) $(c_flags) -c -o $@ $<

我把它做一下变量扩展,易于理解如下:

drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -b -i -a -o vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf.hcs cc -c -o /hdf_hcs_hex.o /root/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.c

这里我们就可以发现,实际上内核在编译的时候,通过linux的hdf驱动的Makefile内迁到hcs的Makefile上编译hdf.hcs为hdf_hcs_hex.c,然后将hdf_hcs_hex.c编译成hdf_hcs_hex.o,然后合并到Image内使用。

2.3 built-in.a

根据上面的分析,我们完全可以清楚的知道,内核是将其作为o插入的Image内,这时候我们直接找built-in.a即可找到实际的文件路径,如下

../../OBJ/linux-5.10/drivers/hdf/built-in.a

这时候我们找到文件应该如下:

# cat ../../OBJ/linux-5.10/drivers/hdf/built-in.a | grep hdf_hcs_hex.o ../../../../vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o/

此时我们将相对路径转换成绝对路径,所以如下:

out/kernel/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o

此时,我们完全正确的找到了hcs的编译成果

三、hcs的简要解析

上面介绍了hcs,也说明了hcs的最终编译成果,这里简单说明一下hcs在openharmony是如何修改的

我们知道openharmony使用的hcs应该如下:

vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf.hcs

内容如下:

#include "../hdf.hcs" #include "hdf_config_test.hcs" #include "hdf_test_manager/device_info.hcs" #include "adc_test_config.hcs" #include "gpio_test_config.hcs" #include "i2c_test_config.hcs" #include "pwm_test_config.hcs" #include "spi_test_config.hcs" #include "sdio_test_config.hcs" #include "emmc_test_config.hcs" #include "uart_test_config.hcs" #include "rtc_test_config.hcs" #include "watchdog_test_config.hcs" root { module = "hisilicon,hi35xx_chip"; }

上面可以知道, 其还是包含了上一层的hdf.hcs("../hdf.hcs")

这时候我们找上层的hdf.hcs如下:

#include "device_info/device_info.hcs" #include "platform/adc_config_linux.hcs" #include "platform/pwm_config.hcs" #include "platform/rk3568_watchdog_config.hcs" #include "platform/rk3568_uart_config.hcs" #include "platform/sdio_config.hcs" #include "platform/emmc_config.hcs" #include "platform/rk3568_spi_config.hcs" #include "input/input_config.hcs" #include "wifi/wlan_platform.hcs" #include "wifi/wlan_chip_ap6275s.hcs" #include "camera/camera_config.hcs" #include "sensor/sensor_config.hcs" #include "audio/audio_config.hcs" #include "audio/codec_config.hcs" #include "audio/dai_config.hcs" #include "audio/dma_config.hcs" #include "audio/dsp_config.hcs" #include "audio/analog_headset_config.hcs" #include "light/light_config.hcs" #include "vibrator/vibrator_config.hcs" #include "vibrator/linear_vibrator_config.hcs" #include "vibrator/drv2605l_linear_vibrator_config.hcs" #include "lcd/lcd_config.hcs" root { module = "rockchip,rk3568_chip"; }

此时我们知道设备的信息可以在:#include "device_info/device_info.hcs"

于是我们对设备驱动的修改可以在#include "device_info/device_info.hcs"中,修改即可生效

四、编译措施

回到我们的问题,正常情况下,我们通过单独编译或者全量编译的命令是没办法编译到hcs文件的,这是openharmony的bug。

正常的单独编译命令如下:

KBUILD_OUTPUT=../../OBJ/linux-5.10 PRODUCT_PATH=vendor/hihope/rk3568 DEVICE_COMPANY=rockchip DEVICE_NAME=rk3568 PRODUCT_COMPANY=hihope GPUDRIVER=mali ./make-ohos.sh TB-RK3568X0 enable_ramdisk

此时我们无法编译我们修改的hcs文件,我们需要将中间文件单独删除如下:

rm ../../vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o

这样我们就可以在编译内核的时候编译更新hcs的修改了。

五、常用问题排查

5.1 hcs修改了仍不生效

有同事根据上述方法修改了hcs还是无法生效,这里给出排除方法,帮助定位:

5.1.1 手动编译确定hcs编写无误

cd vendor/hihope/rk3568/hdf_config/khdf make

此时会在本地生成hcb文件,我们通常情况下编写的hcs文件带有字串标识符,所以此时使用grep可以判断是否已经包含,如下

grep -nr "HDF_DRMPANEL" device_info/device_info.hcs:244: moduleName = "HDF_DRMPANEL"; 匹配到二进制文件 hdf_test/hdf_hcs.hcb 匹配到二进制文件 hdf_hcs.hcb

如果进一步还想确认,可以使用上文的hc-gen命令,反编译后查看自己写的节点是否存在

$sdk/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -d hdf_hcs.hcb -o test.hcs

5.1.2 确定boot_linux.img是否包含字符串

上面5.1.1确定没问题了,基本就不会出问题, 但是为了给个说法,这里也可以通过grep确定boot_linux.img是否包含,如下

grep -nr "HDF_DRMPANEL" $sdk/out/rk3568/packages/phone/images/boot_linux.img grep: 警告: GREP_OPTIONS 已被废弃;请使用别名或脚本 匹配到二进制文件 /$sdk/out/rk3568/packages/phone/images/boot_linux.img

5.1.3 我不想确认,但是我就觉得没编进去怎么办

如果不想确认,建议在编译内核的过程中打印日志,如果有日志,代表编译是进去的,如果没有,没编译进去。方便懒人。如下:

还是hcs的编译目录:

vendor/hihope/rk3568/hdf_config/khdf/hdf_test

打开Makefile,添加如下

diff --git a/rk3568/hdf_config/khdf/hdf_test/Makefile b/rk3568/hdf_config/khdf/hdf_test/Makefile index 7b53c08..6781b0a 100755 --- a/rk3568/hdf_config/khdf/hdf_test/Makefile +++ b/rk3568/hdf_config/khdf/hdf_test/Makefile @@ -68,11 +68,12 @@ HCS_MACRO_GEN_FILE := $(HDF_FRAMWORK_TEST_OUT)/hdf_macro_test HCS_FILE := $(HCS_DIR)/hdf.hcs $(obj)/$(HCS_OBJ): $(CONFIG_GEN_HEX_SRC) + $(Q)echo 你看编没编进去: [$(CC) $(c_flags) -c -o $@ $<] make o $(Q)$(CC) $(c_flags) -c -o $@ $< $(Q)rm -f $<

此时你编译内核的时候就会打印。打印了就编译进去了,没打印就编译没进去,那你检查hcs是不是写错了。

编辑
2025-01-20
记录知识
0

在调试Openharmony的时候,发现开机logo的图层并没有消失,故分析drmModeSet的api,发现了一个关于atomic modestting相关的宏可以workaround解决此问题,但是后面找到根本原因了,故此修改无效,仅作知识了解储备,此文章总结一下这两个宏的区别和联系

一、drmModeAtomicCommit

关于drmModeAtomicCommit的函数,其来源于libdrm库,参考kwin的实现,可以查到具体描述如下:

参考如下

https://github.com/KDE/kwin/blob/master/src/backends/drm/overview.md?plain=1

image.png 可以知道,drmModeAtomicCommit会提交atomic property change给内核drm框架

对于DRM_MODE_ATOMIC_NONBLOCK,代表这次提交的值不会block

对于DRM_MODE_ATOMIC_ALLOW_MODESET,

其会提交所有modeset的改变,与DRM_MODE_ATOMIC_NONBLOCK不同的是,他会阻塞直到内核drm完成modeset的property完成改变

二、调用关系

drmModeAtomicCommit最终会调用到内核的ioctl判断,流程如下:

drmModeAtomicCommit----> DRM_IOCTL(fd, DRM_IOCTL_MODE_ATOMIC, &atomic);----> drm_ioctl---->DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATOMIC, drm_mode_atomic_ioctl, DRM_MASTER),--→drm_mode_atomic_ioctl

此时可以观察如下代码:

if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY) { ret = drm_atomic_check_only(state); } else if (arg->flags & DRM_MODE_ATOMIC_NONBLOCK) { ret = drm_atomic_nonblocking_commit(state); } else { if (drm_debug_enabled(DRM_UT_STATE)) drm_atomic_print_state(state); ret = drm_atomic_commit(state); }

这里可以发现,如果设置DRM_MODE_ATOMIC_NONBLOCK和不设置DRM_MODE_ATOMIC_NONBLOCK,代码调用函数不一样,如下:

int drm_atomic_nonblocking_commit(struct drm_atomic_state *state) { struct drm_mode_config *config = &state->dev->mode_config; int ret; ret = drm_atomic_check_only(state); if (ret) return ret; DRM_DEBUG_ATOMIC("committing %p nonblocking\n", state); return config->funcs->atomic_commit(state->dev, state, true); } int drm_atomic_commit(struct drm_atomic_state *state) { struct drm_mode_config *config = &state->dev->mode_config; int ret; ret = drm_atomic_check_only(state); if (ret) return ret; DRM_DEBUG_ATOMIC("committing %p\n", state); return config->funcs->atomic_commit(state->dev, state, false); }

对于回调函数atomic_commit,rockchip平台如下设置

static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { .fb_create = rockchip_fb_create, .output_poll_changed = rockchip_drm_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, };

所以我们重点留意drm_atomic_helper_commit内的判断如下:

if (!nonblock) { ret = drm_atomic_helper_wait_for_fences(dev, state, true); if (ret) goto err; } if (nonblock) queue_work(system_unbound_wq, &state->commit_work); else commit_tail(state);

这里可以发现,如果是不是nonblock commit,则先会drm_atomic_helper_wait_for_fences,这里会调用dma_fence_wait,用于睡眠等待fence(sleep until the fence gets signaled),如果fence信号触发,则直接提交到尾 commit_tail(state) 直接渲染

而如果是nonblock commit,则直接将这个事件通过queue_work发送到wq上,这个wq的函数如下:

static void commit_work(struct work_struct *work) { struct drm_atomic_state *state = container_of(work, struct drm_atomic_state, commit_work); commit_tail(state); }

这里可以看到如果wq工作时,才调用commit_tail提交到尾

三、Openharmony的使用

在Openharmony的代码中可以知道,只有在更新DrmMode的时候才执行阻塞提交DRM_MODE_ATOMIC_ALLOW_MODESET,而对于每一帧Composition的时候,只是DRM_MODE_ATOMIC_NONBLOCK非阻塞提交。鉴于此,我们可以在每一帧Composition执DRM_MODE_ATOMIC_ALLOW_MODESET,但根据上述代码分析,可能带来图形的性能损失,故不可行。

四、调试方向

根据如上,了解到了drm在atomic commit的时候,可以选择block和nonblock,如果我都设置block,则开机logo的问题确实解决了,鉴于分析代码的时候发现,此方法可能带来性能损失,故仍继续寻找根因。

出现开机后开机logo的图层没消失的主要原因是应该是openharmony图层设置存在问题,故应该从Plane查起。