开发Openharmony的设备开发第一步就是移植内核,本文基于openharmony的sdk角度介绍openharmony的内核编译方式和单独编译内核的命令
这里内核有两个位置,一个是内核原始源码位置,这是oh标准内核,位置为/kernel/linux/linux-5.10/
在这个内核上,会添加如下几个依赖仓库的改动如下
"kernel/linux/build" # 编译相关脚本 "kernel/linux/linux-5.10" # 内核仓库 "kernel/linux/patches" # 内核相关补丁 "kernel/linux/config" # 内核的defconfig "kernel/linux/common_modules" # oh特性的内核驱动 "third_party/bounds_checking_function" # libboundscheck库,安全函数,带边界检查的标准函数 "device/soc/hisilicon/common/platform/wifi" # 海思wifi "third_party/FreeBSD/sys/dev/evdev" # evdev 头文件 "drivers/hdf_core" # hdf 核心驱动 "prebuilts/clang/ohos/linux-x86_64/llvm/bin" # llvm
这里值得注意的是,patch目录,它需要对内核合入hdf补丁,并且需要合入rk3568芯片的基础改动的patch,主要如下
└── rk3568_patch ├── kernel.patch # linux-5.10 rk3568 SOC patches └── hdf.patch # linux-5.10 rk3568 定制 HDF patches
我们通常做移植的后续工作是设配oh的硬件抽象层,主要如下
├── adapter ├── bundle.json ├── figures ├── framework ├── interfaces ├── LICENSE ├── OAT.xml ├── README.md └── README_zh.md
编译后的内核位置为/out/kernel/src_tmp/linux-5.10/
, 编译后内核的中间文件位置为/out/kernel/OBJ/linux-5.10/
此时代码是已经合入补丁和相关oh特性功能的代码。
编译内核可以分为最外层sdk模块编译,内核源码build_kernel.sh脚本编译(合并前),内核源码make-ohos.sh脚本编译(合并后),和内核分步骤编译,可以按需自行编译
对于内核而言,sdk模块编译目前默认不支持,但sdk外层编译有必要解释一下,所以编译内核的最外层sdk编译命令如下
./build.sh --product-name rk3568 --ccache
上述命令可以整体环境编译,故内核也编译了,但是缺点是编译一次时间太长。为了指定模块的单独编译,可以设置--build-target,如下是代码示例
group("make_images") group("eng_system_image") group("eng_chipset_image") group("chip_prod_image") group("sys_prod_image") group("system_image") group("userdata_image") group("vendor_image") group("ramdisk_image") group("updater_ramdisk_image") group("updater_image")
可以知道,通过如下参数指定编译
./build.sh --product-name rk3568 --ccache --build-target make_images ./build.sh --product-name rk3568 --ccache --build-target system_image ./build.sh --product-name rk3568 --ccache --build-target userdata_image
build-target还能通过指定gn路径+目标名指定模块编译,如下
./build.sh --product-name rk3568 --ccache --build-target commonlibrary/c_utils/base:util
这里commonlibrary/c_utils/base是BUILD.gn文件目录,util是BUILD.gn声明的模块名称。
虽然内核不能单独编译,但是执行全量编译只需要等待即可获得boot_linux.img文件
查看build-target支持的选项,如下命令
prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3568/ -t targets
对于查出来的选项,可以直接用ninja编译模块,以内核举例
prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3568/ kernel
如果使用hb命令,可以如下编译内核
hb build -T kernel
build_kernel.sh作为内核单模块BUILD.gn拉起的脚本,我们可以主动调用它来单独编译内核,但是前提是代码是全编过一次的,也就是out/rk3568目录存在。编译命令如下
`pwd`/../../device/board/hihope/rk3568/kernel/build_kernel.sh `pwd`/../../kernel/linux/linux-5.10 `pwd`/../../out/rk3568/packages/phone/images `pwd`/../../device/board/hihope/rk3568 vendor/hihope/rk3568 `pwd`/../../ rockchip rk3568 hihope root default disable_lto_O0 enable_ramdisk
这里解释如下
此命令需要绝对路径
build_kernel.sh需要11个形参分别如下 $1=../../kernel/linux/linux-5.10 # pushd 参数,切换目录到linux-5.10上 $2=out/rk3568/packages/phone/images # 镜像输出目录 $3=device/board/hihope/rk3568 # BUILD_SCRIPT_PATH变量 $4=vendor/hihope/rk3568 # PRODUCT_PATH变量 $5=`pwd` # ROOT_DIR变量 $6=rockchip # DEVICE_COMPANY变量 $7=rk3568 # DEVICE_NAME变量 $8=hihope # PRODUCT_COMPANY变量 $9=root # KERNEL_FORM变量 $10=default # KERNEL_PROD变量 $11=disable_lto_O0 # ENABLE_LTO_O0变量 $12=enable_ramdisk # 判断是否生成ramdisk
此命令如果是第一次运行,会自动打patch和复制oh特性驱动代码。如果不是第一次运行,则跳过不应用补丁,直接开始编译。此命令会主动拷贝文件到out/rk3568/packages/phone/images目录。
根据对build_kernel.sh的解析,为了在代码目录out/kernel/src_tmp/linux-5.10直接编译,可以直接如下命令编译
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
对于参数解析如下:
KBUILD_OUTPUT:指定内核编译中间文件位置 PRODUCT_PATH:指定产品路径,khdf需要使用 GPUDRIVER: 指定显卡驱动,瑞芯微支持mali和panfrost两种驱动 TB-RK3568X0:指定开发板类型,当前oh的sdk只支持TB-RK3568X0和TB-RK3568X10两款开发板,两个开发板对应的设备树不一样而已,其他没区别 enable_ramdisk:指定生成ramdisk镜像
通过上述命令可以在源码目录生成boot_linux目录和boot_linux.img,注意,此时的boot_linux.img不会主动复制到out/rk3568/packages/phone/images目录。
内核编译分三个步骤
编译defconfig 编译ko模块 编译image内核二进制
内核打包通过mke2fs打包ext2格式的镜像,通过make-boot.sh打包
涉及命令主要如下:
make KBUILD_OUTPUT=../../OBJ/linux-5.10 PRODUCT_PATH=vendor/hihope/rk3568 LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 rockchip_linux_defconfig make KBUILD_OUTPUT=../../OBJ/linux-5.10 PRODUCT_PATH=vendor/hihope/rk3568 LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 modules_prepare rm -rf ../../../kernel/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/ make KBUILD_OUTPUT=../../OBJ/linux-5.10 PRODUCT_PATH=vendor/hihope/rk3568 LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 rk3568-toybrick-x0-linux.img -j96
这时候内核已经编译成Image文件,需要手动cp一下
cp ../../OBJ/linux-5.10/arch/arm64/boot/Image boot_linux/extlinux/Image
然后运行make-boot.sh脚本打包boot_linux.img
./make-boot.sh ../../../
此时boot_linux.img文件存放在/rk3568/packages/phone/images/内
默认的hdf_test在单编译的时候会报错如下:
ld.lld: error: drivers/built-in.a(../../../vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o): Invalid value (Producer: 'LLVM15.0.4' Reader: 'LLVM 12.0.1')
上述日志说明hdf_hcs_hex.o和当前单编的llvm版本不一样,所以删除.o即可,如下操作
rm -rf ../../../kernel/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/
缺少工具是因为缺少包导致,错误信息如下
BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
安装dwarves包即可
apt install dwarves
如果编译环境出问题,如下命令清理一下
KBUILD_OUTPUT=../../OBJ/linux-5.10 make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 PRODUCT_PATH=vendor/hihope/rk3568 DEVICE_COMPANY=rockchip mrproper
如果是误操作在out/kernel/src_tmp/linux-5.10编译了中间文件,则需要在本目录操作mrproper,如下
make LLVM=1 LLVM_IAS=1 CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 PRODUCT_PATH=vendor/hihope/rk3568 DEVICE_COMPANY=rockchip mrproper
我们在开发openharmony的时候经常修改内核代码,而在openharmony内内核的编译目录在out临时目录下,所以我们应该遵循openharmony的规范提交内核,如下是具体情况
根据openharmony的仓库目录,我们可以知道,如果我们修改一个设备的配置,通常其目录在device/board/xxx下,例如hihope的设备,那么配置仓库为device_board_hihope
仓库下面的build_kernel.sh是编译内核的脚本,我们需要重点关注
根据分析build_kernel.sh脚本,我们可以知道如下:
对于openharmony内核而言,原始代码地址在
KERNEL_SOURCE=${ROOT_DIR}/kernel/linux/linux-5.10
对于厂商的补丁而言,其patch位置在
KERNEL_PATCH=${ROOT_DIR}/kernel/linux/patches/linux-5.10/rk3568_patch/kernel.patch
这是openharmony团队适配rk3568平台的补丁
对于内核,配置是多个config组合的,如下:
KERNEL_CONFIG_FILE=${ROOT_DIR}/device/board/hihope/rk3568/kernel/kernel_config/linux-5.10/rk3568_standard_defconfig
这里可以知道,openharmony的内核配置是rockchip的配置defconfig
根据二中的分析,我们可以知道,2.1不建议修改,因为是华为的内核源码,2.2也不建议修改,因为是openharmony适配rk3568的通用修改,2.3 是默认配置,不建议修改。我们如果需要修改,需要另加patch,如下方式
# rk628 驱动 KERNEL_RK628_PATCH=${ROOT_DIR}/device/board/hihope/rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/rk628.patch # allgo 设备树 KERNEL_DTS_MBRC3568A=${ROOT_DIR}/device/board/hihope/rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/allgo-mbrc3568a-dts.patch # make-ohos patch BUILD_MBRC3568_PATCH=${ROOT_DIR}/device/board/hihope/rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/build-mbrc3568a.patch # bugfix patch KERNEL_BUGFIX_ON_MBRC3568_PATH=${ROOT_DIR}/device/board/hihope/rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/mbrc3568a-bugfix.patch
根据这些定义,在后面需要合入补丁
# patching rk628 patch -p1 < ${KERNEL_RK628_PATCH} # patching dts patch -p1 < ${KERNEL_DTS_MBRC3568A} # patching build script patch -p1 < ${BUILD_MBRC3568_PATCH} # patching bugfix patch patch -p1 < ${KERNEL_BUGFIX_ON_MBRC3568_PATH}
这里KERNEL_RK628_PATCH是适配rk628驱动的单独补丁,KERNEL_DTS_MBRC3568A是适配众达机器的设备树,BUILD_MBRC3568_PATCH是合入openharmony编译的改动,KERNEL_BUGFIX_ON_MBRC3568_PATH是根据上述改动后,针对bugfix的改动补丁。
所以后续如果我们针对众达3568机器的补丁修复,可以持续更新补丁文件mbrc3568a-bugfix.patch即可。
根据上面提到的,我们需要更新补丁,这里提供补丁的更新方法如下:
假设我们基于声卡做了定制修改,我们需要进入声卡的驱动目录做如下工作:
cd sound/soc/codecs git init . git add . git commit -m 'initail code'
此时我们初始化了一个空的仓库
如果我们修改代码,直接vim修改即可,如下
vim es8323.c
修改后我们可以看到改动情况如下:
git status . 位于分支 master 尚未暂存以备提交的变更: (使用 "git add <文件>..." 更新要提交的内容) (使用 "git restore <文件>..." 丢弃工作区的改动) 修改: es8323.c
根据上面的修改代码,可以直接用git diff生成,如下:
git diff > support-sound-card.patch
此时我们将产生一个基于es8323改造的补丁,但是需要留意的是如下:
patch -p1 < ${KERNEL_BUGFIX_ON_MBRC3568_PATH}
这里合入补丁的方式是p1,所以我们需要整改一下这个patch,如下
%s/a\//a\/sound\/soc\/codecs\//g %s/b\//b\/sound\/soc\/codecs\//g
此时pathc的变化如下,先显示未修改之前的
diff --git a/es8323.c b/es8323.c index 6365108..5241703 100755 --- a/es8323.c +++ b/es8323.c
再通过替换符合p1路径的,也就是修改之后的,如下:
diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index 6365108..5241703 100755 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c
此时这个补丁基本可用了。我们需要将其合入mbrc3568a-bugfix.patch。
合入补丁的方式也很简单,通过追加的方式即可,如下:
cat support-sound-card.patch >> device/board/hihope/rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/mbrc3568a-bugfix.patch
此时我们在仓库device_board_hihope上做提交即可。
git add rk3568/kernel/kernel_patch/linux-5.10/allgo_patch/mbrc3568a-bugfix.patch git commit git push
Openharmony在使用hdmi显示时,发现接上鼠标之后,光标并不显示,于是开始定位,如下是相关分析解释
Openharmony4.0的rk3588系统上,插上hdmi后显示,鼠标不亮,不方便非触摸屏的设备进行开发使用。
根据openharmony代码熟悉,根据文章《Openharmony目录树》可以知道其可能涉及子系统代码位置在foundation/multimodalinput/位置,如下图
根据BUILD.gn可以发现mmi_tests工具可以测试光标事件,但是实际上我们存在光标事件,只是光标不显示,故此工具不适用此问题,只能从代码角度分析
根据sa配置可以知道,multimodalinput服务关键服务是libmmi-server.so,如下
# cat /system/profile/multimodalinput.json { "process": "multimodalinput", "systemability": [ { "name": 3101, "libpath": "libmmi-server.z.so", "run-on-create": true, "distributed": true, "dump_level": 1 } ]
根据service/BUILD.gn可以发现,与libmmi-server.so相关的代码如下
ohos_shared_library("libmmi-server") { sources = libmmi_service_sources ...... if (input_feature_mouse) { sources += [ "mouse_event_normalize/src/mouse_device_state.cpp", "mouse_event_normalize/src/mouse_event_normalize.cpp", "mouse_event_normalize/src/mouse_transform_processor.cpp", "touch_event_normalize/src/gesture_transform_processor.cpp", "touch_event_normalize/src/touchpad_transform_processor.cpp", ] if (input_feature_pointer_drawing) { sources += [ "window_manager/src/pointer_drawing_manager.cpp" ] } else { sources += [ "window_manager/src/i_pointer_drawing_manager.cpp" ] } } libmmi_service_sources = [ "$mmi_path/frameworks/proxy/event_handler/src/bytrace_adapter.cpp", "delegate_task/src/delegate_tasks.cpp", "device_config/src/device_config_file_parser.cpp", "device_manager/src/input_device_manager.cpp", "dfx/src/dfx_hisysevent.cpp", "display_state_manager/src/display_event_monitor.cpp", "event_dispatch/src/event_dispatch_handler.cpp", "event_dump/src/event_dump.cpp", "event_handler/src/anr_manager.cpp", "event_handler/src/event_normalize_handler.cpp", "event_handler/src/input_event_handler.cpp", "event_handler/src/key_auto_repeat.cpp", "event_handler/src/key_event_value_transformation.cpp", "event_handler/src/key_map_manager.cpp", "fingersense_wrapper/src/fingersense_wrapper.cpp", "libinput_adapter/src/hotplug_detector.cpp", "libinput_adapter/src/libinput_adapter.cpp", "message_handle/src/server_msg_handler.cpp", "module_loader/src/mmi_service.cpp", "module_loader/src/uds_server.cpp", "permission_helper/src/permission_helper.cpp", "timer_manager/src/timer_manager.cpp", "window_manager/src/input_display_bind_helper.cpp", "window_manager/src/input_windows_manager.cpp", ]
于是阅读代码和定位逻辑,可以发现输入设备的识别和管理在input_device_manager.cpp实现
foundation/multimodalinput/input/service/device_manager/src/input_device_manager.cpp
而光标图层管理的代码在input_windows_manager.cpp实现
foundation/multimodalinput/input/service/window_manager/src/input_windows_manager.cpp
实际的光标绘制的代码在pointer_drawing_manager.cpp实现
foundation/multimodalinput/input/service/window_manager/src/pointer_drawing_manager.cpp
相关问题定位清晰后,随针对此三块代码进行分析和调试。
此文件功能作为输入设备的识别,添加,其关键函数如下,详细代码实现不必列出:
GetInputDevice 实例化InputDevice,返回InputDevice对象 FillInputDevice 填充InputDevice对象的数据 OnInputDeviceAdded 通过libinput库获得设备信息,并分发光标绘制任务 ScanPointerDevice 遍历InputDevice实例,找到pointer设备 IsPointerDevice 如果libinput_device_get_tags存在EVDEV_UDEV_TAG_MOUSE,则是pointer设备
此文件功能作为输入设备的图层显示设置,窗口信息,光标事件。其关键函数如下
GetClientFd 根据pointer事件获取client的fd值,用作后续获取session的句柄 GetDisplayId 根据pointer事件获取displayid UpdateDisplayInfo 通过displayGroupInfo更新displayid和WindowInfo SendPointerEvent 通过session发送点击事件 DispatchPointer 通过session发送点击事件(存在上次点击事件的情况) GetPhysicalDisplayCoord 通过libinput获取触摸的坐标 SetPointerStyle 设置光标style GetPointerStyle 获取光标style SelectWindowInfo 根据点击事件获取windowid,从而获得WindowInfo UpdateMouseTarget 更新鼠标位置和显示 UpdateTargetPointer 根据pointer的类型选择更新对应事件位置和显示 UpdateAndAdjustMouseLocation 调整鼠标位置
此文件处理真正的光标绘制需求,根据光标的位置和style来执行绘制请求
DrawPointer 绘制光标 InitLayer 初始化光标层 CreatePointerWindow 创建surface节点用于绘制光标层 GetSurfaceBuffer 根据传入的surface节点,获取sufface buffer DrawPixelmap 根据icon使用canvas绘制bitmap SetMouseIcon 设置鼠标icon DecodeImageToPixelMap 将icon解码成pixelmap SetPointerColor 设置光标颜色 SetPointerSize 设置光标大小 UpdatePointerDevice 更新光标设备 DrawManager 绘制管理,根据是否是光标设备来判断是否绘制和销毁窗口 UpdatePointerVisible 更新光标是否可见 SetPointerStyle 设置光标style DrawPointerStyle 绘制光标style
在完成上述1.判断问题相关代码,2.熟悉代码流程之后,可以开始进行定位实际问题,主要策略如下
根据解析BUILD.gn可以知道,此模块可以单独编译,如下是单独编译命令
prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3588/ libmmi-server
可以将编译成功的库文件,替换到系统内,通过hdc命令,如下
hdc -t 9b01005932503033320045da20422900 shell mount -o remount,rw / hdc -t 9b01005932503033320045da20422900 file send /root/tf/rk3588/out/rk3588/multimodalinput/input/libmmi-server.z.so /system/lib64/
根据上述措施,已经成功将libmmi-server推送到系统内部,这时候需要将multimodalinput服务关闭和拉起,方法如下
service_control stop multimodalinput service_control start multimodalinput
当然,因为openharmony的服务可以自动拉起,所以直接kill也可以,如下
kill -9 `pidof multimodalinput`
对于调试过程中,主要是通过hilog来进行分析,本例子用到如下
1、设置hilog为调试
hilog -b D
2、定位InputDeviceManager日志
hilog -T InputDeviceManager
3、定位InputWindowsManager日志
hilog -T InputWindowsManager
4、定位PointerDrawingManager日志
hilog -T PointerDrawingManager
5、整体定位mmi服务的日志
hilog -P `pidof multimodalinput`
针对此问题,可以借助社区的pr进行方向性思考,如下pr通过解决多屏同显,多屏异显对multimodalinput做了修改
https://gitee.com/openharmony/multimodalinput_input/pulls/3103/files
对于此笔pr,我们可以发现对于绘制光标的surface接口,可以做如下改变:
surfaceNode_->DetachToDisplay(screenId_); ----> Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); surfaceNode_->AttachToDisplay(screenId_); ----> Rosen::DisplayManager::GetInstance().AddSurfaceNodeToDisplay(displayId_, surfaceNode_);
可以发现,对于光标,openharmony默认直接使用surfaceNode对象来attach和detach对于的display id。此方法在3588使用hdmi时,并不生效。
而通过使用窗管代码的RemoveSurfaceNodeFromDisplay/AddSurfaceNodeToDisplay的接口,可以正常的将光标的surface绑定到对于的display上。此方法有效。
根据上述内容,我们可以清楚的知道光标的实现逻辑以及代码的修改点,如下贴出代码的提交改动
BUILD.gn改动
diff --git a/service/BUILD.gn b/service/BUILD.gn index aa307c7ac..5b908c7d8 100644 --- a/service/BUILD.gn +++ b/service/BUILD.gn @@ -164,7 +164,10 @@ ohos_shared_library("libmmi-server") { external_deps = [ "c_utils:utils" ] if (input_feature_pointer_drawing) { - external_deps += [ "window_manager:libwm" ] + external_deps += [ + "window_manager:libdm", + "window_manager:libwm", + ] } if (security_component_enable) { window_manager/include/pointer_drawing_manager.h改动 diff --git a/service/window_manager/include/pointer_drawing_manager.h b/service/window_manager/include/pointer_drawing_manager.h index 52a0ee255..d111b02c6 100644 --- a/service/window_manager/include/pointer_drawing_manager.h +++ b/service/window_manager/include/pointer_drawing_manager.h @@ -105,7 +105,7 @@ private: std::list<PidInfo> pidInfos_; bool mouseDisplayState_ { false }; std::unique_ptr<OHOS::Media::PixelMap> userIcon_ { nullptr }; - uint64_t screenId_ { 0 }; + Rosen::DisplayId displayId_ { 0 }; std::shared_ptr<Rosen::RSSurfaceNode> surfaceNode_; std::shared_ptr<Rosen::RSCanvasNode> canvasNode_; int32_t userIconHotSpotX_ { 0 }; window_manager/src/input_windows_manager.cpp改动 diff --git a/service/window_manager/src/input_windows_manager.cpp b/service/window_manager/src/input_windows_manager.cpp index c31c103d5..77a98401f 100644 --- a/service/window_manager/src/input_windows_manager.cpp +++ b/service/window_manager/src/input_windows_manager.cpp @@ -1470,7 +1470,10 @@ int32_t InputWindowsManager::UpdateTargetPointer(std::shared_ptr<PointerEvent> p #ifdef OHOS_BUILD_ENABLE_POINTER bool InputWindowsManager::IsInsideDisplay(const DisplayInfo& displayInfo, int32_t physicalX, int32_t physicalY) { - return (physicalX >= 0 && physicalX < displayInfo.width) && (physicalY >= 0 && physicalY < displayInfo.height); + if (displayInfo.direction == DIRECTION0 || displayInfo.direction == DIRECTION180) { + return (physicalX >= 0 && physicalX < displayInfo.width) && (physicalY >= 0 && physicalY < displayInfo.height); + } + return (physicalX >= 0 && physicalX < displayInfo.height) && (physicalY >= 0 && physicalY < displayInfo.width); } void InputWindowsManager::FindPhysicalDisplay(const DisplayInfo& displayInfo, int32_t& physicalX, @@ -1490,14 +1493,25 @@ void InputWindowsManager::FindPhysicalDisplay(const DisplayInfo& displayInfo, in for (const auto &item : displayGroupInfo_.displaysInfo) { int32_t displayMaxX = 0; int32_t displayMaxY = 0; - if (!AddInt32(item.x, item.width, displayMaxX)) { - MMI_HILOGE("The addition of displayMaxX overflows"); - return; - } - if (!AddInt32(item.y, item.height, displayMaxY)) { - MMI_HILOGE("The addition of displayMaxY overflows"); - return; - } + if (item.direction == DIRECTION0 || item.direction == DIRECTION180) { + if (!AddInt32(item.x, item.width, displayMaxX)) { + MMI_HILOGE("The addition of displayMaxX overflows"); + return; + } + if (!AddInt32(item.y, item.height, displayMaxY)) { + MMI_HILOGE("The addition of displayMaxY overflows"); + return; + } + } else { + if (!AddInt32(item.x, item.height, displayMaxX)) { + MMI_HILOGE("The addition of displayMaxX overflows"); + return; + } + if (!AddInt32(item.y, item.width, displayMaxY)) { + MMI_HILOGE("The addition of displayMaxY overflows"); + return; + } + } if ((logicalX >= item.x && logicalX < displayMaxX) && (logicalY >= item.y && logicalY < displayMaxY)) { physicalX = logicalX - item.x;
window_manager/src/pointer_drawing_manager.cpp改动
diff --git a/service/window_manager/src/pointer_drawing_manager.cpp b/service/window_manager/src/pointer_drawing_manager.cpp index cc53d196e..e51fb4c63 100644 --- a/service/window_manager/src/pointer_drawing_manager.cpp +++ b/service/window_manager/src/pointer_drawing_manager.cpp @@ -19,6 +19,7 @@ #include "image_source.h" #include "image_type.h" #include "image_utils.h" +#include "display_manager.h" #include "define_multimodal.h" #include "input_device_manager.h" @@ -83,6 +84,12 @@ void PointerDrawingManager::DrawPointer(int32_t displayId, int32_t physicalX, in physicalY + displayInfo_.y, surfaceNode_->GetStagingProperties().GetBounds().z_, surfaceNode_->GetStagingProperties().GetBounds().w_); + if (displayId_ != static_cast<Rosen::DisplayId>(displayId)) { + // Move pointer to another display. 'Add' before 'Remove' to avoid the surfaceNode_ to be destroyed by dms. + Rosen::DisplayManager::GetInstance().AddSurfaceNodeToDisplay(displayId, surfaceNode_); + Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); + displayId_ = static_cast<Rosen::DisplayId>(displayId); + } Rosen::RSTransaction::FlushImplicitTransaction(); MMI_HILOGD("Pointer window move success"); if (lastMouseStyle_.id == mouseStyle) { @@ -125,7 +132,7 @@ int32_t PointerDrawingManager::InitLayer(const MOUSE_ICON mouseStyle) sptr<OHOS::Surface> layer = GetLayer(); if (layer == nullptr) { MMI_HILOGE("Init layer is failed, Layer is nullptr"); - surfaceNode_->DetachToDisplay(screenId_); + Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); surfaceNode_ = nullptr; Rosen::RSTransaction::FlushImplicitTransaction(); MMI_HILOGD("Pointer window destroy success"); @@ -135,7 +142,7 @@ int32_t PointerDrawingManager::InitLayer(const MOUSE_ICON mouseStyle) sptr<OHOS::SurfaceBuffer> buffer = GetSurfaceBuffer(layer); if (buffer == nullptr || buffer->GetVirAddr() == nullptr) { MMI_HILOGE("Init layer is failed, buffer or virAddr is nullptr"); - surfaceNode_->DetachToDisplay(screenId_); + Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); surfaceNode_ = nullptr; Rosen::RSTransaction::FlushImplicitTransaction(); MMI_HILOGD("Pointer window destroy success"); @@ -312,9 +319,10 @@ void PointerDrawingManager::CreatePointerWindow(int32_t displayId, int32_t physi surfaceNode_->SetPositionZ(Rosen::RSSurfaceNode::POINTER_WINDOW_POSITION_Z); surfaceNode_->SetBounds(physicalX, physicalY, IMAGE_WIDTH, IMAGE_HEIGHT); surfaceNode_->SetBackgroundColor(SK_ColorTRANSPARENT); - screenId_ = static_cast<uint64_t>(displayId); - std::cout << "ScreenId: " << screenId_ << std::endl; - surfaceNode_->AttachToDisplay(screenId_); + + displayId_ = static_cast<Rosen::DisplayId>(displayId); + MMI_HILOGD("DisplayId: %{public}" PRIu64 "", displayId_); + Rosen::DisplayManager::GetInstance().AddSurfaceNodeToDisplay(displayId_, surfaceNode_); surfaceNode_->SetRotation(0); canvasNode_ = Rosen::RSCanvasNode::Create(); @@ -630,7 +638,7 @@ void PointerDrawingManager::OnDisplayInfo(const DisplayGroupInfo& displayGroupIn lastPhysicalY_ = displayGroupInfo.displaysInfo[0].height / CALCULATE_MIDDLE; MouseEventHdr->OnDisplayLost(displayInfo_.id); if (surfaceNode_ != nullptr) { - surfaceNode_->DetachToDisplay(screenId_); + Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); surfaceNode_ = nullptr; Rosen::RSTransaction::FlushImplicitTransaction(); MMI_HILOGD("Pointer window destroy success"); @@ -681,7 +689,7 @@ void PointerDrawingManager::DrawManager() } if (!hasPointerDevice_ && surfaceNode_ != nullptr) { MMI_HILOGD("Pointer window destroy start"); - surfaceNode_->DetachToDisplay(screenId_); + Rosen::DisplayManager::GetInstance().RemoveSurfaceNodeFromDisplay(displayId_, surfaceNode_); surfaceNode_ = nullptr; Rosen::RSTransaction::FlushImplicitTransaction(); MMI_HILOGD("Pointer window destroy success"); @@ -885,7 +893,7 @@ void PointerDrawingManager::DrawPointerStyle() CALL_DEBUG_ENTER; if (hasDisplay_ && hasPointerDevice_) { if (surfaceNode_ != nullptr) { - surfaceNode_->AttachToDisplay(screenId_); + Rosen::DisplayManager::GetInstance().AddSurfaceNodeToDisplay(displayId_, surfaceNode_); Rosen::RSTransaction::FlushImplicitTransaction(); } PointerStyle pointerStyle;
代码补丁文件:0001-rk3588-fixed-cursor-is-hided.patch
至此,openharmony可正常显示光标,其根本原因是适配时,openharmony的surfaceNode实现的attach接口并不生效,可能和底层相关。但参照pr修改为windows manager的api即正常。此问题应属于系统本身问题
前言:根据ubuntu2004的chroot开发环境的内容我们已经具备ubuntu2004的chroot环境了,基于此我们从零搭建OH的开发环境步骤如下
#!/bin/bash CHROOT_DIR=/data/squashfs-root/ # 挂载dev/proc/sys ! mountpoint -q /${CHROOT_DIR}/dev/ && mount -o bind /dev /${CHROOT_DIR}/dev/ ! mountpoint -q /${CHROOT_DIR}/dev/pts && mount -t devpts devpts /${CHROOT_DIR}/dev/pts ! mountpoint -q /${CHROOT_DIR}/proc/ && mount -t proc proc /${CHROOT_DIR}/proc/ ! mountpoint -q /${CHROOT_DIR}/sys/ && mount -t sysfs sys /${CHROOT_DIR}/sys/
根据此脚本可以正常进入2004的chroot环境
根据OH官网的文档,需要安装如下包
apt install default-jdk genext2fs mtd-utils scons gcc-arm-none-eabi gcc-arm-linux-gnueabi ccache apt install libxinerama-dev libxcursor-dev libxrandr-dev libxi-dev curl python3-pip apt-get install git git-lfs
配置repo,拉取代码,安装hb脚本如下
mkdir ~/bin curl https://gitee.com/oschina/repo/raw/fork_flow/repo-py3 -o ~/bin/repo chmod a+x ~/bin/repo pip3 install -i https://repo.huaweicloud.com/repository/pypi/simple requests vim ~/.bashrc # 编辑环境变量 export PATH=~/bin:$PATH # 在环境变量的最后添加一行repo路径信息 source ~/.bashrc # 应用环境变量 repo init -u git@gitee.com:openharmony/manifest.git -b master --no-repo-verify repo sync -c repo forall -c 'git lfs pull' python3 -m pip install --user build/hb export PATH=~/.local/bin:$PATH build/prebuilts_download.sh
repo init -u https://gitee.com/openharmony/manifest -b OpenHarmony-4.0-Release --no-repo-verify -g ohos:standard repo sync -c repo forall -c 'git lfs pull'
./build.sh --product-name rk3568 --ccache --no-prebuilt-sdk
build/prebuilts_download.sh ./build.sh --product-name dayu210 --ccache
out/rk3568/packages/phone/images/
最近在调试openharmony的显示问题上面受阻,原因是众达的硬件上是hdmi接的rk628转接,然后再接的lvds显示器,而openharmony实现的显示panel比较简单,直接dsi panel。所以针对于此,需要将众达的显示panel相关驱动好好梳理出来
针对众达设备的场景,我们知道其重点在于drm panel和drm bridge。这里解释一下:
drm:内核drm显示框架,用于抽象
panel:屏的意思,在drm内抽象用于表示这是一个屏幕,例如hdmi,lvds,edp等
bridge:桥的意思,很多总线概念(pcie)都有,桥接显示器的意思,这里hdmi转lvds接的屏,肯定有bridge的概念
对于众达rk3568而言,我这里图示如下:
对于我们而言,我们需要初始化hdmi驱动,并且把hdmi作为bridge使能,然后找到下一个bridge为rk628,然后在rk628上找到真正的panel。
对于实现此问题的场景,此函数应该重点讲解,它是内核提供的标准api,用于找到下一个panel或bridge。
函数的定义如下:
int drm_of_find_panel_or_bridge(struct device *dev, int port, int endpoint, struct drm_panel **panel, struct drm_bridge **bridge);
这里需要解释的是:
驱动通过此函数能够从设备树获得到下一个bridge或panel的节点,从而触发component_ops的bind回调
为了把事情说清楚,这里有必要简单介绍一下component,详细的介绍建议阅读代码学习,网上知识不多,我也是个人理解
component是组件框架,通常存在在比较大的内核框架中实现,component框架分master和component,两者没有明显区别,只是master会按照match来遍历所有组件,如果都注册了就触发bind。而组件是通过返回-517也就是-DPROBE_DEFER来延迟探测。
这里我们把显示框架"display-subsystem"作为了master,而其他的panel和bridge都以component来注册。示例如下:
ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
对于设备树,我们针对drm_of_find_panel_or_bridge的函数,需要按照实际硬件连接的方式准备port和endpoint,我整理如下:
hdmi: port@1 { reg = <1>; hdmi_out_hdmirx: endpoint { remote-endpoint = <&hdmirx_in_hdmi>; status = "okay"; }; }; rk628_hdmirx: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; hdmirx_in_hdmi: endpoint { remote-endpoint = <&hdmi_out_hdmirx>; }; }; port@1 { reg = <1>; hdmirx_out_post_process: endpoint { remote-endpoint = <&post_process_in_hdmirx>; }; }; }; 抽象rk628_post_process ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; post_process_in_hdmirx: endpoint { remote-endpoint = <&hdmirx_out_post_process>; }; }; port@1 { reg = <1>; post_process_out_lvds: endpoint { remote-endpoint = <&lvds_in_post_process>; }; }; }; rk628_lvds: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; lvds_in_post_process: endpoint { remote-endpoint = <&post_process_out_lvds>; }; }; port@1 { reg = <1>; lvds_out_panel: endpoint { remote-endpoint = <&panel_in_lvds>; }; }; }; simple_panel: port { panel_in_lvds: endpoint { remote-endpoint = <&lvds_out_panel>; }; };
上面代码我梳理一下如下:
hdmi---->hdmirx_in_hdmi---->post_process_in_hdmirx---->lvds_in_post_process---->panel_in_lvds
根据上面的代码梳理,我们可以知道了其调用流程,这里总结一下如下:
component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
添加了组件,等待调用bind
dw_hdmi_bind中drm_bridge_attach到drm中,并通过of_drm_find_bridge找下一个bridge,这里下一个设备是hdmirx_in_hdmi
drm_bridge_add添加bridge设备,attach执行回调
在attach中,调用
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &hdmirx→bridge)
然后drm_bridge_attach上
这里1和-1是指的从port1上遍历找endpoint,这里是post_process_in_hdmirx
同样的
drm_bridge_add添加bridge设备,attach执行回调
在attach中,调用
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &pp→bridge)
然后drm_bridge_attach上
这里1和-1是指的从port1上遍历找endpoint,这里是lvds_in_post_process
这里直接在probe函数中寻找panel设备
drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &lvds->panel, NULL)
然后调用drm_bridge_add添加即可。
因为这个panel_simple驱动就是drm panel的驱动,所以它负责初始化panel drm_panel_init,添加panel drm_panel_add
根据Openharmony的display的驱动来看,通过ili9881_st_5p5.c驱动直接初始化了dsi的硬件,然后通过hdf_drm_panel.c来进行drm_panel_init和drm_panel_add
可以知道,Openharmony没有涉及bridge的概念,所以根据众达的显示panel流程,我们理想中的Openharmony的hdf实现应该是如下:
我们可以跳过1,2,3,4四个步骤,我们只实现步骤5,让驱动在component的bind的时候能够将drm bridge or panel路径走通。此方法可以参考文章《Openharmony-rk3568 移植panel_simple_common》
根据上面的分析解剖,我们知道,如果想要完全按照hdf的思想来设计hdf驱动,我们针对rk628需要写很多个驱动才能实现hdf的基本功能。
我们再基于一个原理:
drm是显示框架的抽象,它有crtc,plane,connector,encoder,frambuffer,panel和bridge。通常情况下,我们通过libdrm下发调用,不会涉及到panel和bridge,它与crtc是割裂开的。所以我们基于drm的panel和bridge也不是一定需要用hdf实现,可以借用linux的实现方式。
而crtc及其他,根据代码追踪,hdf_disp并没有去实现drm的一整套功能。
所以hdf_disp的驱动实际上是不完整的,它只实现了panel,bridge甚至都没有实现,所以我的建议是display驱动可以暂时使用linux标准。
关于hdf_disp调用drm的参考文章《Openharmony-RK3588-drm_plane设置》
针对上面的文章《Openharmony-RK3588-drm_plane设置》分析可以知道,hdf的display并不关键,所以可以完全使用linux的实现,方法如下:
基于drm的显示我们需要如下操作
对于已经编译的内核,我们修改arch/arm64/configs/rockchip_linux_defconfig
文件,对于未编译的内核,我们修改kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig
我们在最底下添加如下:
# CONFIG_DRIVERS_HDF_DISP is not set CONFIG_DRM_PANEL_SIMPLE=y
对于设备树,我们参考《Openharmony RK3568图层设置》修改,添加如下:
&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的内核的众达rk3568设备已经可以正常显示了。