From 16afdcb7562e55798906c63390f4daf9b482995c Mon Sep 17 00:00:00 2001 From: yanghongliang Date: Mon, 8 Nov 2021 22:13:24 +0800 Subject: [PATCH] add display source Signed-off-by: yanghongliang --- build/BUILD.gn | 2 +- {common => hardware}/BUILD.gn | 3 +- hardware/display/BUILD.gn | 174 +++++-- hardware/display/include/display_common.h | 120 +++++ .../display/include/display_gralloc_private.h | 35 ++ hardware/display/lib/libdisplay_device.z.so | Bin 5208 -> 0 bytes .../display/lib/libdisplay_device_drm.z.so | Bin 128164 -> 0 bytes hardware/display/lib/libdisplay_gfx.z.so | Bin 8272 -> 0 bytes hardware/display/lib/libdisplay_gralloc.z.so | Bin 57480 -> 0 bytes hardware/display/lib/libdisplay_layer.z.so | Bin 55884 -> 0 bytes hardware/display/lib64/libdisplay_device.z.so | Bin 7192 -> 0 bytes .../display/lib64/libdisplay_device_drm.z.so | Bin 169128 -> 0 bytes hardware/display/lib64/libdisplay_gfx.z.so | Bin 10240 -> 0 bytes .../display/lib64/libdisplay_gralloc.z.so | Bin 83512 -> 0 bytes .../src/display_device/drm_connector.cpp | 308 ++++++++++++ .../src/display_device/drm_connector.h | 133 +++++ .../display/src/display_device/drm_crtc.cpp | 80 +++ .../display/src/display_device/drm_crtc.h | 86 ++++ .../display/src/display_device/drm_device.cpp | 320 ++++++++++++ .../display/src/display_device/drm_device.h | 80 +++ .../src/display_device/drm_display.cpp | 263 ++++++++++ .../display/src/display_device/drm_display.h | 68 +++ .../src/display_device/drm_encoder.cpp | 60 +++ .../display/src/display_device/drm_encoder.h | 59 +++ .../display/src/display_device/drm_plane.cpp | 61 +++ .../display/src/display_device/drm_plane.h | 89 ++++ .../src/display_device/drm_vsync_worker.cpp | 126 +++++ .../src/display_device/drm_vsync_worker.h | 54 ++ .../src/display_device/hdi_composer.cpp | 46 ++ .../display/src/display_device/hdi_composer.h | 69 +++ .../src/display_device/hdi_device_common.h | 39 ++ .../display_device/hdi_device_interface.cpp | 44 ++ .../src/display_device/hdi_device_interface.h | 38 ++ .../src/display_device/hdi_display.cpp | 227 +++++++++ .../display/src/display_device/hdi_display.h | 129 +++++ .../display_device/hdi_drm_composition.cpp | 168 +++++++ .../src/display_device/hdi_drm_composition.h | 70 +++ .../src/display_device/hdi_drm_layer.cpp | 88 ++++ .../src/display_device/hdi_drm_layer.h | 63 +++ .../display_device/hdi_gfx_composition.cpp | 203 ++++++++ .../src/display_device/hdi_gfx_composition.h | 48 ++ .../display/src/display_device/hdi_layer.cpp | 204 ++++++++ .../display/src/display_device/hdi_layer.h | 191 +++++++ .../display_device/hdi_netlink_monitor.cpp | 69 +++ .../src/display_device/hdi_netlink_monitor.h | 39 ++ .../src/display_device/hdi_session.cpp | 394 +++++++++++++++ .../display/src/display_device/hdi_session.h | 72 +++ .../src/display_device/hdi_shared_fd.h | 65 +++ .../display/src/display_gfx/display_gfx.c | 468 ++++++++++++++++++ .../src/display_gralloc/display_gralloc.c | 124 +++++ .../src/display_gralloc/display_gralloc_gbm.c | 444 +++++++++++++++++ .../src/display_gralloc/display_gralloc_gbm.h | 74 +++ hardware/display/src/display_gralloc/hi_gbm.c | 194 ++++++++ hardware/display/src/display_gralloc/hi_gbm.h | 100 ++++ .../src/display_gralloc/hi_gbm_internal.h | 40 ++ .../src/display_gralloc/hisilicon_drm.h | 40 ++ .../display_gralloc/wayland_drm_auth_client.c | 112 +++++ .../display_gralloc/wayland_drm_auth_client.h | 41 ++ rk3568/build/rootfs/BUILD.gn | 9 + rk3568/build/rootfs/weston.cfg | 42 ++ rk3568/camera/BUILD.gn | 61 +-- 61 files changed, 6060 insertions(+), 76 deletions(-) rename {common => hardware}/BUILD.gn (94%) mode change 100755 => 100644 hardware/display/BUILD.gn create mode 100644 hardware/display/include/display_common.h create mode 100644 hardware/display/include/display_gralloc_private.h delete mode 100644 hardware/display/lib/libdisplay_device.z.so delete mode 100644 hardware/display/lib/libdisplay_device_drm.z.so delete mode 100644 hardware/display/lib/libdisplay_gfx.z.so delete mode 100644 hardware/display/lib/libdisplay_gralloc.z.so delete mode 100644 hardware/display/lib/libdisplay_layer.z.so delete mode 100644 hardware/display/lib64/libdisplay_device.z.so delete mode 100644 hardware/display/lib64/libdisplay_device_drm.z.so delete mode 100644 hardware/display/lib64/libdisplay_gfx.z.so delete mode 100644 hardware/display/lib64/libdisplay_gralloc.z.so create mode 100644 hardware/display/src/display_device/drm_connector.cpp create mode 100644 hardware/display/src/display_device/drm_connector.h create mode 100644 hardware/display/src/display_device/drm_crtc.cpp create mode 100644 hardware/display/src/display_device/drm_crtc.h create mode 100644 hardware/display/src/display_device/drm_device.cpp create mode 100644 hardware/display/src/display_device/drm_device.h create mode 100644 hardware/display/src/display_device/drm_display.cpp create mode 100644 hardware/display/src/display_device/drm_display.h create mode 100644 hardware/display/src/display_device/drm_encoder.cpp create mode 100644 hardware/display/src/display_device/drm_encoder.h create mode 100644 hardware/display/src/display_device/drm_plane.cpp create mode 100644 hardware/display/src/display_device/drm_plane.h create mode 100644 hardware/display/src/display_device/drm_vsync_worker.cpp create mode 100644 hardware/display/src/display_device/drm_vsync_worker.h create mode 100644 hardware/display/src/display_device/hdi_composer.cpp create mode 100644 hardware/display/src/display_device/hdi_composer.h create mode 100644 hardware/display/src/display_device/hdi_device_common.h create mode 100644 hardware/display/src/display_device/hdi_device_interface.cpp create mode 100644 hardware/display/src/display_device/hdi_device_interface.h create mode 100644 hardware/display/src/display_device/hdi_display.cpp create mode 100644 hardware/display/src/display_device/hdi_display.h create mode 100644 hardware/display/src/display_device/hdi_drm_composition.cpp create mode 100644 hardware/display/src/display_device/hdi_drm_composition.h create mode 100644 hardware/display/src/display_device/hdi_drm_layer.cpp create mode 100644 hardware/display/src/display_device/hdi_drm_layer.h create mode 100644 hardware/display/src/display_device/hdi_gfx_composition.cpp create mode 100644 hardware/display/src/display_device/hdi_gfx_composition.h create mode 100644 hardware/display/src/display_device/hdi_layer.cpp create mode 100644 hardware/display/src/display_device/hdi_layer.h create mode 100644 hardware/display/src/display_device/hdi_netlink_monitor.cpp create mode 100644 hardware/display/src/display_device/hdi_netlink_monitor.h create mode 100644 hardware/display/src/display_device/hdi_session.cpp create mode 100644 hardware/display/src/display_device/hdi_session.h create mode 100644 hardware/display/src/display_device/hdi_shared_fd.h create mode 100644 hardware/display/src/display_gfx/display_gfx.c create mode 100644 hardware/display/src/display_gralloc/display_gralloc.c create mode 100644 hardware/display/src/display_gralloc/display_gralloc_gbm.c create mode 100644 hardware/display/src/display_gralloc/display_gralloc_gbm.h create mode 100644 hardware/display/src/display_gralloc/hi_gbm.c create mode 100644 hardware/display/src/display_gralloc/hi_gbm.h create mode 100644 hardware/display/src/display_gralloc/hi_gbm_internal.h create mode 100644 hardware/display/src/display_gralloc/hisilicon_drm.h create mode 100644 hardware/display/src/display_gralloc/wayland_drm_auth_client.c create mode 100644 hardware/display/src/display_gralloc/wayland_drm_auth_client.h create mode 100755 rk3568/build/rootfs/weston.cfg diff --git a/build/BUILD.gn b/build/BUILD.gn index 2a5bf2c..86a380f 100755 --- a/build/BUILD.gn +++ b/build/BUILD.gn @@ -20,6 +20,6 @@ group("products_group") { } deps += [ - "//device/rockchip/common:common_group", + "//device/rockchip/hardware:hardware_group", ] } diff --git a/common/BUILD.gn b/hardware/BUILD.gn similarity index 94% rename from common/BUILD.gn rename to hardware/BUILD.gn index 0288373..4388a2d 100755 --- a/common/BUILD.gn +++ b/hardware/BUILD.gn @@ -13,8 +13,7 @@ import("//build/ohos.gni") -print("common_group in") -group("common_group") { +group("hardware_group") { deps = [ "//device/rockchip/hardware/wifi:ap6xxx", "//device/rockchip/hardware/isp:isp", diff --git a/hardware/display/BUILD.gn b/hardware/display/BUILD.gn old mode 100755 new mode 100644 index 41fc1dd..5b423bb --- a/hardware/display/BUILD.gn +++ b/hardware/display/BUILD.gn @@ -12,62 +12,150 @@ # limitations under the License. import("//build/ohos.gni") -import("//build/ohos/ndk/ndk.gni") -ohos_prebuilt_shared_library("display_gralloc") { - if (target_cpu == "arm") { - source = "lib/libdisplay_gralloc.z.so" - } else if (target_cpu == "arm64") { - source = "lib64/libdisplay_gralloc.z.so" - } - subsystem_name = "hdf" - part_name = "hdf" - install_enable = true - # output_name = "display_gralloc.z" +group("display_group") { + deps = [ + ":display_device", + ":display_gralloc", + ":display_gfx", + ":higbm", + ] } -ohos_prebuilt_shared_library("display_gfx") { - if (target_cpu == "arm") { - source = "lib/libdisplay_gfx.z.so" - } else if (target_cpu == "arm64") { - source = "lib64/libdisplay_gfx.z.so" - } - subsystem_name = "hdf" - part_name = "hdf" - install_enable = true - # output_name = "display_gfx.z" +ohos_static_library("higbm") { + sources = [ "src/display_gralloc/hi_gbm.c" ] + include_dirs = [ + "include", + "//utils/native/base/include", + "//base/hiviewdfx/interfaces/innerkits/libhilog/include", + ] + output_name = "higbm" + cflags = [ + "-DGRALLOC_GBM_SUPPORT", + "-Wno-macro-redefined", + ] + deps = [ + "//third_party/libdrm:libdrm", + "//utils/native/base:utils", + ] + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] } -ohos_prebuilt_shared_library("display_device_drm") { - if (target_cpu == "arm") { - source = "lib/libdisplay_device_drm.z.so" - } else if (target_cpu == "arm64") { - source = "lib64/libdisplay_device_drm.z.so" - } +ohos_shared_library("display_gralloc") { + sources = [ + "src/display_gralloc/display_gralloc.c", + "src/display_gralloc/display_gralloc_gbm.c", + "src/display_gralloc/wayland_drm_auth_client.c", + ] + include_dirs = [ + "include", + "//drivers/peripheral/display/interfaces/include", + "//drivers/peripheral/base", + "//drivers/framework/include/utils", + "//drivers/adapter/uhdf2/osal/include", + "//utils/native/base/include", + "//foundation/graphic/standard/utils/include", + "//foundation/graphic/standard/prebuilts/librarys/drm/include", + "//base/hiviewdfx/interfaces/innerkits/libhilog/include", + ] + output_name = "display_gralloc" + cflags = [ + "-DGRALLOC_GBM_SUPPORT", + "-Wno-macro-redefined", + ] + deps = [ + ":higbm", + "//third_party/libdrm:libdrm", + "//third_party/wayland_standard:libwayland_client", + "//third_party/weston:drm_auth_protocol", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + install_enable = true subsystem_name = "hdf" part_name = "hdf" - install_enable = true - # output_name = "display_device_drm.z" } -ohos_prebuilt_shared_library("display_device") { - if (target_cpu == "arm") { - source = "lib/libdisplay_device.z.so" - } else if (target_cpu == "arm64") { - source = "lib64/libdisplay_device.z.so" - } +ohos_shared_library("display_gfx") { + sources = [ "src/display_gfx/display_gfx.c" ] + include_dirs = [ + "include", + "//drivers/peripheral/display/interfaces/include", + "//drivers/peripheral/base", + "//drivers/framework/include/utils", + "//drivers/adapter/uhdf2/adapter/osal/include/", + "//utils/native/base/include", + "//foundation/graphic/standard/utils/include", + "//device/rockchip/hardware/rga/include/", + "//base/hiviewdfx/interfaces/innerkits/libhilog/include", + ] + output_name = "display_gfx" + cflags = [ "-Wno-macro-redefined" ] + deps = [ + ":display_gralloc", + "//utils/native/base:utils", + "//device/rockchip/hardware/rga:librga", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + install_enable = true subsystem_name = "hdf" part_name = "hdf" - install_enable = true - # output_name = "display_device_drm.z" } -ohos_prebuilt_shared_library("display_layer") { - if (target_cpu == "arm") { - source = "lib/libdisplay_layer.z.so" - } +ohos_shared_library("display_device") { + sources = [ + "src/display_device/drm_connector.cpp", + "src/display_device/drm_crtc.cpp", + "src/display_device/drm_device.cpp", + "src/display_device/drm_display.cpp", + "src/display_device/drm_encoder.cpp", + "src/display_device/drm_plane.cpp", + "src/display_device/drm_vsync_worker.cpp", + "src/display_device/hdi_composer.cpp", + "src/display_device/hdi_device_interface.cpp", + "src/display_device/hdi_display.cpp", + "src/display_device/hdi_drm_composition.cpp", + "src/display_device/hdi_drm_layer.cpp", + "src/display_device/hdi_gfx_composition.cpp", + "src/display_device/hdi_layer.cpp", + "src/display_device/hdi_session.cpp", + ] + output_name = "display_device" + include_dirs = [ + "src/display_device", + "include", + "//drivers/peripheral/display/interfaces/include", + "//drivers/peripheral/base", + "//drivers/framework/include/utils", + "//drivers/adapter/uhdf2/osal/include", + "//utils/native/base/include", + "//foundation/graphic/standard/utils/include", + "//foundation/graphic/standard/prebuilts/librarys/drm/include", + "//base/hiviewdfx/interfaces/innerkits/libhilog/include", + ] + deps = [ + ":display_gfx", + ":display_gralloc", + "//third_party/libdrm:libdrm", + "//utils/native/base:utils", + ] + cflags = [ "-Wno-unused-function" ] + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + install_enable = true subsystem_name = "hdf" part_name = "hdf" - install_enable = true - # output_name = "display_device_drm.z" +} + +group("display_layer") { + deps = [ + ":display_device", + ":display_gralloc", + ":display_gfx", + ":higbm", + ] } diff --git a/hardware/display/include/display_common.h b/hardware/display/include/display_common.h new file mode 100644 index 0000000..316ce7e --- /dev/null +++ b/hardware/display/include/display_common.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DISP_COMMON_H +#define DISP_COMMON_H +#include +#include +#include "hilog/log.h" +#include "stdio.h" +#ifdef HDF_LOG_TAG +#undef HDF_LOG_TAG +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#undef LOG_TAG +#undef LOG_DOMAIN +#define LOG_TAG "DISP" +#define LOG_DOMAIN 0xD001400 + +#ifndef DISPLAY_UNUSED +#define DISPLAY_UNUSED(x) (void)x +#endif + +#define __FILENAME__ (strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/') + 1) : __FILE__) + +#ifndef DISPLAY_LOGD +#define DISPLAY_LOGD(format, ...) \ + do { \ + HILOG_DEBUG(LOG_CORE, "[%{public}s@%{public}s:%{public}d] " format "\n", __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DISPLAY_LOGI +#define DISPLAY_LOGI(format, ...) \ + do { \ + HILOG_INFO(LOG_CORE, "[%{public}s@%{public}s:%{public}d] " format "\n", __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DISPLAY_LOGW +#define DISPLAY_LOGW(format, ...) \ + do { \ + HILOG_WARN(LOG_CORE, "[%{public}s@%{public}s:%{public}d] " format "\n", __FUNCTION__, __FILENAME__, __LINE__, \ + ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef DISPLAY_LOGE +#define DISPLAY_LOGE(format, ...) \ + do { \ + HILOG_ERROR(LOG_CORE, \ + "\033[0;32;31m" \ + "[%{public}s@%{public}s:%{public}d] " format "\033[m" \ + "\n", \ + __FUNCTION__, __FILENAME__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif + +#ifndef CHECK_NULLPOINTER_RETURN_VALUE +#define CHECK_NULLPOINTER_RETURN_VALUE(pointer, ret) \ + do { \ + if ((pointer) == NULL) { \ + DISPLAY_LOGE("pointer is null and return ret\n"); \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHECK_NULLPOINTER_RETURN +#define CHECK_NULLPOINTER_RETURN(pointer) \ + do { \ + if ((pointer) == NULL) { \ + DISPLAY_LOGE("pointer is null and return\n"); \ + return; \ + } \ + } while (0) +#endif + +#ifndef DISPLAY_CHK_RETURN +#define DISPLAY_CHK_RETURN(val, ret, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef DISPLAY_CHK_RETURN_NOT_VALUE +#define DISPLAY_CHK_RETURN_NOT_VALUE(val, ret, ...) \ + do { \ + if (val) { \ + __VA_ARGS__; \ + return; \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* DISP_COMMON_H */ diff --git a/hardware/display/include/display_gralloc_private.h b/hardware/display/include/display_gralloc_private.h new file mode 100644 index 0000000..e5ce9e0 --- /dev/null +++ b/hardware/display/include/display_gralloc_private.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DISPLAY_GRALLOC_INTERNAL_H +#define DISPLAY_GRALLOC_INTERNAL_H +#include "display_type.h" +#ifdef __cplusplus +extern "C" { +#endif +#define GRALLOC_NUM_FDS 1 +#define GRALLOC_NUM_INTS ((sizeof(struct PrivBufferHandle) - sizeof(BufferHandle)) / sizeof(int) - GRALLOC_NUM_FDS) + +#define INVALID_PIXEL_FMT 0 + +typedef struct { + BufferHandle hdl; +} PriBufferHandle; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/hardware/display/lib/libdisplay_device.z.so b/hardware/display/lib/libdisplay_device.z.so deleted file mode 100644 index 697f706903fe1616281aba4e61e43110046b61a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5208 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

&5{gBZ(kCIIKyt z#t?%_=#QFF{D{k>B~NLxirvTSB6FJTn*7$8%eDAn7kUbDzpX_T1Hy;}wPT9y<3siy zwB9`;Y;HjW7+ckFxB1rhG&u1ME@Ec44fRD+>F1s$quP!dUld+6nX}nOjI|pFL=)l1 zj4>(nawB@5{GAy|MZTYmQO;-LYW$58L#D!vrqXrPEuq_gGJ2oTlm9W$WU{a0L843;slWL?+E7%Gq)kvKo;1DpWM(VVH zEnrfO)Tx0rFsVlBgda`;Ce=vc%~5#uITq|Dna?>{o=|sEM4inmnwoPmCLq5xt>b)6 zKt5qX$NBVtd|p6)en7rFAipIbpIP2EBFfqA5`!M3a(S| ziwdq$@MZ;2f{PS9N5KvSPf&2Wf|C^-uiz*J3k9G0A(+1n3O=l0mxA9? z@Gb>EtKcmPUa#QQ3SOb$3I#7z@O%YNS8$GkGZmbw-~zNTi3*NYuuZ{d+k*M2 zEBFfqA5`!M3a(S|iwdq$@MZ;2f{PS9N5KvSPf&2Wf|C^-uiz*J3k9G0 zHkiK+3O=l0mmf2ICXXbmCLV{*b=-^<^^XDHt2pp?4-C`9l(dd}%_aXp01mu%ALnuL z+J5T75t^6`b#>g#`P=@D@5(YX@i5fY;mp#+(@rLQ2me!!iPPa5^a|uZ zew1HIdHR2It~BC0?hgeXf$yJxnCH_iEn<%_Y2A2 zLp*o>WBz7M^nF1S@=fNl-$tfozSQYU@AswEzVs7ClH~ zATAL(L-3hT`Z3S@2GB%+;v)7WlcYsi#Lq~X%Ku+?Y;V=X|Iz>P;D4(O$O0LOJqot7A<;dFs*DlohTLx+= zniyHOxUBM*#Y>kK-zG**zGBA6Ma#+-E)ydw7cHw4Bd=d#evGUrC$n@>xtS@s=_d2y z;>yaUWhFOPE&?ySZShT&OG%3>i;YW{E-`WWbV} z%gPsyC|hV;RD46(bz9|yC3*Q<<2-s%>c$+DX5e-5 zTI|(^&Xwn-UX>*Es{fGBmIvE!1xr(m-%EWiN$T^!`$@gd@-q2dOaHE3di1ir)n9`YL5_Q|hgyOY+U z1+_c=>mJ~H`QPMq3%G6p*BvA;%Sayn_m%CZ|GAm8gPF7kn~f{X5C8oJ1zacFOBdID z;4cim^Iyl(U4xJExmyw~VvDYcWdB2b-;u~k3;EwFWL_Tfx(R01_>})i9vdNfFaL3v M>uUUf)PLQ71CE`^7XSbN diff --git a/hardware/display/lib/libdisplay_gfx.z.so b/hardware/display/lib/libdisplay_gfx.z.so deleted file mode 100644 index 16d562d0d83c4ca8b7997be67410cfdfec9940f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8272 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 diff --git a/hardware/display/lib/libdisplay_gralloc.z.so b/hardware/display/lib/libdisplay_gralloc.z.so deleted file mode 100644 index ffe7315cd6e4f02813b23413712a4c9ca9323572..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57480 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 diff --git a/hardware/display/lib/libdisplay_layer.z.so b/hardware/display/lib/libdisplay_layer.z.so deleted file mode 100644 index 53705e692dbf28533165d7d9ba61eb29863bca58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55884 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

RF|Y({%NoS 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 XXGnb!_CiZt{;!pC49^#Se!2ez%%!o& diff --git a/hardware/display/lib64/libdisplay_device.z.so b/hardware/display/lib64/libdisplay_device.z.so deleted file mode 100644 index 86030b399877ca5a7cd69ab5476be2eacb8028d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7192 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 QiSqAQkC;J>3)~_83$Ur0cmMzZ diff --git a/hardware/display/lib64/libdisplay_device_drm.z.so b/hardware/display/lib64/libdisplay_device_drm.z.so deleted file mode 100644 index 44885cce8381d47ca25cb4288505542a4a954f2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169128 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 z 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 W{NJPedbc=^@&5;Xk@K|2vHlAhq!YjZ diff --git a/hardware/display/lib64/libdisplay_gfx.z.so b/hardware/display/lib64/libdisplay_gfx.z.so deleted file mode 100644 index 6c4fd345708ad6b4a60cb24ca98f517a5bb825ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10240 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 +#include +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +void DrmMode::ConvertToHdiMode(DisplayModeInfo &hdiMode) +{ + hdiMode.height = mModeInfo.vdisplay; + hdiMode.width = mModeInfo.hdisplay; + hdiMode.freshRate = mModeInfo.vrefresh; + hdiMode.id = mId; +} + +DrmConnector::DrmConnector(drmModeConnector c, FdPtr &fd) + : mId(c.connector_id), + mPhyWidth(c.mmWidth), + mPhyHeight(c.mmHeight), + mEncoderId(c.encoder_id), + mConnectState(c.connection), + mDrmFdPtr(fd) +{ + DISPLAY_LOGD("encoder_id %{public}d", mEncoderId); + DISPLAY_LOGD("the connect state is %{public}d", mConnectState); + + for (int i = 0; i < c.count_encoders; i++) { + mPossibleEncoders.push_back(c.encoders[i]); + DISPLAY_LOGD("add possible encoder id %{public}d", c.encoders[i]); + } + + ConvertToHdiType(c.connector_type, mType); + ConvertTypeToName(mType, mName); + InitModes(c); + DISPLAY_LOGD("name %{public}s", mName.c_str()); +} + +void DrmConnector::InitModes(drmModeConnector c) +{ + DISPLAY_LOGD("id %{public}d", mId); + mModes.clear(); + mPreferenceId = INVALID_MODE_ID; + for (int i = 0; i < c.count_modes; i++) { + drmModeModeInfoPtr mode = c.modes + i; + DISPLAY_LOGD("mode: hdisplay %{public}d, vdisplay %{public}d vrefresh %{public}d type %{public}d", + mode->hdisplay, mode->vdisplay, mode->vrefresh, mode->type); + if ((mPreferenceId == INVALID_MODE_ID) && (mode->type & DRM_MODE_TYPE_PREFERRED)) { + DISPLAY_LOGD("set it to prefernce id %{public}d", i); + mPreferenceId = i; + } + mModes.emplace(i, DrmMode { *mode, i }); + } + DISPLAY_LOGD("mode count %{public}zd", mModes.size()); +} + +int32_t DrmConnector::Init(DrmDevice &drmDevice) +{ + int32_t ret; + DrmProperty prop; + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((mDrmFdPtr == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("the mDrmFdPtr is NULL")); + DISPLAY_CHK_RETURN((mDrmFdPtr->GetFd() == -1), DISPLAY_FAILURE, DISPLAY_LOGE("the drm fd is -1")); + // find dpms prop + ret = drmDevice.GetConnectorProperty(*this, PROP_DPMS, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not get mode prop id")); + mPropDpmsId = prop.propId; + mDpmsState = prop.value; + DISPLAY_LOGD("dpms state : %{public}" PRIu64 "", mDpmsState); + // find the crtcid + ret = drmDevice.GetConnectorProperty(*this, PROP_CRTCID, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get out fence prop id")); + mPropCrtcId = prop.propId; + DISPLAY_LOGD("encoder_id %{public}d", mEncoderId); + DISPLAY_LOGD("mPropCrtcId %{public}d", mPropCrtcId); + // find the brightness + ret = drmDevice.GetConnectorProperty(*this, PROP_BRIGHTNESS, prop); + if (ret == DISPLAY_SUCCESS) { + mPropBrightnessId = prop.propId; + mBrightnessLevel = static_cast(prop.value); + DISPLAY_LOGD("prop brightness is %{public}d, level is %{public}d", mPropBrightnessId, mBrightnessLevel); + } else { + DISPLAY_LOGW("can not get the brightness prop, can not set the brightness"); + } + return DISPLAY_SUCCESS; +} + +int32_t DrmConnector::GetBrightness(uint32_t& level) { + if (mPropBrightnessId == DRM_INVALID_ID) { + DISPLAY_LOGE("the prop id of brightness is invalid"); + return DISPLAY_NOT_SUPPORT; + } + level = mBrightnessLevel; + return DISPLAY_SUCCESS; +} + +int32_t DrmConnector::SetBrightness(uint32_t level) +{ + DISPLAY_LOGD("set %{public}d", level); + if (mPropBrightnessId == DRM_INVALID_ID) { + DISPLAY_LOGE("the prop id of brightness is invalid"); + return DISPLAY_NOT_SUPPORT; + } + int ret = drmModeConnectorSetProperty(mDrmFdPtr->GetFd(), mId, mPropBrightnessId, level); + DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, DISPLAY_LOGE("can not set dpms")); + mBrightnessLevel = level; + return DISPLAY_SUCCESS; +} + +void DrmConnector::GetDisplayCap(DisplayCapability &cap) +{ + cap.phyHeight = mPhyHeight; + cap.phyWidth = mPhyWidth; + cap.type = mType; + memcpy_s(cap.name, sizeof(cap.name), mName.c_str(), mName.size()); + if (mName.size() >= sizeof(cap.name)) { + cap.name[sizeof(cap.name) - 1] = 0; + } else { + cap.name[mName.size()] = 0; + } +} + +void DrmConnector::ConvertTypeToName(uint32_t type, std::string &name) +{ + DISPLAY_LOGD("type %{public}d", type); + switch (type) { + case DISP_INTF_VGA: + name = "VGA"; + break; + case DISP_INTF_HDMI: + name = "HDMI"; + break; + case DISP_INTF_MIPI: + name = "MIPI"; + break; + default: + name = "Unknown"; + break; + } +} + +void DrmConnector::ConvertToHdiType(uint32_t type, InterfaceType &hdiType) +{ + switch (type) { + case DRM_MODE_CONNECTOR_VGA: + hdiType = DISP_INTF_VGA; + break; + case DRM_MODE_CONNECTOR_DSI: + hdiType = DISP_INTF_MIPI; + break; + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + hdiType = DISP_INTF_HDMI; + break; + default: + hdiType = DISP_INTF_BUTT; + break; + } +} +int32_t DrmConnector::TryPickEncoder(IdMapPtr &encoders, uint32_t encoderId, IdMapPtr &crtcs, + uint32_t &crtcId) +{ + int ret; + auto encoderIter = encoders.find(encoderId); + if (encoderIter == encoders.end()) { + DISPLAY_LOGW("can not find encoder for id : %{public}d", encoderId); + return DISPLAY_FAILURE; + } + + auto &encoder = encoderIter->second; + DISPLAY_LOGD("connector : %{public}d encoder : %{public}d", mId, encoder->GetId()); + ret = encoder->PickIdleCrtcId(crtcs, crtcId); + DISPLAY_CHK_RETURN((ret == DISPLAY_SUCCESS), DISPLAY_SUCCESS, + DISPLAY_LOGD("connector : %{public}d pick encoder : %{public}d", mId, encoder->GetId())); + return DISPLAY_FAILURE; +} + +int32_t DrmConnector::PickIdleCrtcId(IdMapPtr &encoders, IdMapPtr &crtcs, uint32_t &crtcId) +{ + DISPLAY_LOGD(); + DISPLAY_LOGD("encoder_id %{public}d", mEncoderId); + int ret = TryPickEncoder(encoders, mEncoderId, crtcs, crtcId); + DISPLAY_CHK_RETURN((ret == DISPLAY_SUCCESS), DISPLAY_SUCCESS, + DISPLAY_LOGD("connector : %{public}d pick endcoder : %{public}d crtcId : %{public}d", + mId, mEncoderId, crtcId)); + + for (auto encoder : mPossibleEncoders) { + ret = TryPickEncoder(encoders, encoder, crtcs, crtcId); + DISPLAY_CHK_RETURN((ret == DISPLAY_SUCCESS), DISPLAY_SUCCESS, + DISPLAY_LOGD("connector : %{public}d pick endcoder : %{public}d crtcId : %{public}d", mId, mEncoderId, + crtcId)); + } + + DISPLAY_LOGW("can not pick a crtc for connector"); + return DISPLAY_FAILURE; +} + +int32_t DrmConnector::UpdateModes() +{ + int drmFd = mDrmFdPtr->GetFd(); + drmModeConnectorPtr c = drmModeGetConnector(drmFd, mId); + DISPLAY_CHK_RETURN((c == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not get connector")); + mConnectState = c->connection; + // init the modes + InitModes(*c); + drmModeFreeConnector(c); + return DISPLAY_SUCCESS; +} + +int32_t DrmConnector::GetDisplaySuppportedModes(int *num, DisplayModeInfo *modes) +{ + DISPLAY_CHK_RETURN((num == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("num is nullptr")); + *num = static_cast(mModes.size()); + if (modes != nullptr) { + int i = 0; + for (const auto &modeMap : mModes) { + DrmMode mode = modeMap.second; + mode.ConvertToHdiMode(*(modes + i)); + i++; + } + } + return DISPLAY_SUCCESS; +} + +int32_t DrmConnector::SetDpmsState(uint64_t dmps) +{ + DISPLAY_LOGD("dmps %{public}" PRIu64 "", dmps); + int ret = drmModeConnectorSetProperty(mDrmFdPtr->GetFd(), mId, mPropDpmsId, dmps); + DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, DISPLAY_LOGE("can not set dpms")); + mDpmsState = dmps; + return DISPLAY_SUCCESS; +} + +bool DrmConnector::IsConnected() +{ + return (mConnectState == DRM_MODE_CONNECTED); +} + +int32_t DrmConnector::GetModeFromId(int32_t id, DrmMode &mode) +{ + DISPLAY_LOGD(); + auto iter = mModes.find(id); + if (iter == mModes.end()) { + return DISPLAY_FAILURE; + } + mode = mModes[id]; + return DISPLAY_SUCCESS; +} + +std::unique_ptr DrmConnector::GetModeBlockFromId(int32_t id) +{ + DISPLAY_LOGD("id %{public}d", id); + auto iter = mModes.find(id); + DISPLAY_CHK_RETURN((iter == mModes.end()), nullptr, DISPLAY_LOGE("can not the mode %{public}d", id)); + return std::make_unique(mModes[id]); +} + +DrmModeBlock::DrmModeBlock(DrmMode &mode) +{ + DISPLAY_LOGD(); + Init(mode); +} + +int32_t DrmModeBlock::Init(DrmMode &mode) +{ + int ret; + int drmFd; + drmFd = DrmDevice::GetDrmFd(); + DISPLAY_CHK_RETURN((drmFd < 0), DISPLAY_FAILURE, DISPLAY_LOGE("the drm fd is invalid")); + drmModeModeInfo modeInfo = *(mode.GetModeInfoPtr()); + ret = drmModeCreatePropertyBlob(drmFd, static_cast(&modeInfo), sizeof(modeInfo), &mBlockId); + DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, DISPLAY_LOGE("create property blob failed")); + DISPLAY_LOGD("mBlockId %{public}d", mBlockId); + return DISPLAY_SUCCESS; +} + +DrmModeBlock::~DrmModeBlock() +{ + DISPLAY_LOGD("mBlockId %{public}d", mBlockId); + int drmFd; + drmFd = DrmDevice::GetDrmFd(); + if ((mBlockId != DRM_INVALID_ID) && (drmFd >= 0)) { + int ret = drmModeDestroyPropertyBlob(drmFd, mBlockId); + if (ret != 0) { + DISPLAY_LOGE("destroy property blob failed errno %{public}d", errno); + } + } else { + DISPLAY_LOGE("can not destruct the block id %{public}d drmFd %{public}d", mBlockId, drmFd); + } +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_connector.h b/hardware/display/src/display_device/drm_connector.h new file mode 100644 index 0000000..9d07db4 --- /dev/null +++ b/hardware/display/src/display_device/drm_connector.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_CONNECTOR_H +#define DRM_CONNECTOR_H +#include +#include +#include +#include +#include +#include "display_type.h" +#include "drm_encoder.h" +#include "hdi_device_common.h" +#include "hdi_shared_fd.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const std::string PROP_DPMS = "DPMS"; +const std::string PROP_CRTCID = "CRTC_ID"; +const std::string PROP_BRIGHTNESS = "brightness"; +class DrmDevice; +class DrmModeBlock; + +class DrmMode { +public: + DrmMode() {}; + DrmMode(drmModeModeInfo &modeInfo, uint32_t id) : mModeInfo(modeInfo), mId(id) {} + virtual ~DrmMode() {}; + drmModeModeInfoPtr GetModeInfoPtr() + { + return &mModeInfo; + } + void ConvertToHdiMode(DisplayModeInfo &hdiMode); + +private: + drmModeModeInfo mModeInfo = { 0 }; + int32_t mId = -1; +}; + +class DrmModeBlock { +public: + explicit DrmModeBlock(DrmMode &mode); + virtual ~DrmModeBlock(); + int32_t Init(DrmMode &mode); + uint32_t GetBlockId() const + { + return mBlockId; + } + +private: + uint32_t mBlockId = DRM_INVALID_ID; +}; + +class DrmConnector { +public: + DrmConnector(drmModeConnector c, FdPtr &fd); + virtual ~DrmConnector() {}; + uint32_t GetId() const + { + return mId; + } + uint32_t GetEncoderId() const + { + return mEncoderId; + } + void GetDisplayCap(DisplayCapability &cap); + int32_t Init(DrmDevice &drmDevice); + int32_t PickIdleCrtcId(IdMapPtr &encoders, IdMapPtr &crtcs, uint32_t &crtcId); + int32_t GetDisplaySuppportedModes(int *num, DisplayModeInfo *modes); + int32_t GetPreferenceId() const + { + return mPreferenceId; + } + uint32_t GetPropCrtcId() const + { + return mPropCrtcId; + } + int32_t TryPickEncoder(IdMapPtr &encoders, uint32_t encoderId, IdMapPtr &crtcs, + uint32_t &crtcId); + // update modes will reset the preference mode id and active mode id + int32_t UpdateModes(); + std::unique_ptr GetModeBlockFromId(int32_t id); + int32_t GetModeFromId(int32_t id, DrmMode &mode); + uint64_t GetDpmsState() const + { + return mDpmsState; + } + int32_t SetDpmsState(uint64_t dmps); + bool IsConnected(); + + int32_t GetBrightness(uint32_t& level); + int32_t SetBrightness(uint32_t level); + +private: + static void ConvertTypeToName(uint32_t type, std::string &name); + static void ConvertToHdiType(uint32_t type, InterfaceType &hdiType); + + void InitModes(drmModeConnector c); + uint32_t mId; + InterfaceType mType; + uint32_t mPhyWidth; + uint32_t mPhyHeight; + uint32_t mEncoderId; + std::vector mPossibleEncoders; + std::string mName; + drmModeConnection mConnectState; + uint32_t mPropDpmsId = DRM_INVALID_ID; + uint64_t mDpmsState = 0; + uint32_t mPropCrtcId = DRM_INVALID_ID; + uint32_t mPropBrightnessId = DRM_INVALID_ID; + uint32_t mBrightnessLevel = 0; + std::unordered_map mModes; + int32_t mPreferenceId = INVALID_MODE_ID; + + FdPtr mDrmFdPtr; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY +#endif // DRM_CONNECTOR_H diff --git a/hardware/display/src/display_device/drm_crtc.cpp b/hardware/display/src/display_device/drm_crtc.cpp new file mode 100644 index 0000000..d0fe8b2 --- /dev/null +++ b/hardware/display/src/display_device/drm_crtc.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_crtc.h" +#include "display_common.h" +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmCrtc::DrmCrtc(drmModeCrtcPtr c, uint32_t pipe) : mId(c->crtc_id), mPipe(pipe) {} + +int32_t DrmCrtc::Init(DrmDevice &drmDevice) +{ + DISPLAY_LOGD(); + int32_t ret; + DrmProperty prop; + ret = drmDevice.GetCrtcProperty(*this, PROP_MODEID, prop); + mModePropId = prop.propId; + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not get mode prop id")); + + ret = drmDevice.GetCrtcProperty(*this, PROP_OUTFENCE, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get out fence prop id")); + mOutFencePropId = prop.propId; + + ret = drmDevice.GetCrtcProperty(*this, PROP_ACTIVE, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get out fence prop id")); + mActivePropId = prop.propId; + + return DISPLAY_SUCCESS; +} + +int32_t DrmCrtc::BindToDisplay(uint32_t id) +{ + DISPLAY_CHK_RETURN((mDisplayId != INVALIDE_DISPLAY_ID), DISPLAY_FAILURE, + DISPLAY_LOGE("the crtc has bind to %{public}d", mDisplayId)); + mDisplayId = id; + return DISPLAY_SUCCESS; +} + +void DrmCrtc::UnBindDisplay(uint32_t id) +{ + DISPLAY_LOGD(); + if (mDisplayId == id) { + mDisplayId = INVALIDE_DISPLAY_ID; + } else { + DISPLAY_LOGE("can not unbind"); + } +} + +bool DrmCrtc::CanBind() +{ + return (mDisplayId == INVALIDE_DISPLAY_ID); +} + +int32_t DrmCrtc::SetActivieMode(int32_t id) +{ + DISPLAY_LOGD("set activie modeid to %{public}d", id); + DISPLAY_CHK_RETURN((id > 0), DISPLAY_PARAM_ERR, DISPLAY_LOGE("id %{public}d is invalid ", id)); + if (mActiveModeId != id) { + mNeedModeSet = true; + } + mActiveModeId = id; + return DISPLAY_SUCCESS; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_crtc.h b/hardware/display/src/display_device/drm_crtc.h new file mode 100644 index 0000000..2d7d0b9 --- /dev/null +++ b/hardware/display/src/display_device/drm_crtc.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_CRTC_H +#define DRM_CRTC_H +#include +#include +#include +#include +#include "hdi_device_common.h" +#include "hdi_display.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const std::string PROP_ACTIVE = "ACTIVE"; +const std::string PROP_MODEID = "MODE_ID"; +const std::string PROP_OUTFENCE = "OUT_FENCE_PTR"; + +class DrmDevice; + +class DrmCrtc { +public: + DrmCrtc(drmModeCrtcPtr c, uint32_t pipe); + virtual ~DrmCrtc() {}; + int32_t BindToDisplay(uint32_t id); + void UnBindDisplay(uint32_t id); + bool CanBind(); + uint32_t GetId() const + { + return mId; + } + uint32_t GetModePropId() const + { + return mModePropId; + } + uint32_t GetOutFencePropId() const + { + return mOutFencePropId; + } + uint32_t GetActivePropId() const + { + return mActivePropId; + } + uint32_t GetPipe() const + { + return mPipe; + } + int32_t Init(DrmDevice &drmDevice); + int32_t SetActivieMode(int32_t id); + int32_t GetActiveModeId() const + { + return mActiveModeId; + } + bool NeedModeSet() + { + return mNeedModeSet; + } + +private: + uint32_t mId = 0; + uint32_t mModePropId = 0; + uint32_t mOutFencePropId = 0; + uint32_t mActivePropId = 0; + uint32_t mDisplayId = INVALIDE_DISPLAY_ID; + uint32_t mPipe = 0; + int32_t mActiveModeId = INVALID_MODE_ID; + bool mNeedModeSet = false; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // DRM_CRTC_H \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_device.cpp b/hardware/display/src/display_device/drm_device.cpp new file mode 100644 index 0000000..fdbedab --- /dev/null +++ b/hardware/display/src/display_device/drm_device.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_device.h" +#include +#include +#include +#include +#include +#include "display_common.h" +#include "drm_display.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +FdPtr DrmDevice::mDrmFd = nullptr; +std::shared_ptr DrmDevice::mInstance; + +std::shared_ptr DrmDevice::Create() +{ + DISPLAY_LOGD(); + if (mDrmFd == nullptr) { + const std::string name("rockchip"); + int drmFd = drmOpen(name.c_str(), nullptr); + if (drmFd < 0) { + DISPLAY_LOGE("drm file:%{public}s open failed %{public}s", name.c_str(), strerror(errno)); + return nullptr; + } + DISPLAY_LOGD("the drm fd is %{public}d", drmFd); + mDrmFd = std::make_shared(drmFd); + } + if (mInstance == nullptr) { + mInstance = std::make_shared(); + } + return mInstance; +} + +int DrmDevice::GetDrmFd() +{ + if (mDrmFd == nullptr) { + DISPLAY_LOGE("the drmfd is null not open"); + return -1; + } + return mDrmFd->GetFd(); +} + +using PixelFormatConvertTbl = struct PixFmtConvertTbl { + uint32_t drmFormat; + PixelFormat pixFormat; +}; + +uint32_t DrmDevice::ConvertToDrmFormat(PixelFormat fmtIn) +{ + static const PixelFormatConvertTbl convertTable[] = { + {DRM_FORMAT_XBGR8888, PIXEL_FMT_RGBX_8888}, {DRM_FORMAT_ABGR8888, PIXEL_FMT_RGBA_8888}, + {DRM_FORMAT_RGB888, PIXEL_FMT_RGB_888}, {DRM_FORMAT_RGB565, PIXEL_FMT_BGR_565}, + {DRM_FORMAT_BGRX4444, PIXEL_FMT_BGRX_4444}, {DRM_FORMAT_BGRA4444, PIXEL_FMT_BGRA_4444}, + {DRM_FORMAT_RGBA4444, PIXEL_FMT_RGBA_4444}, {DRM_FORMAT_RGBX4444, PIXEL_FMT_RGBX_4444}, + {DRM_FORMAT_BGRX5551, PIXEL_FMT_BGRX_5551}, {DRM_FORMAT_BGRA5551, PIXEL_FMT_BGRA_5551}, + {DRM_FORMAT_BGRX8888, PIXEL_FMT_BGRX_8888}, {DRM_FORMAT_ARGB8888, PIXEL_FMT_BGRA_8888}, + {DRM_FORMAT_NV12, PIXEL_FMT_YCBCR_420_SP}, {DRM_FORMAT_NV21, PIXEL_FMT_YCRCB_420_SP}, + {DRM_FORMAT_YUV420, PIXEL_FMT_YCBCR_420_P}, {DRM_FORMAT_YVU420, PIXEL_FMT_YCRCB_420_P}, + {DRM_FORMAT_NV16, PIXEL_FMT_YCBCR_422_SP}, {DRM_FORMAT_NV61, PIXEL_FMT_YCRCB_422_SP}, + {DRM_FORMAT_YUV422, PIXEL_FMT_YCBCR_422_P}, {DRM_FORMAT_YVU422, PIXEL_FMT_YCRCB_422_P}, + }; + uint32_t fmtOut = 0; + for (uint32_t i = 0; i < sizeof(convertTable) / sizeof(convertTable[0]); i++) { + if (convertTable[i].pixFormat == fmtIn) { + fmtOut = convertTable[i].drmFormat; + } + } + DISPLAY_LOGD("fmtIn %{public}d, outFmt %{public}d", fmtIn, fmtOut); + return fmtOut; +} + +DrmDevice::DrmDevice() {} + +int32_t DrmDevice::GetCrtcProperty(const DrmCrtc &crtc, const std::string &name, DrmProperty &prop) +{ + return GetProperty(crtc.GetId(), DRM_MODE_OBJECT_CRTC, name, prop); +} + +int32_t DrmDevice::GetConnectorProperty(const DrmConnector &connector, const std::string &name, DrmProperty &prop) +{ + return GetProperty(connector.GetId(), DRM_MODE_OBJECT_CONNECTOR, name, prop); +} + +int32_t DrmDevice::GetPlaneProperty(const DrmPlane &plane, const std::string &name, DrmProperty &prop) +{ + return GetProperty(plane.GetId(), DRM_MODE_OBJECT_PLANE, name, prop); +} + +int32_t DrmDevice::GetProperty(const uint32_t objId, uint32_t objType, const std::string &name, DrmProperty &prop) +{ + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(GetDrmFd(), objId, objType); + DISPLAY_CHK_RETURN((!props), DISPLAY_FAILURE, DISPLAY_LOGE("can not get properties")); + bool found = false; + for (uint32_t i = 0; i < props->count_props; i++) { + drmModePropertyPtr p = drmModeGetProperty(GetDrmFd(), props->props[i]); + if (strcmp(p->name, name.c_str()) == 0) { + found = true; + prop.propId = p->prop_id; + prop.value = props->prop_values[i]; + } + drmModeFreeProperty(p); + } + drmModeFreeObjectProperties(props); + return found ? DISPLAY_SUCCESS : DISPLAY_NOT_SUPPORT; +} + +int32_t DrmDevice::Init() +{ + int ret = drmSetClientCap(GetDrmFd(), DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, + DISPLAY_LOGE("DRM_CLIENT_CAP_UNIVERSAL_PLANES set failed %{public}s", strerror(errno))); + ret = drmSetClientCap(GetDrmFd(), DRM_CLIENT_CAP_ATOMIC, 1); + DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, + DISPLAY_LOGE("DRM_CLIENT_CAP_ATOMIC set failed %{public}s", strerror(errno))); + + ret = drmSetMaster(GetDrmFd()); + DISPLAY_CHK_RETURN((ret), DISPLAY_FAILURE, DISPLAY_LOGE("can not set to master errno : %{public}d", errno)); + DISPLAY_LOGE("chenyf master"); + ret = drmIsMaster(GetDrmFd()); + DISPLAY_CHK_RETURN((!ret), DISPLAY_FAILURE, DISPLAY_LOGE("is not master : %{public}d", errno)); + + return DISPLAY_SUCCESS; +} + +void DrmDevice::DeInit() +{ + mDisplays.clear(); + mCrtcs.clear(); +} + +void DrmDevice::FindAllCrtc(const drmModeResPtr &res) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((res == nullptr), DISPLAY_LOGE("the res is null")); + mCrtcs.clear(); + for (int i = 0; i < res->count_crtcs; i++) { + drmModeCrtcPtr crtc = drmModeGetCrtc(GetDrmFd(), res->crtcs[i]); + if (!crtc) { + DISPLAY_LOGE("can not get crtc %{public}d", i); + continue; + } + uint32_t crtc_id = crtc->crtc_id; + std::shared_ptr drmCrtc = std::make_shared(crtc, i); + int ret = drmCrtc->Init(*this); + drmModeFreeCrtc(crtc); + if (ret != DISPLAY_SUCCESS) { + DISPLAY_LOGE("crtc %{public}d init failed", i); + continue; + } + mCrtcs.emplace(crtc_id, std::move(drmCrtc)); + } +} + +void DrmDevice::FindAllEncoder(const drmModeResPtr &res) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((res == nullptr), DISPLAY_LOGE("the res is null")); + mEncoders.clear(); + for (int i = 0; i < res->count_encoders; i++) { + drmModeEncoderPtr encoder = drmModeGetEncoder(GetDrmFd(), res->encoders[i]); + if (!encoder) { + DISPLAY_LOGE("can not get encoder %{public}d", i); + continue; + } + std::shared_ptr drmEncoder = std::make_shared(*encoder); + mEncoders.emplace(encoder->encoder_id, std::move(drmEncoder)); + drmModeFreeEncoder(encoder); + } + DISPLAY_LOGD("find encoder count %{public}zd", mEncoders.size()); +} + +void DrmDevice::FindAllConnector(const drmModeResPtr &res) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((res == nullptr), DISPLAY_LOGE("the res is null")); + mConnectors.clear(); + int ret; + for (int i = 0; i < res->count_connectors; i++) { + drmModeConnectorPtr connector = drmModeGetConnector(GetDrmFd(), res->connectors[i]); + if (!connector) { + DISPLAY_LOGE("can not get connector mode %{public}d", i); + continue; + } + std::shared_ptr drmConnector = std::make_shared(*connector, mDrmFd); + ret = drmConnector->Init(*this); + drmModeFreeConnector(connector); + if (ret != DISPLAY_SUCCESS) { + continue; + } + int connectorId = drmConnector->GetId(); + mConnectors.emplace(connectorId, std::move(drmConnector)); + } + DISPLAY_LOGD("find connector count %{public}zd", mConnectors.size()); +} + +void DrmDevice::FindAllPlane() +{ + mPlanes.clear(); + int ret; + drmModePlaneResPtr planeRes = drmModeGetPlaneResources(GetDrmFd()); + DISPLAY_CHK_RETURN_NOT_VALUE((planeRes == nullptr), DISPLAY_LOGE("can not get plane resource")); + DISPLAY_LOGD("count_planes %{public}d", planeRes->count_planes); + for (uint32_t i = 0; i < planeRes->count_planes; i++) { + drmModePlanePtr p = drmModeGetPlane(GetDrmFd(), planeRes->planes[i]); + if (!p) { + DISPLAY_LOGE("can not get plane %{public}d", i); + continue; + } + std::shared_ptr drmPlane = std::make_shared(*p); + DISPLAY_LOGD(); + ret = drmPlane->Init(*this); + DISPLAY_LOGD(); + drmModeFreePlane(p); + if (ret != DISPLAY_SUCCESS) { + DISPLAY_LOGE("drm plane %{public}d init failed", i); + continue; + } + mPlanes.emplace_back(std::move(drmPlane)); + } + DISPLAY_LOGD("find plane count %{public}zd", mPlanes.size()); +} + +std::shared_ptr DrmDevice::GetDrmEncoderFromId(uint32_t id) +{ + /*int32_t ret = DISPLAY_FAILURE; + auto iter = mEncoders.find(id); + if (iter == mEncoders.end()) { + ret = DISPLAY_SUCCESS; + }*/ + return nullptr; +} + +std::shared_ptr DrmDevice::GetDrmConnectorFromId(uint32_t id) +{ + /*int32_t ret = DISPLAY_FAILURE; + auto iter = mConnectors.find(id); + if (iter == mConnectors.end()) { + ret = DISPLAY_SUCCESS; + }*/ + return nullptr; +} + +std::shared_ptr DrmDevice::GetDrmCrtcFromId(uint32_t id) +{ + /*int32_t ret = DISPLAY_FAILURE; + auto iter = mCrtcs.find(id); + if (iter == mCrtcs.end()) { + ret = DISPLAY_SUCCESS; + }*/ + return nullptr; +} + +std::vector> DrmDevice::GetDrmPlane(uint32_t pipe, uint32_t type) +{ + std::vector> planes; + for (const auto &plane : mPlanes) { + if (plane->IsIdle() && ((1 << pipe) & plane->GetPossibleCrtcs()) && (type == plane->GetType())) { + plane->BindToPipe(pipe); + planes.push_back(plane); + } + } + DISPLAY_LOGD("the planes count %{public}zd", planes.size()); + return planes; +} + + +std::unordered_map> DrmDevice::DiscoveryDisplay() +{ + mDisplays.clear(); + drmModeResPtr res = drmModeGetResources(GetDrmFd()); + DISPLAY_CHK_RETURN((res == nullptr), mDisplays, DISPLAY_LOGE("can not get drm resource")); + // discovery all drm resource + FindAllCrtc(res); + FindAllEncoder(res); + FindAllConnector(res); + FindAllPlane(); + DISPLAY_LOGD(); + // travel all connector + for (auto &connectorPair : mConnectors) { + auto connector = connectorPair.second; + uint32_t crtcId = 0; + int32_t ret = connector->PickIdleCrtcId(mEncoders, mCrtcs, crtcId); + if (ret != DISPLAY_SUCCESS) { + continue; + } + DISPLAY_LOGD(); + + auto crtcIter = mCrtcs.find(crtcId); + if (crtcIter == mCrtcs.end()) { + DISPLAY_LOGE("can not find crtc for the id %{public}d", connector->GetId()); + continue; + } + DISPLAY_LOGD(); + auto crtc = crtcIter->second; + DISPLAY_LOGD("crtc %{public}p", crtc.get()); + // create the display + std::shared_ptr display = std::make_shared(connector, crtc, mInstance); + DISPLAY_LOGD(); + display->Init(); + mDisplays.emplace(display->GetId(), std::move(display)); + } + DISPLAY_LOGD("find display size %{public}zd", mDisplays.size()); + return mDisplays; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_device.h b/hardware/display/src/display_device/drm_device.h new file mode 100644 index 0000000..178db8b --- /dev/null +++ b/hardware/display/src/display_device/drm_device.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_DEVICE_H +#define DRM_DEVICE_H +#include +#include +#include +#include +#include "drm_connector.h" +#include "drm_crtc.h" +#include "drm_encoder.h" +#include "drm_plane.h" +#include "hdi_device_common.h" +#include "hdi_device_interface.h" +#include "hdi_display.h" +#include "hdi_shared_fd.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +struct DrmProperty { + uint32_t propId; + uint64_t value; +}; + +class DrmDevice : public HdiDeviceInterface, std::enable_shared_from_this { +public: + static std::shared_ptr Create(); + static uint32_t ConvertToDrmFormat(PixelFormat fmtIn); + static int GetDrmFd(); + DrmDevice(); + virtual ~DrmDevice() {} + + std::vector> GetDrmPlane(uint32_t pipe, uint32_t type); + + int32_t GetCrtcProperty(const DrmCrtc &crtc, const std::string &name, DrmProperty &prop); + int32_t GetConnectorProperty(const DrmConnector &connector, const std::string &name, DrmProperty &prop); + int32_t GetPlaneProperty(const DrmPlane &plane, const std::string &name, DrmProperty &prop); + + int32_t GetProperty(uint32_t objId, uint32_t objType, const std::string &name, DrmProperty &prop); + std::shared_ptr GetDrmEncoderFromId(uint32_t id); + std::shared_ptr GetDrmConnectorFromId(uint32_t id); + std::shared_ptr GetDrmCrtcFromId(uint32_t id); + void CreateCrtc(drmModeCrtcPtr c); + virtual std::unordered_map> DiscoveryDisplay(); + virtual int32_t Init(); + virtual void DeInit(); + +private: + static FdPtr mDrmFd; + static std::shared_ptr mInstance; + void FindAllCrtc(const drmModeResPtr &drmRes); + void FindAllEncoder(const drmModeResPtr &drmRes); + void FindAllConnector(const drmModeResPtr &drmRes); + void FindAllPlane(); + int InitNetLink(); + IdMapPtr mDisplays; + IdMapPtr mCrtcs; + IdMapPtr mEncoders; + IdMapPtr mConnectors; + std::vector> mPlanes; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // DRM_DEVICE_H \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_display.cpp b/hardware/display/src/display_device/drm_display.cpp new file mode 100644 index 0000000..b9576e6 --- /dev/null +++ b/hardware/display/src/display_device/drm_display.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_display.h" +#include +#include +#include +#include +#include +#include "display_gralloc.h" +#include "display_common.h" +#include "drm_device.h" +#include "drm_vsync_worker.h" +#include "hdi_drm_composition.h" +#include "hdi_gfx_composition.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmDisplay::DrmDisplay(std::shared_ptr connector, std::shared_ptr crtc, + std::shared_ptr drmDevice) + : mDrmDevice(drmDevice), mConnector(connector), mCrtc(crtc) +{} + +DrmDisplay::~DrmDisplay() +{ + if (mCrtc != nullptr) { + mCrtc->UnBindDisplay(GetId()); + } +} + +int32_t DrmDisplay::Init() +{ + int ret; + DISPLAY_CHK_RETURN((mCrtc == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("crtc is null")); + DISPLAY_CHK_RETURN((mConnector == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("connector is null")); + DISPLAY_CHK_RETURN((mDrmDevice == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("drmDevice is null")); + + ret = HdiDisplay::Init(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("init failed")); + auto preComp = std::make_unique(); + DISPLAY_CHK_RETURN((preComp == nullptr), DISPLAY_FAILURE, + DISPLAY_LOGE("can not new HdiGfxComposition errno %{public}d", errno)); + ret = preComp->Init(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not init HdiGfxComposition")); + + auto postComp = std::make_unique(mConnector, mCrtc, mDrmDevice); + DISPLAY_CHK_RETURN((postComp == nullptr), DISPLAY_FAILURE, + DISPLAY_LOGE("can not new HdiDrmComposition errno %{public}d", errno)); + ret = postComp->Init(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not init HdiDrmComposition")); + mComposer = std::make_unique(std::move(preComp), std::move(postComp)); + ret = mCrtc->BindToDisplay(GetId()); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("bind crtc failed")); + + ret = ChosePreferenceMode(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("choose preference mode fialed")); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::GetDisplayCapability(DisplayCapability *info) +{ + mConnector->GetDisplayCap(*info); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::GetDisplaySuppportedModes(int *num, DisplayModeInfo *modes) +{ + mConnector->GetDisplaySuppportedModes(num, modes); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::GetDisplayMode(uint32_t *modeId) +{ + DISPLAY_CHK_RETURN((modeId == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("the in modeId is nullptr")); + *modeId = mCrtc->GetActiveModeId(); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::SetDisplayMode(uint32_t modeId) +{ + return mCrtc->SetActivieMode(modeId); +} + +int32_t DrmDisplay::GetDisplayPowerStatus(DispPowerStatus *status) +{ + DISPLAY_CHK_RETURN((status == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("status is nullptr")); + return ConvertToHdiPowerState(mConnector->GetDpmsState(), *status); +} + +int32_t DrmDisplay::SetDisplayPowerStatus(DispPowerStatus status) +{ + DISPLAY_LOGD("the status %{public}d ", status); + uint32_t drmPowerState = 0; + int ret = ConvertToDrmPowerState(status, drmPowerState); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_PARAM_ERR, + DISPLAY_LOGE("unknown power status %{public}d", status)); + mConnector->SetDpmsState(drmPowerState); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::ConvertToHdiPowerState(uint32_t drmPowerState, DispPowerStatus &hdiPowerState) +{ + int32_t ret = DISPLAY_SUCCESS; + switch (drmPowerState) { + case DRM_MODE_DPMS_OFF: + hdiPowerState = POWER_STATUS_OFF; + break; + case DRM_MODE_DPMS_ON: + hdiPowerState = POWER_STATUS_ON; + break; + case DRM_MODE_DPMS_STANDBY: + hdiPowerState = POWER_STATUS_STANDBY; + break; + case DRM_MODE_DPMS_SUSPEND: + hdiPowerState = POWER_STATUS_SUSPEND; + break; + default: + hdiPowerState = POWER_STATUS_BUTT; + ret = DISPLAY_FAILURE; + break; + } + DISPLAY_LOGD("hdi power state %{public}u", hdiPowerState); + return ret; +} + +int32_t DrmDisplay::ConvertToDrmPowerState(DispPowerStatus hdiPowerState, uint32_t &drmPowerState) +{ + int32_t ret = DISPLAY_SUCCESS; + switch (hdiPowerState) { + case POWER_STATUS_OFF: + drmPowerState = DRM_MODE_DPMS_OFF; + break; + case POWER_STATUS_ON: + drmPowerState = DRM_MODE_DPMS_ON; + break; + case POWER_STATUS_STANDBY: + drmPowerState = DRM_MODE_DPMS_STANDBY; + break; + case POWER_STATUS_SUSPEND: + drmPowerState = DRM_MODE_DPMS_SUSPEND; + break; + default: + ret = DISPLAY_FAILURE; + break; + } + return ret; +} + +std::unique_ptr DrmDisplay::CreateHdiLayer(LayerType type) +{ + DISPLAY_LOGD(); + return std::make_unique(type); +} + +int32_t DrmDisplay::WaitForVBlank(uint64_t *ns) +{ + int ret; + constexpr uint64_t nPerS = 1000000000; + constexpr uint64_t nPerUS = 1000; + drmVBlank vbl = { + .request.type = DRM_VBLANK_RELATIVE, + .request.sequence = 0, + .request.signal = 0, + }; + DISPLAY_CHK_RETURN((ns == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in ns is nullptr")); + ret = drmWaitVBlank(mDrmDevice->GetDrmFd(), &vbl); + DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, DISPLAY_LOGE("wait vblank failed errno %{public}d", errno)); + *ns = static_cast(vbl.reply.tval_sec * nPerS + vbl.reply.tval_usec * nPerUS); + return DISPLAY_SUCCESS; +} + +bool DrmDisplay::IsConnected() +{ + DISPLAY_LOGD("conneted %{public}d", mConnector->IsConnected()); + return mConnector->IsConnected(); +} + +int32_t DrmDisplay::PushFirstFrame() +{ + GrallocFuncs *grallocFucs = nullptr; + int ret = GrallocInitialize(&grallocFucs); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("Gralloc init failed")); + DrmMode mode; + ret = mConnector->GetModeFromId(mCrtc->GetActiveModeId(), mode); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, + DISPLAY_LOGE("can not get the mode from id %{public}d", mCrtc->GetActiveModeId())); + AllocInfo info = { + .width = mode.GetModeInfoPtr()->hdisplay, + .height = mode.GetModeInfoPtr()->vdisplay, + .usage = HBM_USE_MEM_DMA | HBM_USE_CPU_READ | HBM_USE_CPU_WRITE, + .format = PIXEL_FMT_BGRA_8888 + }; + + BufferHandle *buffer = nullptr; + ret = grallocFucs->AllocMem(&info, &buffer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not alloc memory")); + mClientLayer->SetLayerBuffer(buffer, -1); + + std::vector layers; + HdiDrmComposition *drmComp = static_cast(mComposer->GetPostCompostion()); + drmComp->SetLayers(layers, *mClientLayer); + drmComp->Apply(true); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::ChosePreferenceMode() +{ + int32_t ret; + int32_t modeId = mConnector->GetPreferenceId(); + if (modeId == INVALID_MODE_ID) { + int32_t num = 0; + ret = GetDisplaySuppportedModes(&num, nullptr); + DISPLAY_CHK_RETURN((num == 0) && (ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not get modes")); + modeId = 0; + } + ret = SetDisplayMode(modeId); + // Push first frame to the drm, for that the vblank must init all the componet. + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("set display mode failed")); + return PushFirstFrame(); +} + +int32_t DrmDisplay::RegDisplayVBlankCallback(VBlankCallback cb, void *data) +{ + DISPLAY_LOGD("the VBlankCallback %{public}p ", cb); + (void)data; + std::shared_ptr vsyncCb = std::make_shared(cb, nullptr); + DrmVsyncWorker::GetInstance().ReqesterVBlankCb(vsyncCb); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::SetDisplayVsyncEnabled(bool enabled) +{ + DISPLAY_LOGD("enable %{public}d", enabled); + DrmVsyncWorker::GetInstance().EnableVsync(enabled); + return DISPLAY_SUCCESS; +} + +int32_t DrmDisplay::GetDisplayBacklight(uint32_t *value) +{ + DISPLAY_CHK_RETURN((value == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("value is nullptr")); + return mConnector->GetBrightness(*value); +} + +int32_t DrmDisplay::SetDisplayBacklight(uint32_t value) +{ + return mConnector->SetBrightness(value); +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_display.h b/hardware/display/src/display_device/drm_display.h new file mode 100644 index 0000000..e4dfba6 --- /dev/null +++ b/hardware/display/src/display_device/drm_display.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_DISPLAY_H +#define DRM_DISPLAY_H +#include +#include +#include "drm_connector.h" +#include "drm_crtc.h" +#include "drm_device.h" +#include "drm_plane.h" +#include "hdi_composer.h" +#include "hdi_drm_composition.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class DrmDisplay : public HdiDisplay { +public: + DrmDisplay(std::shared_ptr connector, std::shared_ptr crtc, + std::shared_ptr drmDevice); + + virtual ~DrmDisplay(); + + int32_t Init() override; + int32_t GetDisplayCapability(DisplayCapability *info) override; + int32_t GetDisplaySuppportedModes(int *num, DisplayModeInfo *modes) override; + int32_t GetDisplayMode(uint32_t *modeId) override; + int32_t SetDisplayMode(uint32_t modeId) override; + int32_t GetDisplayPowerStatus(DispPowerStatus *status) override; + int32_t SetDisplayPowerStatus(DispPowerStatus status) override; + int32_t GetDisplayBacklight(uint32_t *value) override; + int32_t SetDisplayBacklight(uint32_t value) override; + int32_t ChosePreferenceMode(); + virtual int32_t RegDisplayVBlankCallback(VBlankCallback cb, void *data) override; + virtual int32_t WaitForVBlank(uint64_t *ns) override; + virtual bool IsConnected() override; + virtual int32_t SetDisplayVsyncEnabled(bool enabled) override; + HdiDrmComposition *GetDrmComposition(); + +protected: + std::unique_ptr CreateHdiLayer(LayerType type) override; + +private: + int32_t PushFirstFrame(); + int32_t ConvertToHdiPowerState(uint32_t drmPowerState, DispPowerStatus &hdiPowerState); + int32_t ConvertToDrmPowerState(DispPowerStatus hdiPowerState, uint32_t &drmPowerState); + std::shared_ptr mDrmDevice; + std::shared_ptr mConnector; + std::shared_ptr mCrtc; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_DISPLAY_H \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_encoder.cpp b/hardware/display/src/display_device/drm_encoder.cpp new file mode 100644 index 0000000..70ed875 --- /dev/null +++ b/hardware/display/src/display_device/drm_encoder.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_encoder.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmEncoder::DrmEncoder(drmModeEncoder e) +{ + mEncoderId = e.encoder_id; + mCrtcId = e.crtc_id; + mPossibleCrtcs = e.possible_crtcs; +} + +int32_t DrmEncoder::PickIdleCrtcId(IdMapPtr &crtcs, uint32_t &crtcId) +{ + // find the crtc id; + DISPLAY_LOGD("crtcs szie %{public}zu", crtcs.size()); + std::shared_ptr crtc; + auto crtcIter = crtcs.find(mCrtcId); + if (crtcIter == crtcs.end()) { + DISPLAY_LOGW("can not find crtc for id %{public}d", mCrtcId); + crtcIter = crtcs.begin(); + } + DISPLAY_CHK_RETURN((crtcIter == crtcs.end()), DISPLAY_FAILURE, + DISPLAY_LOGE("have no crtc %{public}zu ", crtcs.size())); + crtc = crtcIter->second; + DISPLAY_CHK_RETURN((crtc == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("crtc is null")); + + if (!crtc->CanBind()) { + crtc = nullptr; + for (const auto &posCrtcPair : crtcs) { + auto &posCrts = posCrtcPair.second; + DISPLAY_LOGD("try crtc id : %{public}d", posCrts->GetId()); + if (posCrts->CanBind() && ((1 << posCrts->GetPipe()) & mPossibleCrtcs)) { + crtc = posCrts; + } + } + } + DISPLAY_CHK_RETURN((crtc == nullptr), DISPLAY_FAILURE, + DISPLAY_LOGE("encoder %{public}d can not bind to idle crtc", mEncoderId)); + crtcId = crtc->GetId(); + return DISPLAY_SUCCESS; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_encoder.h b/hardware/display/src/display_device/drm_encoder.h new file mode 100644 index 0000000..4f35578 --- /dev/null +++ b/hardware/display/src/display_device/drm_encoder.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_ENCODER_H +#define DRM_ENCODER_H +#include +#include +#include +#include +#include +#include + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class DrmEncoder { +public: + explicit DrmEncoder(drmModeEncoder e); + virtual ~DrmEncoder() {} + uint32_t GetCrtcId() const + { + return mCrtcId; + } + void SetCrtcId(uint32_t id) + { + mCrtcId = id; + } + uint32_t GetPossibleCrtcs() const + { + return mPossibleCrtcs; + } + int32_t PickIdleCrtcId(IdMapPtr &crtcs, uint32_t &crtcId); + uint32_t GetId() const + { + return mEncoderId; + } + +private: + uint32_t mEncoderId; + uint32_t mCrtcId; + uint32_t mPossibleCrtcs; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // DRM_ENCODER_H \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_plane.cpp b/hardware/display/src/display_device/drm_plane.cpp new file mode 100644 index 0000000..6a98887 --- /dev/null +++ b/hardware/display/src/display_device/drm_plane.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_plane.h" +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmPlane::DrmPlane(drmModePlane &p) + : mId(p.plane_id), mPossibleCrtcs(p.possible_crtcs), mFormats(p.formats, p.formats + p.count_formats) +{} + +DrmPlane::~DrmPlane() +{ + DISPLAY_LOGD(); +} + +int32_t DrmPlane::Init(DrmDevice &drmDevice) +{ + DISPLAY_LOGD(); + int32_t ret; + DrmProperty prop; + ret = drmDevice.GetPlaneProperty(*this, PROP_FBID, prop); + mPropFbId = prop.propId; + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not get plane fb id")); + ret = drmDevice.GetPlaneProperty(*this, PROP_IN_FENCE_FD, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get plane in fence prop id")); + mPropFenceInId = prop.propId; + ret = drmDevice.GetPlaneProperty(*this, PROP_CRTC_ID, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get pane crtc prop id")); + mPropCrtcId = prop.propId; + ret = drmDevice.GetPlaneProperty(*this, PROP_TYPE, prop); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("cat not get pane crtc prop id")); + switch (prop.value) { + case DRM_PLANE_TYPE_OVERLAY: + case DRM_PLANE_TYPE_PRIMARY: + case DRM_PLANE_TYPE_CURSOR: + mType = static_cast(prop.value); + break; + default: + DISPLAY_LOGE("unknown type value %{public}" PRIu64 "", prop.value); + return DISPLAY_FAILURE; + } + return DISPLAY_SUCCESS; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/drm_plane.h b/hardware/display/src/display_device/drm_plane.h new file mode 100644 index 0000000..05ee6cb --- /dev/null +++ b/hardware/display/src/display_device/drm_plane.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_PLANE_H +#define DRM_PLANE_H +#include +#include +#include +#include +#include + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const std::string PROP_FBID = "FB_ID"; +const std::string PROP_IN_FENCE_FD = "IN_FENCE_FD"; +const std::string PROP_CRTC_ID = "CRTC_ID"; +const std::string PROP_TYPE = "type"; +class DrmDevice; + +class DrmPlane { +public: + explicit DrmPlane(drmModePlane &p); + virtual ~DrmPlane(); + int32_t Init(DrmDevice &drmDevice); + uint32_t GetId() const + { + return mId; + } + uint32_t GetPropFbId() const + { + return mPropFbId; + } + uint32_t GetPropFenceInId() const + { + return mPropFenceInId; + } + uint32_t GetPropCrtcId() const + { + return mPropCrtcId; + } + uint32_t GetPossibleCrtcs() const + { + return mPossibleCrtcs; + } + uint32_t GetType() const + { + return mType; + } + void BindToPipe(uint32_t pipe) + { + mPipe = pipe; + } + void UnBindPipe() + { + mPipe = 0; + } + bool IsIdle() const + { + return (mPipe == 0); + } + +private: + uint32_t mId = 0; + uint32_t mPossibleCrtcs = 0; + uint32_t mPropFbId = 0; + uint32_t mPropFenceInId = 0; + uint32_t mPropCrtcId = 0; + uint32_t mPipe = 0; + uint32_t mType = 0; + std::vector mFormats; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // DRM_PLANE_H \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_vsync_worker.cpp b/hardware/display/src/display_device/drm_vsync_worker.cpp new file mode 100644 index 0000000..b150f95 --- /dev/null +++ b/hardware/display/src/display_device/drm_vsync_worker.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drm_vsync_worker.h" +#include +#include "display_common.h" +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmVsyncWorker::DrmVsyncWorker() {} + +int32_t DrmVsyncWorker::Init(int fd) +{ + DISPLAY_CHK_RETURN((fd < 0), DISPLAY_FAILURE, DISPLAY_LOGE("the fd is invalid")); + mDrmFd = fd; + DISPLAY_LOGD("the drm fd is %{public}d", fd); + mThread = std::make_unique([this]() { WorkThread(); }); + DISPLAY_CHK_RETURN((mThread == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not create thread")); + mRunning = true; + return DISPLAY_SUCCESS; +} + +DrmVsyncWorker &DrmVsyncWorker::GetInstance() +{ + static DrmVsyncWorker instance; + static std::once_flag once; + std::call_once(once, [&]() { + int ret = instance.Init(DrmDevice::GetDrmFd()); + if (ret != DISPLAY_SUCCESS) { + DISPLAY_LOGE("Vsync Worker Init failed"); + } + }); + return instance; +} + +DrmVsyncWorker::~DrmVsyncWorker() +{ + DISPLAY_LOGD(); + { + std::lock_guard lg(mMutex); + mRunning = false; + } + DISPLAY_LOGD(); + mCondition.notify_one(); + if (mThread != nullptr) { + mThread->join(); + } + DISPLAY_LOGD(); +} + +bool DrmVsyncWorker::WaitSignalAndCheckRuning() +{ + std::unique_lock ul(mMutex); + mCondition.wait(ul, [this]() { return (mEnable || !mRunning); }); + return mRunning; +} + + +uint64_t DrmVsyncWorker::WaitNextVBlank(unsigned int &sq) +{ + constexpr uint64_t SEC_TO_NSEC = 1000 * 1000 * 1000; + constexpr uint64_t USEC_TO_NSEC = 1000; + drmVBlank vblank = { + .request = + drmVBlankReq { + .type = DRM_VBLANK_RELATIVE, + .sequence = 1, + .signal = 0, + } + }; + int ret = drmWaitVBlank(mDrmFd, &vblank); + DISPLAY_CHK_RETURN((ret < 0), 0, + DISPLAY_LOGE("wait vblank failed ret : %{public}d errno %{public}d", ret, errno)); + sq = vblank.reply.sequence; + return (uint64_t)(vblank.reply.tval_sec * SEC_TO_NSEC + vblank.reply.tval_usec * USEC_TO_NSEC); +} + + +void DrmVsyncWorker::EnableVsync(bool enable) +{ + DISPLAY_LOGD(); + { + std::lock_guard lg(mMutex); + mEnable = enable; + } + mCondition.notify_one(); +} + +void DrmVsyncWorker::WorkThread() +{ + DISPLAY_LOGD(); + unsigned int seq = 0; + while (WaitSignalAndCheckRuning()) { + // wait the vblank + uint64_t time = WaitNextVBlank(seq); + if (mCallBack != nullptr) { + mCallBack->Vsync(seq, time); + } else { + DISPLAY_LOGE("the callbac is nullptr"); + } + } +} + +void DrmVsyncWorker::ReqesterVBlankCb(std::shared_ptr &cb) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN_NOT_VALUE((cb == nullptr), DISPLAY_LOGE("the VBlankCallback is nullptr ")); + mCallBack = cb; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY \ No newline at end of file diff --git a/hardware/display/src/display_device/drm_vsync_worker.h b/hardware/display/src/display_device/drm_vsync_worker.h new file mode 100644 index 0000000..c578e91 --- /dev/null +++ b/hardware/display/src/display_device/drm_vsync_worker.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_VSYNC_WORKER_H +#define DRM_VSYNC_WORKER_H +#include +#include +#include +#include +#include "display_device.h" +#include "hdi_device_common.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class DrmVsyncWorker { +public: + DrmVsyncWorker(); + virtual ~DrmVsyncWorker(); + int32_t Init(int fd); + static DrmVsyncWorker &GetInstance(); + + void EnableVsync(bool enable); + void WorkThread(); + uint64_t WaitNextVBlank(unsigned int &sq); + bool WaitSignalAndCheckRuning(); + void ReqesterVBlankCb(std::shared_ptr &cb); + +private: + int mDrmFd = 0; + std::unique_ptr mThread; + bool mEnable = false; + std::mutex mMutex; + std::condition_variable mCondition; + std::shared_ptr mCallBack; + bool mRunning = false; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // DRM_VSYNC_WORKER_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_composer.cpp b/hardware/display/src/display_device/hdi_composer.cpp new file mode 100644 index 0000000..04eefdb --- /dev/null +++ b/hardware/display/src/display_device/hdi_composer.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_composer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +HdiComposer::HdiComposer(std::unique_ptr pre, std::unique_ptr post) +{ + mPreComp = std::move(pre); + mPostComp = std::move(post); +} + +int32_t HdiComposer::Prepare(std::vector &layers, HdiLayer &clientLayer) +{ + int ret = mPreComp->SetLayers(layers, clientLayer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("pre composition prepare failed")); + ret = mPostComp->SetLayers(layers, clientLayer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("post composition prepare failed")); + return DISPLAY_SUCCESS; +} + +int32_t HdiComposer::Commit(bool modeSet) +{ + int ret = mPreComp->Apply(modeSet); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("pre composition apply failed")); + ret = mPostComp->Apply(modeSet); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("post composition apply failed")); + return DISPLAY_SUCCESS; +} +} // OHOS +} // HDI +} // DISPLAY diff --git a/hardware/display/src/display_device/hdi_composer.h b/hardware/display/src/display_device/hdi_composer.h new file mode 100644 index 0000000..a3b69de --- /dev/null +++ b/hardware/display/src/display_device/hdi_composer.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_COMPOSER_H +#define HDI_COMPOSER_H +#include +#include +#include "hdi_layer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiComposition { +public: + HdiComposition() {} + virtual int32_t Init() + { + return DISPLAY_SUCCESS; + }; + virtual int32_t SetLayers(std::vector &layers, HdiLayer &clientLayer) + { + return DISPLAY_SUCCESS; + } + virtual int32_t Apply(bool modeSet) + { + return DISPLAY_SUCCESS; + } + virtual ~HdiComposition() {} + +protected: + std::vector mCompLayers; +}; + +class HdiComposer { +public: + HdiComposer(std::unique_ptr pre, std::unique_ptr post); + virtual ~HdiComposer() {}; + int32_t Prepare(std::vector &layers, HdiLayer &clientLayer); + int32_t Commit(bool modeSet); + HdiComposition *GetPreCompostion() + { + return mPreComp.get(); + } + HdiComposition *GetPostCompostion() + { + return mPostComp.get(); + } + +private: + std::unique_ptr mPreComp; + std::unique_ptr mPostComp; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_COMPOSER_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_device_common.h b/hardware/display/src/display_device/hdi_device_common.h new file mode 100644 index 0000000..c5ceaf0 --- /dev/null +++ b/hardware/display/src/display_device/hdi_device_common.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_DEVICE_COMMON_H +#define HDI_DEVICE_COMMON_H +#include +#include +#include "display_type.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const int32_t INVALID_MODE_ID = -1; +const uint32_t DRM_INVALID_ID = 0xFFFFFFFF; +template using IdMapPtr = std::unordered_map>; +class DrmEncoder; +class DrmCrtc; +class DrmPlane; +class DrmDevice; +class DrmConnector; +class VsyncCallBack; +class DrmVsyncWorker; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_DEVICE_COMMON_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_device_interface.cpp b/hardware/display/src/display_device/hdi_device_interface.cpp new file mode 100644 index 0000000..96e5d2e --- /dev/null +++ b/hardware/display/src/display_device/hdi_device_interface.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_device_interface.h" +#include +#include "display_common.h" +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +std::vector> HdiDeviceInterface::DiscoveryDevice() +{ + DISPLAY_LOGD(); + int ret; + std::vector> devices; + std::shared_ptr drmDevice = DrmDevice::Create(); + if (!drmDevice) { + DISPLAY_LOGE("can not create drm device"); + } + ret = drmDevice->Init(); + if (ret == DISPLAY_SUCCESS) { + DISPLAY_LOGD("drm device init success"); + devices.push_back(std::move(drmDevice)); + } else { + DISPLAY_LOGE("drm device init failed"); + } + return devices; +} +} +} +} \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_device_interface.h b/hardware/display/src/display_device/hdi_device_interface.h new file mode 100644 index 0000000..f5964ef --- /dev/null +++ b/hardware/display/src/display_device/hdi_device_interface.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_DEVICE_INTERFACE_H +#define HDI_DEVICE_INTERFACE_H +#include +#include +#include +#include "hdi_display.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiDeviceInterface { +public: + static std::vector> DiscoveryDevice(); + virtual std::unordered_map> DiscoveryDisplay() = 0; + virtual int32_t Init() = 0; + virtual void DeInit() = 0; + virtual ~HdiDeviceInterface() {}; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_DEVICE_INTERFACE_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_display.cpp b/hardware/display/src/display_device/hdi_display.cpp new file mode 100644 index 0000000..31f0fe8 --- /dev/null +++ b/hardware/display/src/display_device/hdi_display.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_display.h" +#include +#include "display_common.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +uint32_t HdiDisplay::mIdleId = 0; +std::unordered_set HdiDisplay::mIdSets; + +uint32_t HdiDisplay::GetIdleId() +{ + const uint32_t oldIdleId = mIdleId; + uint32_t id = INVALIDE_DISPLAY_ID; + // ensure the mIdleId not INVALIDE_DISPLAY_ID + mIdleId = mIdleId % INVALIDE_DISPLAY_ID; + do { + auto iter = mIdSets.find(mIdleId); + if (iter == mIdSets.end()) { + id = mIdleId; + break; + } + mIdleId = (mIdleId + 1) % INVALIDE_DISPLAY_ID; + } while (oldIdleId != mIdleId); + mIdSets.emplace(id); + mIdleId++; + return id; +} + + +int32_t HdiDisplay::Init() +{ + DISPLAY_LOGD(); + uint32_t id = GetIdleId(); + DISPLAY_CHK_RETURN((id == INVALIDE_DISPLAY_ID), DISPLAY_FAILURE, DISPLAY_LOGE("have no id to used")); + mId = id; + auto layer = CreateHdiLayer(LAYER_TYPE_GRAPHIC); + DISPLAY_CHK_RETURN((layer.get() == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not create hdi layer for client")); + mClientLayer = std::move(layer); + DISPLAY_LOGD("the id is %{public}d", id); + return DISPLAY_SUCCESS; +} + + +HdiDisplay::~HdiDisplay() +{ + mIdSets.erase(mId); +} + +int32_t HdiDisplay::SetLayerZorder(uint32_t layerId, uint32_t zorder) +{ + DISPLAY_LOGD("layerId : %{public}d", layerId); + auto iter = mLayersMap.find(layerId); + DISPLAY_CHK_RETURN((iter == mLayersMap.end()), DISPLAY_FAILURE, + DISPLAY_LOGE("can not find the layer %{public}d", layerId)); + auto layer = mLayersMap[layerId].get(); + if (layer->GetZorder() == zorder) { + DISPLAY_LOGD("zorder no change layerId %{public}d, zorder %{public}d", layerId, zorder); + return DISPLAY_SUCCESS; + } + // reset to sort + auto zRange = mLayers.equal_range(layer); + DISPLAY_LOGD("zorder range : zRange.first %{public}p zRange.second %{public}p", *zRange.first, *zRange.second); + for (auto c = zRange.first; c != zRange.second; c++) { + if (*c == layer) { + mLayers.erase(c); + break; + } + } + layer->SetLayerZorder(zorder); + mLayers.emplace(layer); + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::CreateLayer(const LayerInfo *layerInfo, uint32_t *layerId) +{ + DISPLAY_LOGD(); + int ret; + DISPLAY_CHK_RETURN((layerInfo == nullptr), DISPLAY_PARAM_ERR, DISPLAY_LOGE("LayerInfo is null")); + DISPLAY_CHK_RETURN((layerId == nullptr), DISPLAY_PARAM_ERR, DISPLAY_LOGE("layerId is null")); + auto layer = CreateHdiLayer(layerInfo->type); + DISPLAY_CHK_RETURN((layer.get() == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not create hdi layer")); + ret = layer->Init(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("Layer Init failed")); + *layerId = layer->GetId(); + mLayers.insert(layer.get()); + mLayersMap.emplace(layer->GetId(), std::move(layer)); + DISPLAY_LOGD("mLayers size %{public}zu", mLayers.size()); + DISPLAY_LOGD("mLayerMap size %{public}zu", mLayersMap.size()); + return DISPLAY_SUCCESS; +} + +std::unique_ptr HdiDisplay::CreateHdiLayer(LayerType type) +{ + DISPLAY_LOGD(); + return std::make_unique(type); +} + + +int32_t HdiDisplay::CloseLayer(uint32_t layerId) +{ + DISPLAY_LOGD("layerId %{public}d", layerId); + auto iter = mLayersMap.find(layerId); + DISPLAY_CHK_RETURN((iter == mLayersMap.end()), DISPLAY_FAILURE, + DISPLAY_LOGE("can not find the layer id %{public}d", layerId)); + mLayers.erase(iter->second.get()); + mLayersMap.erase(layerId); + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::GetDisplayCompChange(uint32_t *num, uint32_t *layers, int32_t *type) +{ + DISPLAY_CHK_RETURN((num == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("the num is nullptr")); + *num = mChangeLayers.size(); + if ((layers == nullptr) && (type == nullptr)) { + return DISPLAY_SUCCESS; + } + DISPLAY_LOGD("set the layers and type"); + for (uint32_t i = 0; i < mChangeLayers.size(); i++) { + HdiLayer *layer = mChangeLayers[i]; + if (layers != nullptr) { + *(layers + i) = layer->GetId(); + } + if (type != nullptr) { + *(type + i) = layer->GetCompositionType(); + } + } + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::GetDisplayReleaseFence(uint32_t *num, uint32_t *layers, int32_t *fences) +{ + DISPLAY_CHK_RETURN((num == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("the num is nullptr")); + *num = mLayers.size(); + if ((layers == nullptr) && (fences == nullptr)) { + return DISPLAY_SUCCESS; + } + DISPLAY_LOGD("set the layer fences"); + int i = 0; + for (auto layer : mLayers) { + if (layers != nullptr) { + *(layers + i) = layer->GetId(); + } + if (fences != nullptr) { + *(fences + i) = layer->GetReleaseFenceFd(); + } + DISPLAY_LOGD("layer id %{public}d fencefd %{public}d", layer->GetId(), layer->GetReleaseFenceFd()); + i++; + } + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::PrepareDisplayLayers(bool *needFlushFb) +{ + DISPLAY_LOGD(); + mChangeLayers.clear(); + std::vector layers; + for (auto c : mLayers) { + layers.push_back(c); + } + DISPLAY_LOGD(" mLayers size %{public}zu layers size %{public}zu", mLayers.size(), layers.size()); + + mComposer->Prepare(layers, *mClientLayer); + // get the change layers + for (auto &layer : layers) { + if (layer->GetDeviceSelect() != layer->GetCompositionType()) { + DISPLAY_LOGD("layer change"); + layer->SetLayerCompositionType(layer->GetDeviceSelect()); + } + mChangeLayers.push_back(layer); + } + *needFlushFb = true; + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::Commit(int32_t *fence) +{ + DISPLAY_LOGD(); + mComposer->Commit(false); + *fence = mClientLayer->GetReleaseFenceFd(); + DISPLAY_LOGD("the release fence is %{public}d", *fence); + return DISPLAY_SUCCESS; +} + +int32_t HdiDisplay::SetDisplayClientBuffer(const BufferHandle *buffer, int32_t fence) +{ + mClientLayer->SetLayerBuffer(buffer, fence); + return DISPLAY_SUCCESS; +} + +HdiLayer *HdiDisplay::GetHdiLayer(uint32_t id) +{ + DISPLAY_LOGD("id : %{public}d", id); + auto iter = mLayersMap.find(id); + DISPLAY_CHK_RETURN((iter == mLayersMap.end()), nullptr, DISPLAY_LOGE("can not find the layer %{public}d", id)); + return iter->second.get(); +} + +VsyncCallBack::VsyncCallBack(VBlankCallback cb, void *data) : mVBlankCb(cb), mData(data) +{ + DISPLAY_LOGD("VsyncCallBack %{public}p", cb); +} + +void VsyncCallBack::Vsync(unsigned int sequence, uint64_t ns) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN_NOT_VALUE((mVBlankCb == nullptr), DISPLAY_LOGE("the callback is nullptr")); + mVBlankCb(sequence, ns, mData); +} +} +} +} \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_display.h b/hardware/display/src/display_device/hdi_display.h new file mode 100644 index 0000000..57974c6 --- /dev/null +++ b/hardware/display/src/display_device/hdi_display.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_DISPLAY_H +#define HDI_DISPLAY_H +#include +#include +#include +#include +#include "display_device.h" +#include "hdi_composer.h" +#include "hdi_layer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const uint32_t INVALIDE_DISPLAY_ID = 0xffffffff; +const uint32_t DISPLAY_TYPE_DRM = (1 << 31); + +class VsyncCallBack { +public: + VsyncCallBack(VBlankCallback cb, void *data); + virtual void Vsync(unsigned int sequence, uint64_t ns); + virtual ~VsyncCallBack() {} + +private: + VBlankCallback mVBlankCb; + void *mData; +}; + + +class HdiDisplay { +public: + uint32_t GetId() const + { + return mId; + } + virtual int32_t Init(); + virtual void DeInit() {} + HdiDisplay() {} + virtual ~HdiDisplay(); + virtual int32_t GetDisplayCapability(DisplayCapability *info) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t GetDisplaySuppportedModes(int *num, DisplayModeInfo *modes) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t GetDisplayMode(uint32_t *modeId) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t SetDisplayMode(uint32_t modeId) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t GetDisplayPowerStatus(DispPowerStatus *status) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t SetDisplayPowerStatus(DispPowerStatus status) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t GetDisplayBacklight(uint32_t *value) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t SetDisplayBacklight(uint32_t value) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t CreateLayer(const LayerInfo *layerInfo, uint32_t *layerId); + virtual int32_t CloseLayer(uint32_t layerId); + virtual int32_t PrepareDisplayLayers(bool *needFlushFb); + virtual int32_t Commit(int32_t *fence); + virtual int32_t GetDisplayCompChange(uint32_t *num, uint32_t *layers, int32_t *type); + virtual int32_t SetLayerZorder(uint32_t layerId, uint32_t zorder); + virtual bool IsConnected() + { + return false; + } + virtual int32_t RegDisplayVBlankCallback(VBlankCallback cb, void *data) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t SetDisplayVsyncEnabled(bool enabled) + { + return DISPLAY_NOT_SUPPORT; + } + virtual int32_t GetDisplayReleaseFence(uint32_t *num, uint32_t *layers, int32_t *fences); + virtual int32_t SetDisplayClientBuffer(const BufferHandle *buffer, int32_t fence); + virtual int32_t WaitForVBlank(uint64_t *ns) + { + return DISPLAY_NOT_SUPPORT; + } + HdiLayer *GetHdiLayer(uint32_t id); + +protected: + virtual std::unique_ptr CreateHdiLayer(LayerType type); + std::unique_ptr mComposer; + + static uint32_t GetIdleId(); + static uint32_t mIdleId; + static std::unordered_set mIdSets; + uint32_t mId = INVALIDE_DISPLAY_ID; + std::unordered_map> mLayersMap; + std::multiset mLayers; + std::unique_ptr mClientLayer; + std::vector mChangeLayers; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_DISPLAY_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_drm_composition.cpp b/hardware/display/src/display_device/hdi_drm_composition.cpp new file mode 100644 index 0000000..b091663 --- /dev/null +++ b/hardware/display/src/display_device/hdi_drm_composition.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_drm_composition.h" +#include +#include "hdi_drm_layer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +HdiDrmComposition::HdiDrmComposition(std::shared_ptr connector, std::shared_ptr crtc, + std::shared_ptr drmDevice) + : mDrmDevice(drmDevice), mConnector(connector), mCrtc(crtc) +{ + DISPLAY_LOGD(); +} + +int32_t HdiDrmComposition::Init() +{ + DISPLAY_LOGD(); + mPrimPlanes.clear(); + mOverlayPlanes.clear(); + mPlanes.clear(); + DISPLAY_CHK_RETURN((mCrtc == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("crtc is null")); + DISPLAY_CHK_RETURN((mConnector == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("connector is null")); + DISPLAY_CHK_RETURN((mDrmDevice == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("drmDevice is null")); + mPrimPlanes = mDrmDevice->GetDrmPlane(mCrtc->GetPipe(), DRM_PLANE_TYPE_PRIMARY); + mOverlayPlanes = mDrmDevice->GetDrmPlane(mCrtc->GetPipe(), DRM_PLANE_TYPE_OVERLAY); + DISPLAY_CHK_RETURN((mPrimPlanes.size() == 0), DISPLAY_FAILURE, DISPLAY_LOGE("has no primary plane")); + mPlanes.insert(mPlanes.end(), mPrimPlanes.begin(), mPrimPlanes.end()); + mPlanes.insert(mPlanes.end(), mOverlayPlanes.begin(), mOverlayPlanes.end()); + return DISPLAY_SUCCESS; +} + +int32_t HdiDrmComposition::SetLayers(std::vector &layers, HdiLayer &clientLayer) +{ + // now we do not surpport present direct + DISPLAY_LOGD(); + mCompLayers.clear(); + mCompLayers.push_back(&clientLayer); + return DISPLAY_SUCCESS; +} + +int32_t HdiDrmComposition::ApplyPlane(HdiDrmLayer &layer, DrmPlane &drmPlane, drmModeAtomicReqPtr pset) +{ + // set fence in + int ret; + int fenceFd = layer.GetAcquireFenceFd(); + int propId = drmPlane.GetPropFenceInId(); + DISPLAY_LOGD(); + if (propId != 0) { + DISPLAY_LOGD("set the fence in prop"); + if (fenceFd >= 0) { + ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), propId, fenceFd); + DISPLAY_LOGD("set the IfenceProp plane id %{public}d, propId %{public}d, fenceFd %{public}d", + drmPlane.GetId(), propId, fenceFd); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set IN_FENCE_FD failed")); + } + } + + // set fb id + DrmGemBuffer *gemBuffer = layer.GetGemBuffer(); + DISPLAY_CHK_RETURN((gemBuffer == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("current gemBuffer is nullptr")); + DISPLAY_CHK_RETURN((!gemBuffer->IsValid()), DISPLAY_FAILURE, DISPLAY_LOGE("the DrmGemBuffer is invalid")); + ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), drmPlane.GetPropFbId(), gemBuffer->GetFbId()); + DISPLAY_LOGD("set the fb planeid %{public}d, propId %{public}d, fbId %{public}d", drmPlane.GetId(), + drmPlane.GetPropFbId(), gemBuffer->GetFbId()); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set fb id fialed errno : %{public}d", errno)); + + // set crtc id + ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), drmPlane.GetPropCrtcId(), mCrtc->GetId()); + DISPLAY_LOGD("set the crtc planeId %{public}d, propId %{public}d, crtcId %{public}d", drmPlane.GetId(), + drmPlane.GetPropCrtcId(), mCrtc->GetId()); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set crtc id fialed errno : %{public}d", errno)); + return DISPLAY_SUCCESS; +} + +int32_t HdiDrmComposition::UpdateMode(std::unique_ptr &modeBlock, drmModeAtomicReq &pset) +{ + // set the mode + DISPLAY_LOGD(); + if (mCrtc->NeedModeSet()) { + modeBlock = mConnector->GetModeBlockFromId(mCrtc->GetActiveModeId()); + if ((modeBlock != nullptr) && (modeBlock->GetBlockId() != DRM_INVALID_ID)) { + // set to active + DISPLAY_LOGD("set crtc to active"); + int ret = drmModeAtomicAddProperty(&pset, mCrtc->GetId(), mCrtc->GetActivePropId(), 1); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, + DISPLAY_LOGE("can not add the active prop errno %{public}d", errno)); + + // set the mode id + DISPLAY_LOGD("set the mode"); + ret = drmModeAtomicAddProperty(&pset, mCrtc->GetId(), mCrtc->GetModePropId(), modeBlock->GetBlockId()); + DISPLAY_LOGD("set the mode planeId %{public}d, propId %{public}d, GetBlockId: %{public}d", mCrtc->GetId(), + mCrtc->GetModePropId(), modeBlock->GetBlockId()); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, + DISPLAY_LOGE("can not add the mode prop errno %{public}d", errno)); + ret = drmModeAtomicAddProperty(&pset, mConnector->GetId(), mConnector->GetPropCrtcId(), mCrtc->GetId()); + DISPLAY_LOGD("set the connector id: %{public}d, propId %{public}d, crtcId %{public}d", mConnector->GetId(), + mConnector->GetPropCrtcId(), mCrtc->GetId()); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, + DISPLAY_LOGE("can not add the crtc id prop %{public}d", errno)); + } + } + return DISPLAY_SUCCESS; +} + +int32_t HdiDrmComposition::Apply(bool modeSet) +{ + uint64_t crtcOutFence = -1; + int ret; + std::unique_ptr modeBlock; + int drmFd = mDrmDevice->GetDrmFd(); + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((mPlanes.size() < mCompLayers.size()), DISPLAY_FAILURE, DISPLAY_LOGE("plane not enough")); + drmModeAtomicReqPtr pset = drmModeAtomicAlloc(); + DISPLAY_CHK_RETURN((pset == nullptr), DISPLAY_NULL_PTR, + DISPLAY_LOGE("drm atomic alloc failed errno %{public}d", errno)); + AtomicReqPtr atomicReqPtr = AtomicReqPtr(pset); + + // set the outFence property + ret = drmModeAtomicAddProperty(atomicReqPtr.Get(), mCrtc->GetId(), mCrtc->GetOutFencePropId(), + (uint64_t)&crtcOutFence); + + DISPLAY_LOGD("Apply Set OutFence crtc id: %{public}d, fencePropId %{public}d", mCrtc->GetId(), + mCrtc->GetOutFencePropId()); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set the outfence property of crtc failed ")); + + // set the plane info. + DISPLAY_LOGD("mCompLayers size %{public}zd", mCompLayers.size()); + for (uint32_t i = 0; i < mCompLayers.size(); i++) { + HdiDrmLayer *layer = static_cast(mCompLayers[i]); + auto &drmPlane = mPlanes[i]; + ret = ApplyPlane(*layer, *drmPlane, atomicReqPtr.Get()); + if (ret != DISPLAY_SUCCESS) { + DISPLAY_LOGE("apply plane failed"); + break; + } + } + ret = UpdateMode(modeBlock, *(atomicReqPtr.Get())); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("update mode failed")); + uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; + + ret = drmModeAtomicCommit(drmFd, atomicReqPtr.Get(), flags, nullptr); + DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, + DISPLAY_LOGE("drmModeAtomicCommit failed %{public}d errno %{public}d", ret, errno)); + // set the release fence + for (auto layer : mCompLayers) { + layer->SetReleaseFence(static_cast(crtcOutFence)); + } + + return DISPLAY_SUCCESS; +} +} // OHOS +} // HDI +} // DISPLAY diff --git a/hardware/display/src/display_device/hdi_drm_composition.h b/hardware/display/src/display_device/hdi_drm_composition.h new file mode 100644 index 0000000..877897e --- /dev/null +++ b/hardware/display/src/display_device/hdi_drm_composition.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_DRM_COMPOSITION_H +#define HDI_DRM_COMPOSITION_H +#include +#include +#include +#include +#include "drm_device.h" +#include "hdi_composer.h" +#include "hdi_device_common.h" +#include "hdi_drm_layer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class AtomicReqPtr { +public: + explicit AtomicReqPtr(drmModeAtomicReqPtr ptr) : mPtr(ptr) {} + virtual ~AtomicReqPtr() + { + if (mPtr != nullptr) + drmModeAtomicFree(mPtr); + } + drmModeAtomicReqPtr Get() const + { + return mPtr; + } + +private: + drmModeAtomicReqPtr mPtr; +}; + +class HdiDrmComposition : public HdiComposition { +public: + HdiDrmComposition(std::shared_ptr connector, std::shared_ptr crtc, + std::shared_ptr drmDevice); + virtual ~HdiDrmComposition() {} + int32_t Init(); + int32_t SetLayers(std::vector &layers, HdiLayer &clientLayer); + int32_t Apply(bool modeSet); + int32_t UpdateMode(std::unique_ptr &modeBlock, drmModeAtomicReq &pset); + +private: + int32_t ApplyPlane(HdiDrmLayer &layer, DrmPlane &drmPlane, drmModeAtomicReqPtr pset); + std::shared_ptr mDrmDevice; + std::shared_ptr mConnector; + std::shared_ptr mCrtc; + std::vector> mPrimPlanes; + std::vector> mOverlayPlanes; + std::vector> mPlanes; +}; +} // OHOS +} // HDI +} // DISPLAY + +#endif // HDI_DRM_COMPOSITION_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_drm_layer.cpp b/hardware/display/src/display_device/hdi_drm_layer.cpp new file mode 100644 index 0000000..97040c4 --- /dev/null +++ b/hardware/display/src/display_device/hdi_drm_layer.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_drm_layer.h" +#include +#include +#include "drm_device.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +DrmGemBuffer::DrmGemBuffer(int drmfd, HdiLayerBuffer &hdl) : mDrmFd(drmfd) +{ + DISPLAY_LOGD(); + Init(mDrmFd, hdl); +} + +void DrmGemBuffer::Init(int drmFd, HdiLayerBuffer &hdl) +{ + int ret; + const int MAX_COUNT = 4; + uint32_t pitches[MAX_COUNT] = {0}; + uint32_t gemHandles[MAX_COUNT] = {0}; + uint32_t offsets[MAX_COUNT] = {0}; + DISPLAY_LOGD("hdl %{public}" PRIx64 "", hdl.GetPhysicalAddr()); + DISPLAY_CHK_RETURN_NOT_VALUE((drmFd < 0), DISPLAY_LOGE("can not init drmfd %{public}d", drmFd)); + mDrmFormat = DrmDevice::ConvertToDrmFormat(static_cast(hdl.GetFormat())); + ret = drmPrimeFDToHandle(drmFd, hdl.GetFb(), &mGemHandle); + DISPLAY_CHK_RETURN_NOT_VALUE((ret != 0), DISPLAY_LOGE("can not get handle errno %{public}d", errno)); + + pitches[0] = hdl.GetStride(); + gemHandles[0] = mGemHandle; + offsets[0] = 0; + ret = drmModeAddFB2(drmFd, hdl.GetWight(), hdl.GetHeight(), mDrmFormat, gemHandles, pitches, offsets, &mFdId, 0); + DISPLAY_LOGD("mGemHandle %{public}d mFdId %{public}d", mGemHandle, mFdId); + DISPLAY_LOGD("w: %{public}d h: %{public}d mDrmFormat : %{public}d gemHandles: %{public}d pitches: %{public}d " + "offsets: %{public}d", + hdl.GetWight(), hdl.GetHeight(), mDrmFormat, gemHandles[0], pitches[0], offsets[0]); + DISPLAY_CHK_RETURN_NOT_VALUE((ret != 0), DISPLAY_LOGE("can not add fb errno %{public}d", errno)); +} + +DrmGemBuffer::~DrmGemBuffer() +{ + DISPLAY_LOGD(); + if (mFdId) { + if (drmModeRmFB(mDrmFd, mFdId)) { + DISPLAY_LOGE("can not free fdid %{public}d errno %{public}d", mFdId, errno); + } + } + + if (mGemHandle) { + struct drm_gem_close gemClose = { 0 }; + gemClose.handle = mGemHandle; + if (drmIoctl(mDrmFd, DRM_IOCTL_GEM_CLOSE, &gemClose)) { + DISPLAY_LOGE("can not free gem handle %{public}d errno : %{public}d", mGemHandle, errno); + } + } +} + +bool DrmGemBuffer::IsValid() +{ + DISPLAY_LOGD(); + return (mGemHandle != INVALID_DRM_ID) && (mFdId != INVALID_DRM_ID); +} + +DrmGemBuffer *HdiDrmLayer::GetGemBuffer() +{ + DISPLAY_LOGD(); + std::unique_ptr ptr = std::make_unique(DrmDevice::GetDrmFd(), *GetCurrentBuffer()); + mLastBuffer = std::move(mCurrentBuffer); + mCurrentBuffer = std::move(ptr); + return mCurrentBuffer.get(); +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/hdi_drm_layer.h b/hardware/display/src/display_device/hdi_drm_layer.h new file mode 100644 index 0000000..97c3e98 --- /dev/null +++ b/hardware/display/src/display_device/hdi_drm_layer.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_DRM_LAYER_H +#define HDI_DRM_LAYER_H +#include +#include +#include "buffer_handle.h" +#include "display_common.h" +#include "drm_fourcc.h" +#include "hdi_layer.h" +#include "hdi_device_common.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const int INVALID_DRM_ID = 0; +class DrmGemBuffer { +public: + DrmGemBuffer(int drmFd, HdiLayerBuffer &hdl); + virtual ~DrmGemBuffer(); + uint32_t GetFbId() const + { + return mFdId; + } + bool IsValid(); + +private: + void Init(int drmFd, HdiLayerBuffer &hdl); + uint32_t mGemHandle = 0; + uint32_t mFdId = 0; + int mDrmFd = -1; // the fd can not close. the other module will close it. + uint32_t mDrmFormat = DRM_FORMAT_INVALID; +}; + +class HdiDrmLayer : public HdiLayer { +public: + explicit HdiDrmLayer(LayerType type) : HdiLayer(type) {} + virtual ~HdiDrmLayer() {} + // Return value optimization + DrmGemBuffer *GetGemBuffer(); + +private: + std::unique_ptr mCurrentBuffer; + std::unique_ptr mLastBuffer; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_DRM_LAYER_H diff --git a/hardware/display/src/display_device/hdi_gfx_composition.cpp b/hardware/display/src/display_device/hdi_gfx_composition.cpp new file mode 100644 index 0000000..69feb16 --- /dev/null +++ b/hardware/display/src/display_device/hdi_gfx_composition.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_gfx_composition.h" +#include +#include +#include +#include "display_gfx.h" + +#define LIB_HDI_GFX_NAME "libdisplay_gfx.z.so" +#define LIB_GFX_FUNC_INIT "GfxInitialize" +#define LIB_GFX_FUNC_DEINIT "GfxUninitialize" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +int32_t HdiGfxComposition::Init(void) +{ + DISPLAY_LOGD(); + int32_t ret = GfxModuleInit(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS) || (mGfxFuncs == nullptr), DISPLAY_FAILURE, + DISPLAY_LOGE("GfxModuleInit failed")); + ret = mGfxFuncs->InitGfx(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("gfx init failed")); + return DISPLAY_SUCCESS; +} + +int32_t HdiGfxComposition::GfxModuleInit(void) +{ + DISPLAY_LOGD(); + mGfxModule = dlopen(LIB_HDI_GFX_NAME, RTLD_NOW | RTLD_NOLOAD); + if (mGfxModule != nullptr) { + DISPLAY_LOGI("Module '%{public}s' already loaded", LIB_HDI_GFX_NAME); + } else { + DISPLAY_LOGI("Loading module '%{public}s'", LIB_HDI_GFX_NAME); + mGfxModule = dlopen(LIB_HDI_GFX_NAME, RTLD_NOW); + if (mGfxModule == nullptr) { + DISPLAY_LOGE("Failed to load module: %{public}s", dlerror()); + return DISPLAY_FAILURE; + } + } + + using InitFunc = int32_t (*)(GfxFuncs **funcs); + InitFunc func = reinterpret_cast(dlsym(mGfxModule, LIB_GFX_FUNC_INIT)); + if (func == nullptr) { + DISPLAY_LOGE("Failed to lookup %{public}s function: %s", LIB_GFX_FUNC_INIT, dlerror()); + dlclose(mGfxModule); + return DISPLAY_FAILURE; + } + return func(&mGfxFuncs); +} + +int32_t HdiGfxComposition::GfxModuleDeinit(void) +{ + DISPLAY_LOGD(); + int32_t ret = DISPLAY_SUCCESS; + if (mGfxModule == nullptr) { + using DeinitFunc = int32_t (*)(GfxFuncs *funcs); + DeinitFunc func = reinterpret_cast(dlsym(mGfxModule, LIB_GFX_FUNC_DEINIT)); + if (func == nullptr) { + DISPLAY_LOGE("Failed to lookup %{public}s function: %s", LIB_GFX_FUNC_DEINIT, dlerror()); + } else { + ret = func(mGfxFuncs); + } + dlclose(mGfxModule); + } + return ret; +} + +bool HdiGfxComposition::CanHandle(HdiLayer &hdiLayer) +{ + DISPLAY_LOGD(); + (void)hdiLayer; + return true; +} + +int32_t HdiGfxComposition::SetLayers(std::vector &layers, HdiLayer &clientLayer) +{ + DISPLAY_LOGD("layers size %{public}zd", layers.size()); + mClientLayer = &clientLayer; + mCompLayers.clear(); + for (auto &layer : layers) { + if (CanHandle(*layer)) { + if ((layer->GetCompositionType() != COMPOSITION_VIDEO) && + (layer->GetCompositionType() != COMPOSITION_CURSOR)) { + layer->SetDeviceSelect(COMPOSITION_DEVICE); + } else { + layer->SetDeviceSelect(layer->GetCompositionType()); + } + mCompLayers.push_back(layer); + } + } + DISPLAY_LOGD("composer layers size %{public}zd", mCompLayers.size()); + return DISPLAY_SUCCESS; +} + +void HdiGfxComposition::InitGfxSurface(ISurface &surface, HdiLayerBuffer &buffer) +{ + surface.width = buffer.GetWight(); + surface.height = buffer.GetHeight(); + surface.phyAddr = buffer.GetFb();//buffer.GetPhysicalAddr(); +// surface.fd = buffer.GetFb(); + surface.enColorFmt = (PixelFormat)buffer.GetFormat(); + surface.stride = buffer.GetStride(); + surface.bAlphaExt1555 = true; + surface.bAlphaMax255 = true; + surface.alpha0 = 0XFF; + surface.alpha1 = 0XFF; + DISPLAY_LOGD("surface fd %{public}d w:%{public}d h:%{public}d addr:0x%{public}" PRIx64 " fmt:%{public}d stride:%{public}d", + buffer.GetFb(), surface.width, surface.height, buffer.GetPhysicalAddr(), surface.enColorFmt, surface.stride); +} + +// now not handle the alpha of layer +int32_t HdiGfxComposition::BlitLayer(HdiLayer &src, HdiLayer &dst) +{ + ISurface srcSurface = { 0 }; + ISurface dstSurface = { 0 }; + GfxOpt opt = { 0 }; + DISPLAY_LOGD(); + HdiLayerBuffer *srcBuffer = src.GetCurrentBuffer(); + DISPLAY_CHK_RETURN((srcBuffer == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("the srcbuffer is null")); + DISPLAY_LOGD("init the src surface"); + InitGfxSurface(srcSurface, *srcBuffer); + + HdiLayerBuffer *dstBuffer = dst.GetCurrentBuffer(); + DISPLAY_CHK_RETURN((dstBuffer == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not get client layer buffer")); + DISPLAY_LOGD("init the dst surface"); + InitGfxSurface(dstSurface, *dstBuffer); + + opt.blendType = src.GetLayerBlenType(); + DISPLAY_LOGD("blendType %{public}d", opt.blendType); + opt.enPixelAlpha = true; + opt.enableScale = true; + + if (src.GetAlpha().enGlobalAlpha) { // is alpha is 0xff we not set it + opt.enGlobalAlpha = true; + srcSurface.alpha0 = src.GetAlpha().gAlpha; + DISPLAY_LOGD("src alpha %{public}x", src.GetAlpha().gAlpha); + } + opt.rotateType = src.GetTransFormType(); + DISPLAY_LOGD(" the roate type is %{public}d", opt.rotateType); + IRect crop = src.GetLayerCrop(); + IRect displayRect = src.GetLayerDisplayRect(); + DISPLAY_LOGD("crop x: %{public}d y : %{public}d w : %{public}d h: %{public}d", crop.x, crop.y, crop.w, crop.h); + DISPLAY_LOGD("displayRect x: %{public}d y : %{public}d w : %{public}d h : %{public}d", displayRect.x, displayRect.y, + displayRect.w, displayRect.h); + DISPLAY_CHK_RETURN(mGfxFuncs == nullptr, DISPLAY_FAILURE, DISPLAY_LOGE("Blit: mGfxFuncs is null")); + return mGfxFuncs->Blit(&srcSurface, &crop, &dstSurface, &displayRect, &opt); +} + +int32_t HdiGfxComposition::ClearRect(HdiLayer &src, HdiLayer &dst) +{ + ISurface dstSurface = { 0 }; + GfxOpt opt = { 0 }; + DISPLAY_LOGD(); + HdiLayerBuffer *dstBuffer = dst.GetCurrentBuffer(); + DISPLAY_CHK_RETURN((dstBuffer == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not get client layer buffer")); + InitGfxSurface(dstSurface, *dstBuffer); + IRect rect = src.GetLayerDisplayRect(); + DISPLAY_CHK_RETURN(mGfxFuncs == nullptr, DISPLAY_FAILURE, DISPLAY_LOGE("Rect: mGfxFuncs is null")); + return mGfxFuncs->FillRect(&dstSurface, &rect, 0, &opt); +} + +int32_t HdiGfxComposition::Apply(bool modeSet) +{ + int32_t ret; + DISPLAY_LOGD("composer layers size %{public}zd", mCompLayers.size()); + for (uint32_t i = 0; i < mCompLayers.size(); i++) { + HdiLayer *layer = mCompLayers[i]; + CompositionType compType = layer->GetCompositionType(); + switch (compType) { + case COMPOSITION_VIDEO: + ret = ClearRect(*layer, *mClientLayer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, + DISPLAY_LOGE("clear layer %{public}d failed", i)); + break; + case COMPOSITION_DEVICE: + ret = BlitLayer(*layer, *mClientLayer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, + DISPLAY_LOGE("blit layer %{public}d failed ", i)); + break; + default: + DISPLAY_LOGE("the gfx composition can not surpport the type %{public}d", compType); + break; + } + } + return DISPLAY_SUCCESS; +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/hdi_gfx_composition.h b/hardware/display/src/display_device/hdi_gfx_composition.h new file mode 100644 index 0000000..7d09be4 --- /dev/null +++ b/hardware/display/src/display_device/hdi_gfx_composition.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_GFX_COMPOSITION_H +#define HDI_GFX_COMPOSITION_H +#include "display_gfx.h" +#include "hdi_composer.h" +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiGfxComposition : public HdiComposition { +public: + int32_t Init(void) override; + int32_t SetLayers(std::vector &layers, HdiLayer &clientLayer) override; + int32_t Apply(bool modeSet) override; + virtual ~HdiGfxComposition() + { + (void)GfxModuleDeinit(); + } + +private: + bool CanHandle(HdiLayer &hdiLayer); + void InitGfxSurface(ISurface &surface, HdiLayerBuffer &buffer); + int32_t BlitLayer(HdiLayer &src, HdiLayer &dst); + int32_t ClearRect(HdiLayer &src, HdiLayer &dst); + int32_t GfxModuleInit(void); + int32_t GfxModuleDeinit(void); + void *mGfxModule = nullptr; + GfxFuncs *mGfxFuncs = nullptr; + HdiLayer *mClientLayer; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_GFX_COMPOSITION_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_layer.cpp b/hardware/display/src/display_device/hdi_layer.cpp new file mode 100644 index 0000000..e862e59 --- /dev/null +++ b/hardware/display/src/display_device/hdi_layer.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_layer.h" +#include +#include + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +uint32_t HdiLayer::mIdleId = 0; +std::unordered_set HdiLayer::mIdSets; + +HdiLayerBuffer::HdiLayerBuffer(const BufferHandle &hdl) + : mPhyAddr(hdl.phyAddr), mHeight(hdl.height), mWidth(hdl.width), mStride(hdl.stride), mFormat(hdl.format) +{ + DISPLAY_LOGD(); + mFd = dup(hdl.fd); + mHandle = hdl; + if (mFd < 0) { + DISPLAY_LOGE("the fd : %{public}d dup failed errno %{public}d", hdl.fd, errno); + } +} + +HdiLayerBuffer::~HdiLayerBuffer() +{ + DISPLAY_LOGD(); + if (mFd >= 0) { + close(mFd); + } +} + +HdiLayerBuffer &HdiLayerBuffer::operator = (const BufferHandle &right) +{ + DISPLAY_LOGD(); + if (mFd >= 0) { + close(mFd); + } + mFd = right.fd; + mPhyAddr = right.phyAddr; + mWidth = right.width; + mHeight = right.height; + mStride = right.stride; + mFormat = right.format; + return *this; +} + +uint32_t HdiLayer::GetIdleId() +{ + const uint32_t oldIdleId = mIdleId; + uint32_t id = INVALIDE_LAYER_ID; + // ensure the mIdleId not INVALIDE_LAYER_ID + mIdleId = mIdleId % INVALIDE_LAYER_ID; + do { + auto iter = mIdSets.find(mIdleId); + if (iter == mIdSets.end()) { + id = mIdleId; + break; + } + mIdleId = (mIdleId + 1) % INVALIDE_LAYER_ID; + } while (oldIdleId != mIdleId); + mIdSets.emplace(id); + mIdleId++; + DISPLAY_LOGD("id %{public}d mIdleId %{public}d", id, mIdleId); + return id; +} + +int32_t HdiLayer::Init() +{ + DISPLAY_LOGD(); + uint32_t id = GetIdleId(); + DISPLAY_CHK_RETURN((id == INVALIDE_LAYER_ID), DISPLAY_FAILURE, DISPLAY_LOGE("have no id to used")); + mId = id; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerSize(IRect *rect) +{ + DISPLAY_CHK_RETURN((rect == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in rect is nullptr")); + DISPLAY_LOGD(" displayRect x: %{public}d y : %{public}d w : %{public}d h : %{public}d", rect->x, rect->y, rect->w, + rect->h); + mDisplayRect = *rect; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerCrop(IRect *rect) +{ + DISPLAY_CHK_RETURN((rect == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in rect is nullptr")); + DISPLAY_LOGD("id : %{public}d crop x: %{public}d y : %{public}d w : %{public}d h : %{public}d", mId, rect->x, + rect->y, rect->w, rect->h); + mCrop = *rect; + return DISPLAY_SUCCESS; +} + +void HdiLayer::SetLayerZorder(uint32_t zorder) +{ + DISPLAY_LOGD("id : %{public}d zorder : %{public}d ", mId, zorder); + mZorder = zorder; +} + +int32_t HdiLayer::SetLayerPreMulti(bool preMul) +{ + DISPLAY_LOGD(); + mPreMul = preMul; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerAlpha(LayerAlpha *alpha) +{ + DISPLAY_CHK_RETURN((alpha == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in alpha is nullptr")); + DISPLAY_LOGD("enable alpha %{public}d galpha 0x%{public}x", alpha->enGlobalAlpha, alpha->gAlpha); + mAlpha = *alpha; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetTransformMode(TransformType type) +{ + DISPLAY_LOGD("TransformType %{public}d", type); + mTransformType = type; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerDirtyRegion(IRect *region) +{ + DISPLAY_CHK_RETURN((region == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("the in rect is null")); + DISPLAY_LOGD("id : %{public}d DirtyRegion x: %{public}d y : %{public}d w : %{public}d h : %{public}d", mId, + region->x, region->y, region->w, region->h); + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerVisibleRegion(uint32_t num, IRect *rect) +{ + DISPLAY_LOGD("id : %{public}d DirtyRegion x: %{public}d y : %{public}d w : %{public}d h : %{public}d", mId, rect->x, + rect->y, rect->w, rect->h); + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerBuffer(const BufferHandle *buffer, int32_t fence) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((buffer == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("buffer is nullptr")); + std::unique_ptr layerbuffer = std::make_unique(*buffer); + mHdiBuffer = std::move(layerbuffer); + mAcquireFence = fence; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerCompositionType(CompositionType type) +{ + DISPLAY_LOGD("CompositionType type %{public}d", type); + mCompositionType = type; + return DISPLAY_SUCCESS; +} + +int32_t HdiLayer::SetLayerBlendType(BlendType type) +{ + DISPLAY_LOGD("BlendType type %{public}d", type); + mBlendType = type; + return DISPLAY_SUCCESS; +} + +void HdiLayer::SetPixel(const BufferHandle &handle, int x, int y, uint32_t color) +{ + const int32_t pixelBytes = 4; + DISPLAY_CHK_RETURN_NOT_VALUE((handle.format <= 0), + DISPLAY_LOGE("CheckPixel do not support format %{public}d", handle.format)); + DISPLAY_CHK_RETURN_NOT_VALUE((handle.virAddr == nullptr), DISPLAY_LOGE("CheckPixel viraddr is null must map it")); + DISPLAY_CHK_RETURN_NOT_VALUE((x < 0 || x >= handle.width), + DISPLAY_LOGE("CheckPixel invalid parameter x:%{public}d width:%{public}d", x, handle.width)); + DISPLAY_CHK_RETURN_NOT_VALUE((y < 0 || y >= handle.height), + DISPLAY_LOGE("CheckPixel invalid parameter y:%{public}d height:%{public}d", y, handle.height)); + int32_t position = y * handle.width + x; + if ((position * pixelBytes) > handle.size) { + DISPLAY_LOGE("the pixel postion outside\n"); + } + uint32_t *pixel = reinterpret_cast(handle.virAddr) + position; + *pixel = color; +} + +void HdiLayer::ClearColor(uint32_t color) +{ + DISPLAY_LOGD(); + BufferHandle &handle = mHdiBuffer->mHandle; + for (int32_t x = 0; x < handle.width; x++) { + for (int32_t y = 0; y < handle.height; y++) { + SetPixel(handle, x, y, color); + } + } +} +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY diff --git a/hardware/display/src/display_device/hdi_layer.h b/hardware/display/src/display_device/hdi_layer.h new file mode 100644 index 0000000..b4042cb --- /dev/null +++ b/hardware/display/src/display_device/hdi_layer.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_LAYER_H +#define HDI_LAYER_H +#include +#include +#include "buffer_handle.h" +#include "display_common.h" +#include "hdi_device_common.h" +#include "hdi_shared_fd.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +const uint32_t INVALIDE_LAYER_ID = 0xffffffff; +struct HdiLayerBuffer { +public: + explicit HdiLayerBuffer(const BufferHandle &hdl); + virtual ~HdiLayerBuffer(); + HdiLayerBuffer &operator = (const BufferHandle &right); + uint64_t GetPhysicalAddr() const + { + return mPhyAddr; + } + int32_t GetHeight() const + { + return mHeight; + } + int32_t GetWight() const + { + return mWidth; + } + int32_t GetStride() const + { + return mStride; + } + int32_t GetFormat() const + { + return mFormat; + } + int GetFb() const + { + return mFd; + } + BufferHandle mHandle; + +private: + uint64_t mPhyAddr = 0; + int32_t mHeight = 0; + int32_t mWidth = 0; + int32_t mStride = 0; + int32_t mFormat = 0; + int mFd = -1; +}; + +class HdiLayer { +public: + explicit HdiLayer(LayerType type) : mType(type) {} + int32_t Init(); + uint32_t GetId() const + { + return mId; + } + uint32_t GetZorder() const + { + return mZorder; + } + const IRect &GetLayerDisplayRect() const + { + return mDisplayRect; + } + const IRect &GetLayerCrop() const + { + return mCrop; + } + bool GetLayerPreMulti() const + { + return mPreMul; + } + const LayerAlpha &GetAlpha() const + { + return mAlpha; + } + LayerType GetType() const + { + return mType; + } + TransformType GetTransFormType() const + { + return mTransformType; + } + BlendType GetLayerBlenType() const + { + return mBlendType; + } + CompositionType GetCompositionType() const + { + return mCompositionType; + } + void SetDeviceSelect(CompositionType type) + { + DISPLAY_LOGD("%{public}d", type); + mDeviceSelect = type; + } + CompositionType GetDeviceSelect() const + { + return mDeviceSelect; + } + + int GetAcquireFenceFd() + { + return mAcquireFence.GetFd(); + } + int GetReleaseFenceFd() + { + return mReleaseFence.GetFd(); + } + void SetReleaseFence(int fd) + { + mReleaseFence = fd; + }; + void ClearColor(uint32_t color); + + void SetPixel(const BufferHandle &handle, int x, int y, uint32_t color); + + virtual int32_t SetLayerSize(IRect *rect); + virtual int32_t SetLayerCrop(IRect *rect); + virtual void SetLayerZorder(uint32_t zorder); + virtual int32_t SetLayerPreMulti(bool preMul); + virtual int32_t SetLayerAlpha(LayerAlpha *alpha); + virtual int32_t SetTransformMode(TransformType type); + virtual int32_t SetLayerDirtyRegion(IRect *region); + virtual int32_t SetLayerVisibleRegion(uint32_t num, IRect *rect); + virtual int32_t SetLayerBuffer(const BufferHandle *buffer, int32_t fence); + virtual int32_t SetLayerCompositionType(CompositionType type); + virtual int32_t SetLayerBlendType(BlendType type); + virtual HdiLayerBuffer *GetCurrentBuffer() + { + return mHdiBuffer.get(); + } + virtual ~HdiLayer() {} + +private: + static uint32_t GetIdleId(); + static uint32_t mIdleId; + static std::unordered_set mIdSets; + + uint32_t mId = 0; + HdiFd mAcquireFence; + HdiFd mReleaseFence; + LayerType mType; + + IRect mDisplayRect; + IRect mCrop; + uint32_t mZorder = -1; + bool mPreMul = false; + LayerAlpha mAlpha; + TransformType mTransformType; + CompositionType mCompositionType = COMPOSITION_CLIENT; + CompositionType mDeviceSelect = COMPOSITION_CLIENT; + BlendType mBlendType; + std::unique_ptr mHdiBuffer; +}; + +struct SortLayersByZ { + bool operator () (const HdiLayer *lhs, const HdiLayer *rhs) const + { + if (lhs == nullptr || rhs == nullptr) { + return (lhs == nullptr) && (rhs == nullptr); + } + return lhs->GetZorder() < rhs->GetZorder(); + } +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_LAYER_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_netlink_monitor.cpp b/hardware/display/src/display_device/hdi_netlink_monitor.cpp new file mode 100644 index 0000000..5a58290 --- /dev/null +++ b/hardware/display/src/display_device/hdi_netlink_monitor.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hdi_netlink_monitor.h" +#include +#include + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +HdiNetLinkMonitor::HdiNetLinkMonitor() +{ + DISPLAY_LOGD(); +} + +int HdiNetLinkMonitor::Init() +{ + DISPLAY_LOGD(); + int fd; + struct sockaddr_nl snl = { 0 }; + int ret; + const int32_t bufferSize = 1024; + DISPLAY_CHK_RETURN((mScoketFd < 0), DISPLAY_FAILURE, DISPLAY_LOGE("the socket has initial")); + fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + DISPLAY_CHK_RETURN((fd < 0), DISPLAY_FAILURE, DISPLAY_LOGE("scoket create failed")); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); + ret = bind(fd, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl)); + DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("bind failed errno : %{public}d", errno)); + + mThread = std::make_unique([this]() { + mRunning = true; + MonitorThread(); + }); + mScoketFd = fd; + return DISPLAY_SUCCESS; +} + +HdiNetLinkMonitor::~HdiNetLinkMonitor() +{ + DISPLAY_LOGD(); + if (mScoketFd >= 0) { + close(mScoketFd); + } +} + +Void HdiNetLinkMonitor::MonitorThread() +{ + DISPLAY_LOGD(); + constexpr int BUFFER_SIZE = UEVENT_BUFFER_SIZE * 2; + while (mRunning) { + char buf[BUFFER_SIZE] = { 0 }; + recv(mScoketFd, &buf, sizeof(buf), 0); + } +} +} //DISPLAY +} //HDI +} //OHOS \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_netlink_monitor.h b/hardware/display/src/display_device/hdi_netlink_monitor.h new file mode 100644 index 0000000..f46af61 --- /dev/null +++ b/hardware/display/src/display_device/hdi_netlink_monitor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_NETLINK_NONITOR_H +#define HDI_NETLINK_NONITOR_H +#include + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiNetLinkMonitor { +public: + HdiNetLinkMonitor(); + int Init(); + virtual ~HdiNetLinkMonitor(); + +private: + Void MonitorThread(); + volatile bool mRunning = false; + int mScoketFd = -1; + std::unique_ptr mThread; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_NETLINK_NONITOR_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_session.cpp b/hardware/display/src/display_device/hdi_session.cpp new file mode 100644 index 0000000..cac2fb3 --- /dev/null +++ b/hardware/display/src/display_device/hdi_session.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "display_common.h" +#include "display_device.h" +#include "display_layer.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +HdiSession &HdiSession::GetInstance() +{ + static HdiSession instance; + static std::once_flag once; + std::call_once(once, [&]() { instance.Init(); }); + return instance; +} + +void HdiSession::Init() +{ + DISPLAY_LOGD(); + mHdiDevices = HdiDeviceInterface::DiscoveryDevice(); + DISPLAY_LOGD("devices size %{public}zd", mHdiDevices.size()); + mHdiDisplays.clear(); + for (auto device : mHdiDevices) { + auto displays = device->DiscoveryDisplay(); + mHdiDisplays.insert(displays.begin(), displays.end()); + } +} + +int32_t HdiSession::RegHotPlugCallback(HotPlugCallback callback, void *data) +{ + DISPLAY_CHK_RETURN((callback == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("the callback is nullptr")); + mHotPlugCallBacks.emplace(callback, data); + for (auto displayMap : mHdiDisplays) { + auto display = displayMap.second; + if (display->IsConnected()) { + DoHotPlugCallback(display->GetId(), true); + } + } + return DISPLAY_SUCCESS; +} + +void HdiSession::DoHotPlugCallback(uint32_t devId, bool connect) +{ + DISPLAY_LOGD(); + for (const auto &callback : mHotPlugCallBacks) { + callback.first(devId, connect, callback.second); + } +} +} // OHOS +} // HDI +} // DISPLAY + +using namespace OHOS::HDI::DISPLAY; +static int32_t RegHotPlugCallback(HotPlugCallback callback, void *data) +{ + DISPLAY_LOGD(); + HdiSession::GetInstance().RegHotPlugCallback(callback, data); + return DISPLAY_SUCCESS; +} + +static int32_t GetDisplayCapability(uint32_t devId, DisplayCapability *info) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN(info == nullptr, DISPLAY_NULL_PTR, DISPLAY_LOGE("info is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayCapability, info); +} + +static int32_t GetDisplaySuppportedModes(uint32_t devId, int *num, DisplayModeInfo *modes) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN(num == nullptr, DISPLAY_NULL_PTR, DISPLAY_LOGE("num is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplaySuppportedModes, num, modes); +} + +static int32_t GetDisplayMode(uint32_t devId, uint32_t *mode) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((mode == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("mode is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayMode, mode); +} + +static int32_t SetDisplayMode(uint32_t devId, uint32_t mode) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetDisplayMode, mode); +} + +static int32_t GetDisplayPowerStatus(uint32_t devId, DispPowerStatus *status) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((status == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("status is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayPowerStatus, status); +} + +static int32_t SetDisplayPowerStatus(uint32_t devId, DispPowerStatus status) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetDisplayPowerStatus, status); +} + +static int32_t GetDisplayBacklight(uint32_t devId, uint32_t *value) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((value == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("value is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayBacklight, value); +} + +static int32_t SetDisplayBacklight(uint32_t devId, uint32_t value) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetDisplayBacklight, value); +} + +static int32_t GetDisplayProperty(uint32_t devId, uint32_t id, uint64_t *value) +{ + DISPLAY_LOGD(); + (void)id; + DISPLAY_CHK_RETURN((value == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("value is nullptr")); + return DISPLAY_NOT_SUPPORT; +} + +static int32_t SetDisplayProperty(uint32_t devId, uint32_t id, uint64_t value) +{ + DISPLAY_LOGD(); + (void)id; + return DISPLAY_NOT_SUPPORT; +} + +static int32_t PrepareDisplayLayers(uint32_t devId, bool *needFlushFb) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((needFlushFb == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("needFlushFb is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::PrepareDisplayLayers, needFlushFb); +} + +static int32_t GetDisplayCompChange(uint32_t devId, uint32_t *num, uint32_t *layers, int32_t *type) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((num == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("num is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayCompChange, num, layers, type); +} + +static int32_t SetDisplayClientCrop(uint32_t devId, IRect *rect) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + return DISPLAY_NOT_SUPPORT; +} + +static int32_t SetDisplayClientDestRect(uint32_t devId, IRect *rect) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + return DISPLAY_NOT_SUPPORT; +} + +static int32_t SetDisplayClientBuffer(uint32_t devId, const BufferHandle *buffer, int32_t fence) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetDisplayClientBuffer, buffer, fence); +} + +static int32_t SetDisplayClientDamage(uint32_t devId, uint32_t num, IRect *rect) +{ + DISPLAY_LOGD(); + (void)num; + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + return DISPLAY_NOT_SUPPORT; +} + +static int32_t SetDisplayVsyncEnabled(uint32_t devId, bool enabled) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetDisplayVsyncEnabled, enabled); +} + +static int32_t RegDisplayVBlankCallback(uint32_t devId, VBlankCallback callback, void *data) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::RegDisplayVBlankCallback, callback, data); +} + +static int32_t GetDisplayReleaseFence(uint32_t devId, uint32_t *num, uint32_t *layers, int32_t *fences) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::GetDisplayReleaseFence, num, layers, + fences); +// return DISPLAY_NOT_SUPPORT; +} + +static int32_t Commit(uint32_t devId, int32_t *fence) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((fence == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("fence is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::Commit, fence); +} + +static int32_t CreateVirtualDisplay(uint32_t width, uint32_t height, int32_t *format, uint32_t *devId) +{ + DISPLAY_LOGD(); + return DISPLAY_NOT_SUPPORT; +} +static int32_t DestroyVirtualDisplay(uint32_t devId) +{ + DISPLAY_LOGD(); + return DISPLAY_NOT_SUPPORT; +} +static int32_t SetVirtualDisplayBuffer(uint32_t devId, BufferHandle *buffer, int32_t releaseFence) +{ + DISPLAY_LOGD(); + return DISPLAY_NOT_SUPPORT; +} + + +// Layer function +static int32_t CreateLayer(uint32_t devId, const LayerInfo *layerInfo, uint32_t *layerId) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((layerId == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("layerId is nullptr")); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::CreateLayer, layerInfo, layerId); +} + +static int32_t CloseLayer(uint32_t devId, uint32_t layerId) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::CloseLayer, layerId); +} + +static int32_t SetLayerSize(uint32_t devId, uint32_t layerId, IRect *rect) +{ + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerSize, rect); +} + +static int32_t SetLayerCrop(uint32_t devId, uint32_t layerId, IRect *rect) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerCrop, rect); +} + +static int32_t SetLayerZorder(uint32_t devId, uint32_t layerId, uint32_t zorder) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallDisplayFunction(devId, &HdiDisplay::SetLayerZorder, layerId, zorder); +} + +static int32_t SetLayerPreMulti(uint32_t devId, uint32_t layerId, bool preMul) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerPreMulti, preMul); +} + +static int32_t SetLayerAlpha(uint32_t devId, uint32_t layerId, LayerAlpha *alpha) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((alpha == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("alpha is nullptr")); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerAlpha, alpha); +} + +static int32_t SetTransformMode(uint32_t devId, uint32_t layerId, TransformType type) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetTransformMode, type); +} + +static int32_t SetLayerDirtyRegion(uint32_t devId, uint32_t layerId, IRect *region) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((region == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("region is nullptr")); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerDirtyRegion, region); +} + +static int32_t SetLayerVisibleRegion(uint32_t devId, uint32_t layerId, uint32_t num, IRect *rect) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((rect == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("rect is nullptr")); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerVisibleRegion, num, rect); +} + +static int32_t SetLayerBuffer(uint32_t devId, uint32_t layerId, const BufferHandle *buffer, int32_t fence) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerBuffer, buffer, fence); +} + +static int32_t SetLayerCompositionType(uint32_t devId, uint32_t layerId, CompositionType type) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerCompositionType, type); +} + +static int32_t SetLayerBlendType(uint32_t devId, uint32_t layerId, BlendType type) +{ + DISPLAY_LOGD(); + return HdiSession::GetInstance().CallLayerFunction(devId, layerId, &HdiLayer::SetLayerBlendType, type); +} + + +extern "C" { +int32_t DeviceInitialize(DeviceFuncs **funcs) +{ + DISPLAY_CHK_RETURN((funcs == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in funcs is null")); + DeviceFuncs *dFuncs = (DeviceFuncs *)calloc(1, sizeof(DeviceFuncs)); + DISPLAY_CHK_RETURN((dFuncs == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not calloc")); + + dFuncs->RegHotPlugCallback = RegHotPlugCallback; + dFuncs->GetDisplayCapability = GetDisplayCapability; + dFuncs->GetDisplaySuppportedModes = GetDisplaySuppportedModes; + dFuncs->GetDisplayMode = GetDisplayMode; + dFuncs->SetDisplayMode = SetDisplayMode; + dFuncs->GetDisplayPowerStatus = GetDisplayPowerStatus; + dFuncs->SetDisplayPowerStatus = SetDisplayPowerStatus; + dFuncs->PrepareDisplayLayers = PrepareDisplayLayers; + dFuncs->GetDisplayBacklight = GetDisplayBacklight; + dFuncs->SetDisplayBacklight = SetDisplayBacklight; + dFuncs->GetDisplayProperty = GetDisplayProperty; + dFuncs->GetDisplayCompChange = GetDisplayCompChange; + dFuncs->SetDisplayClientCrop = SetDisplayClientCrop; + dFuncs->SetDisplayClientDestRect = SetDisplayClientDestRect; + dFuncs->SetDisplayClientBuffer = SetDisplayClientBuffer; + dFuncs->SetDisplayClientDamage = SetDisplayClientDamage; + dFuncs->SetDisplayVsyncEnabled = SetDisplayVsyncEnabled; + dFuncs->RegDisplayVBlankCallback = RegDisplayVBlankCallback; + dFuncs->GetDisplayReleaseFence = GetDisplayReleaseFence; + dFuncs->CreateVirtualDisplay = CreateVirtualDisplay; + dFuncs->DestroyVirtualDisplay = DestroyVirtualDisplay; + dFuncs->SetVirtualDisplayBuffer = SetVirtualDisplayBuffer; + dFuncs->SetDisplayProperty = SetDisplayProperty; + dFuncs->Commit = Commit; + *funcs = dFuncs; + DISPLAY_LOGD("%{public}s: device initialize success", __func__); + HdiSession::GetInstance(); + return DISPLAY_SUCCESS; +} + +int32_t DeviceUninitialize(DeviceFuncs *funcs) +{ + DISPLAY_CHK_RETURN((funcs == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in funcs is null")); + free(funcs); + return DISPLAY_SUCCESS; +} + + +int32_t LayerInitialize(LayerFuncs **funcs) +{ + DISPLAY_CHK_RETURN((funcs == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("the in funcs is nullptr")); + LayerFuncs *lFuncs = (LayerFuncs *)calloc(1, sizeof(LayerFuncs)); + DISPLAY_CHK_RETURN((lFuncs == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("can not calloc errno: %{public}d", errno)); + lFuncs->SetLayerAlpha = SetLayerAlpha; + lFuncs->CreateLayer = CreateLayer; + lFuncs->CloseLayer = CloseLayer; + lFuncs->SetLayerSize = SetLayerSize; + lFuncs->SetLayerCrop = SetLayerCrop; + lFuncs->SetLayerZorder = SetLayerZorder; + lFuncs->SetLayerPreMulti = SetLayerPreMulti; + lFuncs->SetTransformMode = SetTransformMode; + lFuncs->SetLayerDirtyRegion = SetLayerDirtyRegion; + lFuncs->SetLayerVisibleRegion = SetLayerVisibleRegion; + lFuncs->SetLayerBuffer = SetLayerBuffer; + lFuncs->SetLayerCompositionType = SetLayerCompositionType; + lFuncs->SetLayerBlendType = SetLayerBlendType; + + *funcs = lFuncs; + DISPLAY_LOGD("%{public}s: layer initialize success", __func__); + return DISPLAY_SUCCESS; +} + +int32_t LayerUninitialize(LayerFuncs *funcs) +{ + DISPLAY_CHK_RETURN((funcs == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("the funcs is nullptr")); + free(funcs); + DISPLAY_LOGD("%{public}s: layer uninitialize success", __func__); + return DISPLAY_SUCCESS; +} +} \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_session.h b/hardware/display/src/display_device/hdi_session.h new file mode 100644 index 0000000..bed5d63 --- /dev/null +++ b/hardware/display/src/display_device/hdi_session.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_SESSION_H +#define HDI_SESSION_H +#include +#include +#include +#include "display_device.h" +#include "hdi_device_interface.h" +#include "hdi_display.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiSession { +public: + void Init(); + static HdiSession &GetInstance(); + + template + int32_t CallDisplayFunction(uint32_t devId, int32_t (HdiDisplay::*member)(Args...), Args... args) + { + DISPLAY_LOGD("device Id : %{public}d", devId); + DISPLAY_CHK_RETURN((devId == INVALIDE_DISPLAY_ID), DISPLAY_FAILURE, DISPLAY_LOGE("invalide device id")); + auto iter = mHdiDisplays.find(devId); + DISPLAY_CHK_RETURN((iter == mHdiDisplays.end()), DISPLAY_FAILURE, + DISPLAY_LOGE("can not find display %{public}d", devId)); + auto display = iter->second.get(); + return (display->*member)(std::forward(args)...); + } + + template + int32_t CallLayerFunction(uint32_t devId, uint32_t layerId, int32_t (HdiLayer::*member)(Args...), Args... args) + { + DISPLAY_LOGD("device Id : %{public}d", devId); + DISPLAY_CHK_RETURN((devId == INVALIDE_DISPLAY_ID), DISPLAY_FAILURE, DISPLAY_LOGE("invalide device id")); + auto iter = mHdiDisplays.find(devId); + DISPLAY_CHK_RETURN((iter == mHdiDisplays.end()), DISPLAY_FAILURE, + DISPLAY_LOGE("can not find display %{public}d", devId)); + auto display = iter->second.get(); + auto layer = display->GetHdiLayer(layerId); + DISPLAY_CHK_RETURN((layer == nullptr), DISPLAY_FAILURE, + DISPLAY_LOGE("can not find the layer %{public}d", layerId)); + return (layer->*member)(std::forward(args)...); + } + + int32_t RegHotPlugCallback(HotPlugCallback callback, void *data); + void DoHotPlugCallback(uint32_t devId, bool connect); + +private: + std::unordered_map> mHdiDisplays; + std::vector> mHdiDevices; + std::unordered_map mHotPlugCallBacks; +}; +} // namespace OHOS +} // namespace HDI +} // namespace DISPLAY + +#endif // HDI_SESSION_H \ No newline at end of file diff --git a/hardware/display/src/display_device/hdi_shared_fd.h b/hardware/display/src/display_device/hdi_shared_fd.h new file mode 100644 index 0000000..b31b5af --- /dev/null +++ b/hardware/display/src/display_device/hdi_shared_fd.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDI_SHARED_FD +#define HDI_SHARED_FD +#include +#include +#include "display_common.h" + +namespace OHOS { +namespace HDI { +namespace DISPLAY { +class HdiFd { +public: + HdiFd() + { + DISPLAY_LOGD(); + } + explicit HdiFd(int fd) : mFd(fd) + { + DISPLAY_LOGD("mFd %{public}d", mFd); + } + int GetFd() const + { + return mFd; + }; + + HdiFd &operator = (int fd) + { + if (mFd >= 0) { + close(mFd); + } + mFd = fd; + return *this; + } + + virtual ~HdiFd() + { + if (mFd >= 0) { + close(mFd); + } + } + +private: + int mFd = -1; +}; + +using FdPtr = std::shared_ptr; +} // OHOS +} // HDIO +} // DISPLAY + +#endif // HDI_SHARED_FD \ No newline at end of file diff --git a/hardware/display/src/display_gfx/display_gfx.c b/hardware/display/src/display_gfx/display_gfx.c new file mode 100644 index 0000000..4bd6647 --- /dev/null +++ b/hardware/display/src/display_gfx/display_gfx.c @@ -0,0 +1,468 @@ +/* + * Copyright 2015 Rockchip Electronics Co. LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "display_gfx.h" +#include "im2d.h" +#include "rga.h" +#include "display_common.h" +#include +#include +#include "display_gralloc.h" + +#define ALIGN_UP(x, a) ((((x) + ((a)-1)) / (a)) * (a)) +GrallocFuncs *grallocFucs = NULL; +int32_t rkInitGfx() +{ + DISPLAY_LOGE("%s\n", querystring(RGA_ALL)); + return DISPLAY_SUCCESS; +} + +int32_t rkDeinitGfx() +{ + return DISPLAY_SUCCESS; +} + +RgaSURF_FORMAT colorSpaceModeChange(PixelFormat color, uint8_t *isYuv) +{ + RgaSURF_FORMAT rkFormat; + switch(color) { + case PIXEL_FMT_RGB_565: /**< RGB565 format */ + rkFormat = RK_FORMAT_RGB_565; + *isYuv = 0; + break; + case PIXEL_FMT_RGBA_4444: /**< RGBA4444 format */ + rkFormat = RK_FORMAT_RGBA_4444; + *isYuv = 0; + break; + case PIXEL_FMT_RGBA_5551: /**< RGBA5551 format */ + rkFormat = RK_FORMAT_RGBA_5551; + *isYuv = 0; + break; + case PIXEL_FMT_RGBX_8888: /**< RGBX8888 format */ + rkFormat = RK_FORMAT_RGBX_8888; + *isYuv = 0; + break; + case PIXEL_FMT_RGBA_8888: /**< RGBA8888 format */ + rkFormat = RK_FORMAT_RGBA_8888; + *isYuv = 0; + break; + case PIXEL_FMT_RGB_888: /**< RGB888 format */ + rkFormat = RK_FORMAT_RGB_888; + *isYuv = 0; + break; + case PIXEL_FMT_BGR_565: /**< BGR565 format */ + rkFormat = RK_FORMAT_BGR_565; + *isYuv = 0; + break; + case PIXEL_FMT_BGRA_4444: /**< BGRA4444 format */ + rkFormat = RK_FORMAT_BGRA_4444; + *isYuv = 0; + break; + case PIXEL_FMT_BGRA_5551: /**< BGRA5551 format */ + rkFormat = RK_FORMAT_BGRA_5551; + *isYuv = 0; + break; + case PIXEL_FMT_BGRX_8888: /**< BGRX8888 format */ + rkFormat = RK_FORMAT_BGRX_8888; + *isYuv = 0; + break; + case PIXEL_FMT_BGRA_8888: /**< BGRA8888 format */ + rkFormat = RK_FORMAT_BGRA_8888; + *isYuv = 0; + break; + case PIXEL_FMT_YCBCR_422_SP: /**< YCBCR422 semi-planar format */ + rkFormat = RK_FORMAT_YCbCr_420_SP; + *isYuv = 1; + break; + case PIXEL_FMT_YCRCB_422_SP: /**< YCRCB422 semi-planar format */ + rkFormat = RK_FORMAT_YCrCb_422_SP; + *isYuv = 1; + break; + case PIXEL_FMT_YCBCR_420_SP: /**< YCBCR420 semi-planar format */ + rkFormat = RK_FORMAT_YCbCr_420_SP; + *isYuv = 1; + break; + case PIXEL_FMT_YCRCB_420_SP: /**< YCRCB420 semi-planar format */ + rkFormat = RK_FORMAT_YCrCb_420_SP; + *isYuv = 1; + break; + case PIXEL_FMT_YCBCR_422_P: /**< YCBCR422 planar format */ + rkFormat = RK_FORMAT_YCbCr_422_P; + *isYuv = 1; + break; + case PIXEL_FMT_YCRCB_422_P: /**< YCRCB422 planar format */ + rkFormat = RK_FORMAT_YCrCb_422_P; + *isYuv = 1; + break; + case PIXEL_FMT_YCBCR_420_P: /**< YCBCR420 planar format */ + rkFormat = RK_FORMAT_YCbCr_420_P; + *isYuv = 1; + break; + case PIXEL_FMT_YCRCB_420_P: /**< YCRCB420 planar format */ + rkFormat = RK_FORMAT_YCrCb_420_P; + *isYuv = 1; + break; + case PIXEL_FMT_YUYV_422_PKG: /**< YUYV422 packed format */ + rkFormat = RK_FORMAT_YUYV_422; + *isYuv = 1; + break; + case PIXEL_FMT_UYVY_422_PKG: /**< UYVY422 packed format */ + rkFormat = RK_FORMAT_UYVY_422; + *isYuv = 1; + break; + case PIXEL_FMT_YVYU_422_PKG: /**< YVYU422 packed format */ + rkFormat = RK_FORMAT_YUYV_422; + *isYuv = 1; + break; + case PIXEL_FMT_VYUY_422_PKG: /**< VYUY422 packed format */ + rkFormat = RK_FORMAT_VYUY_422; + *isYuv = 1; + break; + default: +// PIXEL_FMT_CLUT8: /**< CLUT8 format */ +// PIXEL_FMT_CLUT1, /**< CLUT1 format */ +// PIXEL_FMT_CLUT4, /**< CLUT4 format */ +// PIXEL_FMT_RGBA_5658, /**< RGBA5658 format */ +// PIXEL_FMT_RGBX_4444, /**< RGBX4444 format */ +// PIXEL_FMT_RGB_444, /**< RGB444 format */ +// PIXEL_FMT_RGBX_5551, /**< RGBX5551 format */ +// PIXEL_FMT_RGB_555, /**< RGB555 format */ +// PIXEL_FMT_BGRX_4444, /**< BGRX4444 format */ +// PIXEL_FMT_BGRX_5551, /**< BGRX5551 format */ +// PIXEL_FMT_YUV_422_I, /**< YUV422 interleaved format */ + rkFormat = RK_FORMAT_UNKNOWN; + break; + } + return rkFormat; +} + +int32_t rkFillRect(ISurface *surface, IRect *rect, uint32_t color, GfxOpt *opt) +{ + rga_buffer_t dst; + im_rect imRect; + IM_STATUS ret; + uint8_t isYuv; + + memset((void *)&imRect, 0, sizeof(imRect)); + imRect.x = rect->x; + imRect.y = rect->y; + imRect.width = rect->w; + imRect.height = rect->h; + + memset((void *)&dst, 0, sizeof(dst)); + dst.phy_addr = 0;//(void*)surface->phyAddr; + dst.vir_addr = 0;//surface->virAddr; + dst.fd = (int32_t)surface->phyAddr; + if ((int)dst.phy_addr == 0 && dst.fd == 0 && dst.vir_addr == NULL) { + DISPLAY_LOGE("source surface address error"); + return DISPLAY_PARAM_ERR; + } + DISPLAY_LOGE("gfx vir %{public}p phy 0x%{public}x fd %{public}d",dst.vir_addr, (int32_t)dst.phy_addr, dst.fd); + dst.width = surface->width; + dst.height = surface->height; + dst.wstride = ALIGN_UP(surface->width, 16); + dst.hstride = ALIGN_UP(surface->height, 16); + dst.format = colorSpaceModeChange(surface->enColorFmt, &isYuv); + dst.color_space_mode = IM_COLOR_SPACE_DEFAULT; + dst.color = color; + if (opt->enGlobalAlpha) + dst.global_alpha = opt->globalAlpha; + ret = imfill(dst, imRect, color); + + if (ret != IM_STATUS_SUCCESS) + return DISPLAY_FAILURE; + else + return DISPLAY_SUCCESS; +} + +int32_t blendTypeChange(BlendType blendType) +{ + int32_t rkBlendType; + switch(blendType) { + case BLEND_SRC: /**< SRC blending */ + rkBlendType = IM_ALPHA_BLEND_SRC; + break; + case BLEND_DST: /**< SRC blending */ + rkBlendType = IM_ALPHA_BLEND_DST; + break; + case BLEND_SRCOVER: /**< SRC_OVER blending */ + rkBlendType = IM_ALPHA_BLEND_SRC_OVER; + break; + case BLEND_DSTOVER: /**< DST_OVER blending */ + rkBlendType = IM_ALPHA_BLEND_DST_OVER; + break; + default: + /* Fix up later */ +// BLEND_NONE /**< No blending */ +// BLEND_CLEAR: /**< CLEAR blending */ +// BLEND_SRCIN: /**< SRC_IN blending */ +// BLEND_DSTIN: /**< DST_IN blending */ +// BLEND_SRCOUT: /**< SRC_OUT blending */ +// BLEND_DSTOUT: /**< DST_OUT blending */ +// BLEND_SRCATOP: /**< SRC_ATOP blending */ +// BLEND_DSTATOP: /**< DST_ATOP blending */ +// BLEND_ADD: /**< ADD blending */ +// BLEND_XOR: /**< XOR blending */ +// BLEND_DST: /**< DST blending */ +// BLEND_AKS: /**< AKS blending */ +// BLEND_AKD: /**< AKD blending */ +// BLEND_BUTT: /**< Null operation */ + rkBlendType = IM_STATUS_NOT_SUPPORTED; + break; + } + return rkBlendType; +} + +int32_t TransformTypeChange(TransformType type) +{ + int32_t rkRotateType; + switch(type) { + case ROTATE_90: /**< Rotation by 90 degrees */ + rkRotateType = IM_HAL_TRANSFORM_ROT_90; + break; + case ROTATE_180: /**< Rotation by 180 degrees */ + rkRotateType = IM_HAL_TRANSFORM_ROT_180; + break; + case ROTATE_270: /**< Rotation by 270 degrees */ + rkRotateType = IM_HAL_TRANSFORM_ROT_270; + break; + default: + rkRotateType = 0; /**< No rotation */ + break; + } + return rkRotateType; +} + +int32_t mirrorTypeChange(MirrorType type) +{ + int32_t rkMirrorType; + switch(type) { + case MIRROR_LR: /**< Left and right mirrors */ + rkMirrorType = IM_HAL_TRANSFORM_FLIP_H; + break; + case MIRROR_TB: /**< Top and bottom mirrors */ + rkMirrorType = IM_HAL_TRANSFORM_FLIP_V; + break; + default: + rkMirrorType = 0; + break; + } + return rkMirrorType; +} + +int32_t doFlit(ISurface *srcSurface, IRect *srcRect, ISurface *dstSurface, IRect *dstRect, GfxOpt *opt) +{ + int32_t usage = 0; + uint8_t isYuv = 0; + rga_buffer_t dstRgaBuffer, srcRgaBuffer, bRgbBuffer; + IM_STATUS ret = 0; + im_rect srect; + im_rect drect; + im_rect prect; + int32_t rkBlendType = 0; + int32_t rkRotateType = 0; + int32_t rkMirrorType = 0; + + memset(&dstRgaBuffer, 0, sizeof(dstRgaBuffer)); + memset(&srcRgaBuffer, 0, sizeof(srcRgaBuffer)); + memset(&bRgbBuffer, 0, sizeof(bRgbBuffer)); + memset(&srect, 0, sizeof(srect)); + memset(&drect, 0, sizeof(drect)); + memset(&prect, 0, sizeof(prect)); + if (opt->enGlobalAlpha) { + dstRgaBuffer.global_alpha = opt->globalAlpha; + srcRgaBuffer.global_alpha = opt->globalAlpha; + } + dstRgaBuffer.width = dstSurface->width; + dstRgaBuffer.height = dstSurface->height; + dstRgaBuffer.wstride = ALIGN_UP(dstSurface->width, 16); + dstRgaBuffer.hstride = ALIGN_UP(dstSurface->height, 16); + dstRgaBuffer.format = colorSpaceModeChange(dstSurface->enColorFmt, &isYuv); + dstRgaBuffer.phy_addr = 0;//(void *)dstSurface->phyAddr; + dstRgaBuffer.vir_addr = 0;//dstSurface->virAddr; + dstRgaBuffer.color_space_mode = IM_COLOR_SPACE_DEFAULT; + dstRgaBuffer.fd = (int32_t)dstSurface->phyAddr; + if (isYuv == 1) { + DISPLAY_LOGE("rk gfx do not support dst buffer is yuv format"); + return DISPLAY_PARAM_ERR; + } + + srcRgaBuffer.width = srcSurface->width; + srcRgaBuffer.height = srcSurface->height; + srcRgaBuffer.wstride = ALIGN_UP(srcSurface->width, 16); + srcRgaBuffer.hstride = ALIGN_UP(srcSurface->height, 16); + srcRgaBuffer.phy_addr = 0;//(void *)srcSurface->phyAddr; + srcRgaBuffer.vir_addr = 0;//srcSurface->virAddr; + srcRgaBuffer.format = colorSpaceModeChange(srcSurface->enColorFmt, &isYuv); + srcRgaBuffer.color_space_mode = IM_COLOR_SPACE_DEFAULT; + srcRgaBuffer.fd = (int32_t)srcSurface->phyAddr; + + if ((int)srcRgaBuffer.phy_addr == 0 && srcRgaBuffer.fd == 0 && srcRgaBuffer.vir_addr == NULL) { + DISPLAY_LOGE("source surface address error"); + return DISPLAY_PARAM_ERR; + } + + DISPLAY_LOGE("gfx src fd %{public}d, w %{public}d, h %{publuc}d, sw %{public}d sh %{public}d vir %{public}p",(int32_t)srcSurface->phyAddr, srcSurface->width, + srcSurface->height, ALIGN_UP(srcSurface->width, 16), ALIGN_UP(srcSurface->height, 16), srcRgaBuffer.vir_addr); + DISPLAY_LOGE("gfx dst fd %{public}d, w %{public}d, h %{public}d, sw %{public}d sh %{public}d vir %{public}p",(int32_t)dstSurface->phyAddr, dstSurface->width, + dstSurface->height, ALIGN_UP(dstSurface->width, 16), ALIGN_UP(dstSurface->height, 16), dstRgaBuffer.vir_addr); + + srect.x = srcRect->x; + srect.y = srcRect->y; + srect.height = srcRect->h; + srect.width = srcRect->w; + drect.x = dstRect->x; + drect.y = dstRect->y; + drect.height = dstRect->h; + drect.width = dstRect->w; + + if (opt->blendType) { + rkBlendType = blendTypeChange(opt->blendType); + if (rkBlendType > 0) { + usage |= rkBlendType; + if (rkBlendType == IM_ALPHA_BLEND_DST_OVER || rkBlendType == IM_ALPHA_BLEND_SRC_OVER) + usage |= IM_ALPHA_BLEND_PRE_MUL; + } else if (rkBlendType == IM_STATUS_NOT_SUPPORTED) { + return DISPLAY_NOT_SUPPORT; + } + } + if (opt->rotateType) { + rkRotateType = TransformTypeChange(opt->rotateType); + if (rkRotateType != 0) + usage |= rkRotateType; + } + if (opt->mirrorType == MIRROR_LR || opt->mirrorType == MIRROR_TB) { + rkMirrorType = mirrorTypeChange(opt->mirrorType); + if (rkMirrorType != 0) + usage |= rkMirrorType; + } + if (opt->enableScale) { + DISPLAY_LOGE("gfx scale from (%{puhblic}d, %{public}d) to (%{public}d, %{public}d)", srcRgaBuffer.width, srcRgaBuffer.height, dstRgaBuffer.width, + dstRgaBuffer.height); + } + usage |= IM_SYNC; + if (isYuv == 1) { + if (rkBlendType == IM_ALPHA_BLEND_SRC_OVER || rkBlendType == IM_ALPHA_BLEND_SRC) { + usage = 0; + if (opt->enableScale == 0) { + memset(&srect, 0, sizeof(srect)); + srect.width = srcRgaBuffer.width; + srect.height = srcRgaBuffer.height; + + memset(&drect, 0, sizeof(drect)); + drect.x = dstRgaBuffer.width - srcRgaBuffer.width; + drect.y = dstRgaBuffer.height - srcRgaBuffer.height; + drect.width = srcRgaBuffer.width; + drect.height = srcRgaBuffer.height; + } + usage = rkRotateType | rkMirrorType | IM_SYNC; + ret = improcess(srcRgaBuffer, dstRgaBuffer, bRgbBuffer, srect, drect, prect, usage); + if (ret != IM_STATUS_SUCCESS) { + DISPLAY_LOGE("gfx improcess %{public}s", imStrError(ret)); + } + } else if (rkBlendType == IM_ALPHA_BLEND_DST_OVER) { + if (grallocFucs == NULL) { + ret = GrallocInitialize(&grallocFucs); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("Gralloc init failed")); + } + AllocInfo info = { + .width = dstRgaBuffer.width, + .height = dstRgaBuffer.height, + .usage = HBM_USE_MEM_DMA | HBM_USE_CPU_READ | HBM_USE_CPU_WRITE, + .format = PIXEL_FMT_RGBA_8888,//srcSurface->enColorFmt, + }; + BufferHandle *buffer = NULL; + + ret = grallocFucs->AllocMem(&info, &buffer); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not alloc memory")); + + bRgbBuffer.width = dstRgaBuffer.width; + bRgbBuffer.height = dstRgaBuffer.height; + bRgbBuffer.wstride = dstRgaBuffer.wstride; + bRgbBuffer.hstride = dstRgaBuffer.hstride; + bRgbBuffer.format = RK_FORMAT_RGBA_8888;//srcRgaBuffer.format; + bRgbBuffer.phy_addr = 0;//(void *) buffer->phyAddr; + bRgbBuffer.vir_addr = 0;//buffer->virAddr; + bRgbBuffer.color_space_mode = dstRgaBuffer.color_space_mode; + bRgbBuffer.fd = (int32_t)buffer->phyAddr; + memcpy(&prect, &drect, sizeof(drect)); + + ret = improcess(srcRgaBuffer, bRgbBuffer, dstRgaBuffer, srect, prect, drect, usage); + if (ret != IM_STATUS_SUCCESS) { + DISPLAY_LOGE("gfx improcess %{public}s", imStrError(ret)); + } else { + ret = imcopy(bRgbBuffer, dstRgaBuffer); + if (ret != IM_STATUS_SUCCESS) { + DISPLAY_LOGE("gfx improcess %{public}s", imStrError(ret)); + } + } + grallocFucs->FreeMem(buffer); + } + } else { + ret = improcess(srcRgaBuffer, dstRgaBuffer, bRgbBuffer, srect, drect, prect, usage); + if (ret != IM_STATUS_SUCCESS) { + DISPLAY_LOGE("gfx improcess %{public}s", imStrError(ret)); + } + } + if (ret != IM_STATUS_SUCCESS) + return DISPLAY_FAILURE; + else + return DISPLAY_SUCCESS; +} + +int32_t rkBlit(ISurface *srcSurface, IRect *srcRect, ISurface *dstSurface, IRect *dstRect, GfxOpt *opt) +{ + CHECK_NULLPOINTER_RETURN_VALUE(srcSurface, DISPLAY_NULL_PTR); + CHECK_NULLPOINTER_RETURN_VALUE(srcRect, DISPLAY_NULL_PTR); + CHECK_NULLPOINTER_RETURN_VALUE(dstSurface, DISPLAY_NULL_PTR); + CHECK_NULLPOINTER_RETURN_VALUE(dstRect, DISPLAY_NULL_PTR); + CHECK_NULLPOINTER_RETURN_VALUE(opt, DISPLAY_NULL_PTR); + + if (doFlit(srcSurface, srcRect, dstSurface, dstRect, opt) < 0) + return DISPLAY_FAILURE; + else + return DISPLAY_SUCCESS; +} + +int32_t rkSync(int32_t timeOut) +{ + return DISPLAY_SUCCESS; +} + +int32_t GfxInitialize(GfxFuncs **funcs) +{ + DISPLAY_CHK_RETURN((funcs == NULL), DISPLAY_PARAM_ERR, DISPLAY_LOGE("info is null")); + GfxFuncs *gfxFuncs = (GfxFuncs *)malloc(sizeof(GfxFuncs)); + memset((void *)gfxFuncs, 0, sizeof(GfxFuncs)); + + gfxFuncs->InitGfx = rkInitGfx; + gfxFuncs->DeinitGfx = rkDeinitGfx; + gfxFuncs->FillRect = rkFillRect; + gfxFuncs->Blit = rkBlit; + gfxFuncs->Sync = rkSync; + *funcs = gfxFuncs; + + return DISPLAY_SUCCESS; +} + +int32_t GfxUninitialize(GfxFuncs *funcs) +{ + CHECK_NULLPOINTER_RETURN_VALUE(funcs, DISPLAY_NULL_PTR); + free(funcs); + DISPLAY_LOGI("%s: gfx uninitialize success", __func__); + return DISPLAY_SUCCESS; +} + diff --git a/hardware/display/src/display_gralloc/display_gralloc.c b/hardware/display/src/display_gralloc/display_gralloc.c new file mode 100644 index 0000000..4983581 --- /dev/null +++ b/hardware/display/src/display_gralloc/display_gralloc.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "display_gralloc.h" +#include +#include +#include "display_common.h" +#include "display_gralloc_gbm.h" + +int32_t AllocMem(const AllocInfo *info, BufferHandle **handle) +{ + DISPLAY_CHK_RETURN((info == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("info is null")); + DISPLAY_CHK_RETURN((handle == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (info->usage & HBM_USE_MEM_DMA) { + return GbmAllocMem(info, handle); + } +#endif + DISPLAY_LOGE("the usage is not support 0x%{public}" PRIx64 "", info->usage); + return DISPLAY_NOT_SUPPORT; +} + +void FreeMem(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((handle == NULL), DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (handle->usage & HBM_USE_MEM_DMA) { + GbmFreeMem(handle); + return; + } +#endif +} + +void *Mmap(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN((handle == NULL), NULL, DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (handle->usage & HBM_USE_MEM_DMA) { + return GbmMmap(handle); + } +#endif + return NULL; +} + +int32_t Unmap(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN((handle == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (handle->usage & HBM_USE_MEM_DMA) { + return GbmUnmap(handle); + } +#endif + return DISPLAY_NOT_SUPPORT; +} + +int32_t FlushCache(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN((handle == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (handle->usage & HBM_USE_MEM_DMA) { + return GbmFlushCache(handle); + } +#endif + return DISPLAY_NOT_SUPPORT; +} + +int32_t InvalidateCache(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN((handle == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("handle is null")); +#ifdef GRALLOC_GBM_SUPPORT + if (handle->usage & HBM_USE_MEM_DMA) { + return GbmInvalidateCache(handle); + } +#endif + return DISPLAY_NOT_SUPPORT; +} + +int32_t GrallocUninitialize(GrallocFuncs *funcs) +{ + DISPLAY_CHK_RETURN(funcs == NULL, DISPLAY_PARAM_ERR, DISPLAY_LOGE("funcs is null")); + DISPLAY_LOGD(); +#ifdef GRALLOC_GBM_SUPPORT + if (GbmGrallocUninitialize() != DISPLAY_SUCCESS) { + DISPLAY_LOGE("gbm uninit failed"); + } +#endif + free(funcs); + return DISPLAY_SUCCESS; +} + +int32_t GrallocInitialize(GrallocFuncs **funcs) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN((funcs == NULL), DISPLAY_PARAM_ERR, DISPLAY_LOGE("funcs is null")); + GrallocFuncs *grallocFuncs = (GrallocFuncs *)malloc(sizeof(GrallocFuncs)); + DISPLAY_CHK_RETURN((grallocFuncs == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("memset_s failed")); + errno_t eok = memset_s(grallocFuncs, sizeof(GrallocFuncs), 0, sizeof(GrallocFuncs)); + DISPLAY_CHK_RETURN((eok != EOK), DISPLAY_FAILURE, DISPLAY_LOGE("memset_s failed")); + // initialize gbm gralloc +#ifdef GRALLOC_GBM_SUPPORT + int ret = GbmGrallocInitialize(); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), ret, DISPLAY_LOGE("gbm initial"); free(grallocFuncs)); +#endif + grallocFuncs->AllocMem = AllocMem; + grallocFuncs->FreeMem = FreeMem; + grallocFuncs->Mmap = Mmap; + grallocFuncs->Unmap = Unmap; + grallocFuncs->InvalidateCache = InvalidateCache; + grallocFuncs->FlushCache = FlushCache; + *funcs = grallocFuncs; + return DISPLAY_SUCCESS; +} \ No newline at end of file diff --git a/hardware/display/src/display_gralloc/display_gralloc_gbm.c b/hardware/display/src/display_gralloc/display_gralloc_gbm.c new file mode 100644 index 0000000..f1c9323 --- /dev/null +++ b/hardware/display/src/display_gralloc/display_gralloc_gbm.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "display_gralloc_gbm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wayland_drm_auth_client.h" +#include "drm_fourcc.h" +#include "hisilicon_drm.h" +#include "hi_gbm.h" +#include "hdf_dlist.h" +#include "display_gralloc_private.h" +#include "display_common.h" + +const char *g_drmFileNode = "/dev/dri/renderD128"; +static GrallocManager *g_grallocManager = NULL; +static pthread_mutex_t g_lock; + +typedef struct { + uint32_t drmFormat; + PixelFormat pixFormat; +} PixelFormatConvertTbl; + +typedef struct { + uint32_t value; + const char *str; +} ValueStrMap; + +static GrallocManager *GetGrallocManager() +{ + if (g_grallocManager == NULL) { + g_grallocManager = (GrallocManager *)malloc(sizeof(GrallocManager)); + errno_t eok = memset_s(g_grallocManager, sizeof(GrallocManager), 0, sizeof(GrallocManager)); + if (eok != EOK) { + DISPLAY_LOGE("memset_s failed"); + } + if (g_grallocManager == NULL) { + DISPLAY_LOGE("gralloc manager malloc failed"); + } + } + return g_grallocManager; +} + +const char *GetPixelFmtStr(PixelFormat format) +{ + static const ValueStrMap pixelStrMaps[] = { + {PIXEL_FMT_CLUT8, "PIXEL_FMT_CLUT8"}, {PIXEL_FMT_CLUT1, "PIXEL_FMT_CLUT1"}, + {PIXEL_FMT_CLUT4, "PIXEL_FMT_CLUT4"}, {PIXEL_FMT_RGB_565, "PIXEL_FMT_RGB_565"}, + {PIXEL_FMT_RGBA_5658, "IXEL_FMT_RGBA_5658"}, {PIXEL_FMT_RGBX_4444, "PIXEL_FMT_RGBX_4444"}, + {PIXEL_FMT_RGBA_4444, "PIXEL_FMT_RGBA_4444"}, {PIXEL_FMT_RGB_444, "PIXEL_FMT_RGB_444"}, + {PIXEL_FMT_RGBX_5551, "PIXEL_FMT_RGBX_5551"}, {PIXEL_FMT_RGBA_5551, "PIXEL_FMT_RGBA_5551"}, + {PIXEL_FMT_RGB_555, "PIXEL_FMT_RGB_555"}, {PIXEL_FMT_RGBX_8888, "PIXEL_FMT_RGBX_8888"}, + {PIXEL_FMT_RGBA_8888, "PIXEL_FMT_RGBA_8888"}, {PIXEL_FMT_RGB_888, "PIXEL_FMT_RGB_888"}, + {PIXEL_FMT_BGR_565, "PIXEL_FMT_BGR_565"}, {PIXEL_FMT_BGRX_4444, "PIXEL_FMT_BGRX_4444"}, + {PIXEL_FMT_BGRA_4444, "PIXEL_FMT_BGRA_4444"}, {PIXEL_FMT_BGRX_5551, "PIXEL_FMT_BGRX_5551"}, + {PIXEL_FMT_BGRA_5551, "PIXEL_FMT_BGRA_5551"}, {PIXEL_FMT_BGRX_8888, "PIXEL_FMT_BGRX_8888"}, + {PIXEL_FMT_BGRA_8888, "PIXEL_FMT_BGRA_8888"}, {PIXEL_FMT_YUV_422_I, "PIXEL_FMT_YUV_422_I"}, + {PIXEL_FMT_YUV_422_I, "PIXEL_FMT_YUV_422_I"}, {PIXEL_FMT_YCBCR_422_SP, "PIXEL_FMT_YCBCR_422_SP"}, + {PIXEL_FMT_YCRCB_422_SP, "PIXEL_FMT_YCRCB_422_SP"}, {PIXEL_FMT_YCBCR_420_SP, "PIXEL_FMT_YCBCR_420_SP"}, + {PIXEL_FMT_YCRCB_420_SP, "PIXEL_FMT_YCRCB_420_SP"}, {PIXEL_FMT_YCBCR_422_P, "PIXEL_FMT_YCBCR_422_P"}, + {PIXEL_FMT_YCRCB_422_P, "PIXEL_FMT_YCRCB_422_P"}, {PIXEL_FMT_YCBCR_420_P, "PIXEL_FMT_YCBCR_420_P"}, + {PIXEL_FMT_YCRCB_420_P, "PIXEL_FMT_YCRCB_420_P"}, {PIXEL_FMT_YUYV_422_PKG, "PIXEL_FMT_YUYV_422_PKG"}, + {PIXEL_FMT_UYVY_422_PKG, "PIXEL_FMT_UYVY_422_PKG"}, {PIXEL_FMT_YVYU_422_PKG, "PIXEL_FMT_YVYU_422_PKG"}, + {PIXEL_FMT_VYUY_422_PKG, "PIXEL_FMT_VYUY_422_PKG"}, {PIXEL_FMT_BUTT, "PIXEL_FMT_BUTT"}, + }; + static const char *unknown = "unknown format"; + for (uint32_t i = 0; i < sizeof(pixelStrMaps) / sizeof(pixelStrMaps[0]); i++) { + if (pixelStrMaps[i].value == format) { + return pixelStrMaps[i].str; + } + } + DISPLAY_LOGE("GetPixelFmtStr unknown format %{public}d", format); + return unknown; +} + +const char *GetDrmFmtStr(uint32_t format) +{ + static const ValueStrMap formatStrMaps[] = { + {DRM_FORMAT_C8, "DRM_FORMAT_C8" }, {DRM_FORMAT_R8, "DRM_FORMAT_R8" }, + {DRM_FORMAT_R16, "DRM_FORMAT_R16"}, {DRM_FORMAT_RG88, "DRM_FORMAT_RG88"}, + {DRM_FORMAT_GR88, "DRM_FORMAT_GR88"}, {DRM_FORMAT_RG1616, "DRM_FORMAT_RG1616"}, + {DRM_FORMAT_GR1616, "DRM_FORMAT_GR1616"}, {DRM_FORMAT_RGB332, "DRM_FORMAT_RGB332"}, + {DRM_FORMAT_BGR233, "DRM_FORMAT_BGR233"}, {DRM_FORMAT_XRGB4444, "DRM_FORMAT_XRGB4444"}, + {DRM_FORMAT_XBGR4444, "DRM_FORMAT_XBGR4444"}, {DRM_FORMAT_RGBX4444, "DRM_FORMAT_RGBX4444"}, + {DRM_FORMAT_BGRX4444, "DRM_FORMAT_BGRX4444"}, {DRM_FORMAT_ARGB4444, "DRM_FORMAT_ARGB4444"}, + {DRM_FORMAT_ABGR4444, "DRM_FORMAT_ABGR4444"}, {DRM_FORMAT_RGBA4444, "DRM_FORMAT_RGBA4444"}, + {DRM_FORMAT_BGRA4444, "DRM_FORMAT_BGRA4444"}, {DRM_FORMAT_XRGB1555, "DRM_FORMAT_XRGB1555"}, + {DRM_FORMAT_XBGR1555, "DRM_FORMAT_XBGR1555"}, {DRM_FORMAT_RGBX5551, "DRM_FORMAT_RGBX5551"}, + {DRM_FORMAT_BGRX5551, "DRM_FORMAT_BGRX5551"}, {DRM_FORMAT_ARGB1555, "DRM_FORMAT_ARGB1555"}, + {DRM_FORMAT_ABGR1555, "DRM_FORMAT_ABGR1555"}, {DRM_FORMAT_RGBA5551, "DRM_FORMAT_RGBA5551"}, + {DRM_FORMAT_BGRA5551, "DRM_FORMAT_BGRA5551"}, {DRM_FORMAT_RGB565, "DRM_FORMAT_RGB565"}, + {DRM_FORMAT_BGR565, "DRM_FORMAT_BGR565"}, {DRM_FORMAT_RGB888, "DRM_FORMAT_RGB888"}, + {DRM_FORMAT_BGR888, "DRM_FORMAT_BGR888"}, {DRM_FORMAT_XRGB8888, "DRM_FORMAT_XRGB8888"}, + {DRM_FORMAT_XBGR8888, "DRM_FORMAT_XBGR8888"}, {DRM_FORMAT_RGBX8888, "DRM_FORMAT_RGBX8888"}, + {DRM_FORMAT_BGRX8888, "DRM_FORMAT_BGRX8888"}, {DRM_FORMAT_ARGB8888, "DRM_FORMAT_ARGB8888"}, + {DRM_FORMAT_ABGR8888, "DRM_FORMAT_ABGR8888"}, {DRM_FORMAT_RGBA8888, "DRM_FORMAT_RGBA8888"}, + {DRM_FORMAT_BGRA8888, "DRM_FORMAT_BGRA8888"}, {DRM_FORMAT_XRGB2101010, "DRM_FORMAT_XRGB2101010"}, + {DRM_FORMAT_BGRX1010102, "DRM_FORMAT_BGRX1010102"}, {DRM_FORMAT_ARGB2101010, "DRM_FORMAT_ARGB2101010"}, + {DRM_FORMAT_ABGR2101010, "DRM_FORMAT_ABGR2101010"}, {DRM_FORMAT_RGBA1010102, "DRM_FORMAT_RGBA1010102"}, + {DRM_FORMAT_YVYU, "DRM_FORMAT_YVYU"}, {DRM_FORMAT_UYVY, "DRM_FORMAT_UYVY"}, + {DRM_FORMAT_VYUY, "DRM_FORMAT_VYUY"}, {DRM_FORMAT_AYUV, "DRM_FORMAT_AYUV"}, + {DRM_FORMAT_NV12, "DRM_FORMAT_NV12"}, {DRM_FORMAT_NV21, "DRM_FORMAT_NV21"}, + {DRM_FORMAT_NV16, "DRM_FORMAT_NV16"}, {DRM_FORMAT_NV61, "DRM_FORMAT_NV61"}, + {DRM_FORMAT_NV24, "DRM_FORMAT_NV24"}, {DRM_FORMAT_NV42, "DRM_FORMAT_NV42"}, + {DRM_FORMAT_YUV410, "DRM_FORMAT_YUV410"}, {DRM_FORMAT_YVU410, "DRM_FORMAT_YVU410"}, + {DRM_FORMAT_YUV411, "DRM_FORMAT_YUV411"}, {DRM_FORMAT_YVU411, "DRM_FORMAT_YVU411"}, + {DRM_FORMAT_YUV420, "DRM_FORMAT_YUV420"}, {DRM_FORMAT_YVU420, "DRM_FORMAT_YVU420"}, + {DRM_FORMAT_YUV422, "DRM_FORMAT_YUV422"}, {DRM_FORMAT_YVU422, "DRM_FORMAT_YVU422"}, + {DRM_FORMAT_YUV444, "DRM_FORMAT_YUV444"}, {DRM_FORMAT_YVU444, "DRM_FORMAT_YVU444"}, + }; + + static const char *unknown = "unknown drm format"; + for (uint32_t i = 0; i < sizeof(formatStrMaps) / sizeof(formatStrMaps[0]); i++) { + if (formatStrMaps[i].value == format) { + return formatStrMaps[i].str; + } + } + DISPLAY_LOGE("GetDrmFmtStr unknown format %{public}d", format); + return unknown; +} + +static uint32_t ConvertFormatToDrm(PixelFormat fmtIn) +{ + static const PixelFormatConvertTbl convertTable[] = { + {DRM_FORMAT_RGBX8888, PIXEL_FMT_RGBX_8888}, {DRM_FORMAT_RGBA8888, PIXEL_FMT_RGBA_8888}, + {DRM_FORMAT_RGB888, PIXEL_FMT_RGB_888}, {DRM_FORMAT_RGB565, PIXEL_FMT_BGR_565}, + {DRM_FORMAT_BGRX4444, PIXEL_FMT_BGRX_4444}, {DRM_FORMAT_BGRA4444, PIXEL_FMT_BGRA_4444}, + {DRM_FORMAT_RGBA4444, PIXEL_FMT_RGBA_4444}, {DRM_FORMAT_RGBX4444, PIXEL_FMT_RGBX_4444}, + {DRM_FORMAT_BGRX5551, PIXEL_FMT_BGRX_5551}, {DRM_FORMAT_BGRA5551, PIXEL_FMT_BGRA_5551}, + {DRM_FORMAT_BGRX8888, PIXEL_FMT_BGRX_8888}, {DRM_FORMAT_BGRA8888, PIXEL_FMT_BGRA_8888}, + {DRM_FORMAT_NV12, PIXEL_FMT_YCBCR_420_SP}, {DRM_FORMAT_NV21, PIXEL_FMT_YCRCB_420_SP}, + {DRM_FORMAT_YUV420, PIXEL_FMT_YCBCR_420_P}, {DRM_FORMAT_YVU420, PIXEL_FMT_YCRCB_420_P}, + {DRM_FORMAT_NV16, PIXEL_FMT_YCBCR_422_SP}, {DRM_FORMAT_NV61, PIXEL_FMT_YCRCB_422_SP}, + {DRM_FORMAT_YUV422, PIXEL_FMT_YCBCR_422_P}, {DRM_FORMAT_YVU422, PIXEL_FMT_YCRCB_422_P}, + }; + uint32_t fmtOut = 0; + for (uint32_t i = 0; i < sizeof(convertTable) / sizeof(convertTable[0]); i++) { + if (convertTable[i].pixFormat == fmtIn) { + fmtOut = convertTable[i].drmFormat; + } + } + DISPLAY_LOGD("fmtIn %{public}d : %{public}s, outFmt %{public}d : %{public}s", fmtIn, GetPixelFmtStr(fmtIn), fmtOut, + GetDrmFmtStr(fmtOut)); + return fmtOut; +} + +static uint64_t ConvertUsageToGbm(uint64_t inUsage) +{ + uint64_t outUsage = GBM_BO_USE_TEXTURING; + if (inUsage & HBM_USE_CPU_READ) { + outUsage |= GBM_BO_USE_SW_READ_OFTEN; + } + if (inUsage & HBM_USE_CPU_WRITE) { + outUsage |= GBM_BO_USE_SW_WRITE_OFTEN; + } + DISPLAY_LOGD("outUsage 0x%{public}" PRIx64 "", outUsage); + return outUsage; +} + +static int32_t InitGbmDevice(const char *drmFile, GrallocManager *grallocManager) +{ + DISPLAY_LOGD(); + if (grallocManager->gbmDevice == NULL) { + char path[PATH_MAX] = {0}; + if (realpath(drmFile, path) == NULL) { + DISPLAY_LOGE(" drm File : %{public}s is not a realpath, errno: %{public}s", drmFile, strerror(errno)); + return DISPLAY_PARAM_ERR; + } + int drmFd = open(path, O_RDWR); + if (drmFd < 0) { + DISPLAY_LOGE("drm file:%{public}s open failed %{public}s", drmFile, strerror(errno)); + return DISPLAY_FD_ERR; + } + /* + if (WaylandDrmAuth(drmFd) != AUTH_SCUCCESS) { + DISPLAY_LOGE("drm authentication failed, may have no permission to allocate memory"); + } + */ + struct gbm_device *gbmDevice = hdi_gbm_create_device(drmFd); + grallocManager->drmFd = drmFd; + if (gbmDevice == NULL) { + close(drmFd); + grallocManager->drmFd = -1; + DISPLAY_LOGE("gbm device create failed"); + return DISPLAY_FAILURE; + } + grallocManager->gbmDevice = gbmDevice; + grallocManager->drmFd = drmFd; + DListHeadInit(&grallocManager->gbmBoHead); + } + return DISPLAY_SUCCESS; +} + +static void DeInitGbmDevice(GrallocManager *grallocManager) +{ + DISPLAY_LOGD(); + hdi_gbm_device_destroy(grallocManager->gbmDevice); + if (grallocManager->drmFd > 0) { + close(grallocManager->drmFd); + grallocManager->drmFd = -1; + } + grallocManager->gbmDevice = NULL; +} + +static int32_t DmaBufferSync(const BufferHandle *handle, bool start) +{ + DISPLAY_LOGD(); + struct dma_buf_sync syncPrm; + errno_t eok = memset_s(&syncPrm, sizeof(syncPrm), 0, sizeof(syncPrm)); + DISPLAY_CHK_RETURN((eok != EOK), DISPLAY_PARAM_ERR, DISPLAY_LOGE("dma buffer sync memset_s failed")); + + if (handle->usage & HBM_USE_CPU_WRITE) { + syncPrm.flags |= DMA_BUF_SYNC_WRITE; + } + + if (handle->usage & HBM_USE_CPU_READ) { + syncPrm.flags |= DMA_BUF_SYNC_READ; + } + + if (start) { + syncPrm.flags |= DMA_BUF_SYNC_START; + } else { + syncPrm.flags |= DMA_BUF_SYNC_END; + } + int retry = 6; + int ret; + do { + ret = ioctl(handle->fd, DMA_BUF_IOCTL_SYNC, &syncPrm); + } while ((retry--) && (ret != -EAGAIN) && (ret != -EINTR)); + + if (ret < 0) { + DISPLAY_LOGE("sync failed"); + return DISPLAY_SYS_BUSY; + } + return DISPLAY_SUCCESS; +} + +static void InitBufferHandle(struct gbm_bo *bo, int fd, const AllocInfo *info, PriBufferHandle *buffer) +{ + BufferHandle *bufferHandle = &(buffer->hdl); + bufferHandle->fd = fd; + bufferHandle->reserveFds = 0; + bufferHandle->reserveInts = 0; + bufferHandle->stride = hdi_gbm_bo_get_stride(bo); + bufferHandle->width = hdi_gbm_bo_get_width(bo); + bufferHandle->height = hdi_gbm_bo_get_height(bo); + bufferHandle->usage = info->usage; + bufferHandle->format = info->format; + bufferHandle->virAddr = NULL; + bufferHandle->size = hdi_gbm_bo_get_stride(bo) * hdi_gbm_bo_get_height(bo); +} + +//static uint64_t GetPhysicalAddr(int fd, int primeFd) +//{ +// struct DrmHisiliconPhyaddr args; +// int ret; +// DISPLAY_LOGD(); +// errno_t eok = memset_s(&args, sizeof(args), 0, sizeof(args)); +// DISPLAY_CHK_RETURN((eok != EOK), 0, DISPLAY_LOGE("memset_s failed")); +// args.fd = primeFd; +// ret = ioctl(fd, DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR, &args); +// if (ret) { +// DISPLAY_LOGE("DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR return failed"); +// } +// DISPLAY_LOGD("DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR return %{public}d args.phyaddr %{public}llx", ret, +// args.phyaddr); +// return args.phyaddr; +//} + +int32_t GbmAllocMem(const AllocInfo *info, BufferHandle **buffer) +{ + DISPLAY_CHK_RETURN((info == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("info is null")); + DISPLAY_CHK_RETURN((buffer == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("buffer is null")); + PriBufferHandle *priBuffer = NULL; + uint32_t drmFmt = ConvertFormatToDrm(info->format); + DISPLAY_CHK_RETURN((drmFmt == INVALID_PIXEL_FMT), DISPLAY_NOT_SUPPORT, + DISPLAY_LOGE("format %{public}d can not support", info->format)); + DISPLAY_LOGD("requeset width %{public}d, heigt %{public}d, format %{public}d", info->width, info->height, drmFmt); + + GRALLOC_LOCK(); + GrallocManager *grallocManager = GetGrallocManager(); + DISPLAY_CHK_RETURN((grallocManager == NULL), DISPLAY_PARAM_ERR, DISPLAY_LOGE("gralloc manager failed"); + GRALLOC_UNLOCK()); + struct gbm_bo *bo = + hdi_gbm_bo_create(grallocManager->gbmDevice, info->width, info->height, drmFmt, ConvertUsageToGbm(info->usage)); + DISPLAY_CHK_RETURN((bo == NULL), DISPLAY_NOMEM, DISPLAY_LOGE("gbm create bo failed"); GRALLOC_UNLOCK()); + + int fd = hdi_gbm_bo_get_fd(bo); + DISPLAY_CHK_RETURN((fd < 0), DISPLAY_FD_ERR, DISPLAY_LOGE("gbm can not get fd"); hdi_gbm_bo_destroy(bo); + GRALLOC_UNLOCK()); + + priBuffer = (PriBufferHandle *)malloc(sizeof(PriBufferHandle)); + DISPLAY_CHK_RETURN((priBuffer == NULL), DISPLAY_NULL_PTR, DISPLAY_LOGE("bufferhandle malloc failed"); goto error); + errno_t eok = memset_s(priBuffer, sizeof(PriBufferHandle), 0, sizeof(PriBufferHandle)); + DISPLAY_CHK_RETURN((eok != EOK), DISPLAY_PARAM_ERR, DISPLAY_LOGE("memset_s failed"); goto error); + + InitBufferHandle(bo, fd, info, priBuffer); +// priBuffer->hdl.phyAddr = GetPhysicalAddr(grallocManager->drmFd, fd); + *buffer = &priBuffer->hdl; + hdi_gbm_bo_destroy(bo); + GRALLOC_UNLOCK(); + return DISPLAY_SUCCESS; +error: + close(fd); + hdi_gbm_bo_destroy(bo); + if (priBuffer != NULL) { + free(priBuffer); + } + GRALLOC_UNLOCK(); + return DISPLAY_FAILURE; +} + +static void CloseBufferHandle(BufferHandle *handle) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((handle == NULL), DISPLAY_LOGE("buffer is null")); + if (handle->fd >= 0) { + close(handle->fd); + handle->fd = -1; + } + const uint32_t reserveFds = handle->reserveFds; + for (uint32_t i = 0; i < reserveFds; i++) { + if (handle->reserve[i] >= 0) { + close(handle->reserve[i]); + handle->reserve[i] = -1; + } + } +} + +void GbmFreeMem(BufferHandle *buffer) +{ + DISPLAY_LOGD(); + DISPLAY_CHK_RETURN_NOT_VALUE((buffer == NULL), DISPLAY_LOGE("buffer is null")); + if ((buffer->virAddr != NULL) && (GbmUnmap(buffer) != DISPLAY_SUCCESS)) { + DISPLAY_LOGE("freeMem unmap buffer failed"); + } + CloseBufferHandle(buffer); + free(buffer); +} + +void *GbmMmap(BufferHandle *buffer) +{ + void *virAddr = NULL; + DISPLAY_LOGD(); + if (buffer == NULL) { + DISPLAY_LOGE("gbmmap the buffer handle is NULL"); + return NULL; + } + if (buffer->virAddr != NULL) { + DISPLAY_LOGD("the buffer has virtual addr"); + return buffer->virAddr; + } + virAddr = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, buffer->fd, 0); + if (virAddr == MAP_FAILED) { + DISPLAY_LOGE("mmap failed errno %{public}s, fd : %{public}d", strerror(errno), buffer->fd); + } + buffer->virAddr = virAddr; + return virAddr; +} + +int32_t GbmUnmap(BufferHandle *buffer) +{ + DISPLAY_LOGD(); + if (buffer == NULL) { + DISPLAY_LOGE("gbmumap the buffer handle is null"); + return DISPLAY_NULL_PTR; + } + + if (buffer->virAddr == NULL) { + DISPLAY_LOGE("virAddr is NULL , has not map the buffer"); + return DISPLAY_PARAM_ERR; + } + int ret = munmap(buffer->virAddr, buffer->size); + if (ret != 0) { + DISPLAY_LOGE("munmap failed err: %{public}s", strerror(errno)); + return DISPLAY_FAILURE; + } + buffer->virAddr = NULL; + return DISPLAY_SUCCESS; +} + +int32_t GbmInvalidateCache(BufferHandle *buffer) +{ + DISPLAY_LOGD(); + return DmaBufferSync(buffer, true); +} + +int32_t GbmFlushCache(BufferHandle *buffer) +{ + DISPLAY_LOGD(); + return DmaBufferSync(buffer, false); +} + +int32_t GbmGrallocUninitialize(void) +{ + DISPLAY_LOGD(); + GRALLOC_LOCK(); + GrallocManager *grallocManager = GetGrallocManager(); + DISPLAY_CHK_RETURN((grallocManager == NULL), DISPLAY_PARAM_ERR, DISPLAY_LOGE("gralloc manager failed"); + GRALLOC_UNLOCK()); + grallocManager->referCount--; + if (grallocManager->referCount < 0) { + DeInitGbmDevice(grallocManager); + free(g_grallocManager); + g_grallocManager = NULL; + } + GRALLOC_UNLOCK(); + return DISPLAY_SUCCESS; +} + +int32_t GbmGrallocInitialize(void) +{ + DISPLAY_LOGD(); + GRALLOC_LOCK(); + GrallocManager *grallocManager = GetGrallocManager(); + DISPLAY_CHK_RETURN((grallocManager == NULL), DISPLAY_PARAM_ERR, DISPLAY_LOGE("gralloc manager failed"); + GRALLOC_UNLOCK()); + int ret = InitGbmDevice(g_drmFileNode, grallocManager); + DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), ret, DISPLAY_LOGE("gralloc manager failed"); GRALLOC_UNLOCK()); + grallocManager->referCount++; + GRALLOC_UNLOCK(); + return DISPLAY_SUCCESS; +} diff --git a/hardware/display/src/display_gralloc/display_gralloc_gbm.h b/hardware/display/src/display_gralloc/display_gralloc_gbm.h new file mode 100644 index 0000000..9320565 --- /dev/null +++ b/hardware/display/src/display_gralloc/display_gralloc_gbm.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DISPLAY_GRALLOC_GBM_H +#define DISPLAY_GRALLOC_GBM_H +#include "display_type.h" +#include "hdf_dlist.h" +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + struct gbm_device *gbmDevice; + int drmFd; + struct DListHead gbmBoHead; + int32_t referCount; +} GrallocManager; + +typedef struct { + struct DListHead entry; + struct gbm_bo *bo; + int fd; +} GbmBoList; + +int32_t GbmAllocMem(const AllocInfo *info, BufferHandle **buffer); +void GbmFreeMem(BufferHandle *buffer); +void *GbmMmap(BufferHandle *buffer); +int32_t GbmUnmap(BufferHandle *buffer); +int32_t GbmInvalidateCache(BufferHandle *buffer); +int32_t GbmFlushCache(BufferHandle *buffer); +int32_t GbmGrallocUninitialize(); +int32_t GbmGrallocInitialize(); + +#ifdef GRALLOC_LOCK_DEBUG +#define GRALLOC_LOCK(format, ...) \ + do { \ + HDF_LOGD("[%{public}s@%{public}s:%{public}d]" format "\n", __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \ + pthread_mutex_lock(&g_lock); \ + } while (0) + +#define GRALLOC_UNLOCK(format, ...) \ + do { \ + HDF_LOGD("[%{public}s@%{public}s:%{public}d]" format "\n", __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \ + pthread_mutex_unlock(&g_lock); \ + } while (0) +#else +#define GRALLOC_LOCK(format, ...) \ + do { \ + pthread_mutex_lock(&g_lock); \ + } while (0) + +#define GRALLOC_UNLOCK(format, ...) \ + do { \ + pthread_mutex_unlock(&g_lock); \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // DISPLAY_GRALLOC_GBM_H \ No newline at end of file diff --git a/hardware/display/src/display_gralloc/hi_gbm.c b/hardware/display/src/display_gralloc/hi_gbm.c new file mode 100644 index 0000000..8e698a4 --- /dev/null +++ b/hardware/display/src/display_gralloc/hi_gbm.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hi_gbm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "display_common.h" +#include "hi_gbm_internal.h" +#ifdef ROCKCHIP_CMA +#define ROCKCHIP_BO_CONTIG (1 << 0) +#endif + +typedef struct { + uint32_t numPlanes; + uint32_t radio[MAX_PLANES]; +} PlaneLayoutInfo; + +typedef struct { + uint32_t format; + uint32_t bitsPerPixel; // bits per pixel for first plane + const PlaneLayoutInfo *planes; +} FormatInfo; + +static const PlaneLayoutInfo g_yuv420SPLayout = { + .numPlanes = 2, + .radio = { 4, 2 }, +}; + +static const PlaneLayoutInfo g_yuv420PLayout = { + .numPlanes = 3, + .radio = { 4, 1, 1 }, +}; + +static const PlaneLayoutInfo g_yuv422SPLayout = { + .numPlanes = 2, + .radio = { 4, 4 }, +}; + +static const PlaneLayoutInfo g_yuv422PLayout = { + .numPlanes = 3, + .radio = { 4, 2, 2 }, +}; + +static const FormatInfo *GetFormatInfo(uint32_t format) +{ + static const FormatInfo fmtInfos[] = { + {DRM_FORMAT_RGBX8888, 32, NULL}, {DRM_FORMAT_RGBA8888, 32, NULL}, + {DRM_FORMAT_BGRX8888, 32, NULL}, {DRM_FORMAT_BGRA8888, 32, NULL}, + {DRM_FORMAT_RGB888, 24, NULL}, {DRM_FORMAT_RGB565, 16, NULL}, + {DRM_FORMAT_BGRX4444, 16, NULL}, {DRM_FORMAT_BGRA4444, 16, NULL}, + {DRM_FORMAT_RGBA4444, 16, NULL}, {DRM_FORMAT_RGBX4444, 16, NULL}, + {DRM_FORMAT_BGRX5551, 16, NULL}, {DRM_FORMAT_BGRA5551, 16, NULL}, + {DRM_FORMAT_NV12, 8, &g_yuv420SPLayout}, {DRM_FORMAT_NV21, 8, &g_yuv420SPLayout}, + {DRM_FORMAT_NV16, 8, &g_yuv422SPLayout}, {DRM_FORMAT_NV61, 8, &g_yuv422SPLayout}, + {DRM_FORMAT_YUV420, 8, &g_yuv420PLayout}, {DRM_FORMAT_YVU420, 8, &g_yuv420PLayout}, + {DRM_FORMAT_YUV422, 8, &g_yuv422PLayout}, {DRM_FORMAT_YVU422, 8, &g_yuv422PLayout}, + }; + + for (uint32_t i = 0; i < sizeof(fmtInfos) / sizeof(FormatInfo); i++) { + if (fmtInfos[i].format == format) { + return &fmtInfos[i]; + } + } + DISPLAY_LOGE("the format can not support"); + return NULL; +} + +void InitGbmBo(struct gbm_bo *bo, const struct drm_mode_create_dumb *dumb) +{ + DISPLAY_CHK_RETURN_NOT_VALUE((dumb == NULL), DISPLAY_LOGE("dumb is null")); + DISPLAY_CHK_RETURN_NOT_VALUE((bo == NULL), DISPLAY_LOGE("bo is null")); + bo->stride = dumb->pitch; + bo->size = dumb->size; + bo->handle = dumb->handle; +} + +static uint32_t AdjustStrideFromFormat(uint32_t format, uint32_t height) +{ + const FormatInfo *fmtInfo = GetFormatInfo(format); + if ((fmtInfo != NULL) && (fmtInfo->planes != NULL)) { + uint32_t sum = fmtInfo->planes->radio[0]; + for (uint32_t i = 1; (i < fmtInfo->planes->numPlanes) && (i < MAX_PLANES); i++) { + sum += fmtInfo->planes->radio[i]; + } + if (sum > 0) { + height = DIV_ROUND_UP((height * sum), fmtInfo->planes->radio[0]); + } + DISPLAY_LOGD("height adjust to : %{public}d", height); + } + return height; +} + +struct gbm_bo *hdi_gbm_bo_create(struct gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, + uint32_t usage) +{ + DISPLAY_UNUSED(usage); + int ret; + struct gbm_bo *bo; + struct drm_mode_create_dumb dumb = { 0 }; + const FormatInfo *fmtInfo = GetFormatInfo(format); + DISPLAY_CHK_RETURN((fmtInfo == NULL), NULL, DISPLAY_LOGE("formt: 0x%{public}x can not get layout info", format)); + bo = (struct gbm_bo *)calloc(1, sizeof(struct gbm_bo)); + DISPLAY_CHK_RETURN((bo == NULL), NULL, DISPLAY_LOGE("gbm bo create fialed no memery")); + (void)memset_s(bo, sizeof(struct gbm_bo), 0, sizeof(struct gbm_bo)); + bo->width = width; + bo->height = height; + bo->gbm = gbm; + bo->format = format; + // init create_dumb + dumb.height = ALIGN_UP(height, HEIGHT_ALIGN); + dumb.width = ALIGN_UP(AdjustStrideFromFormat(format, width), WIDTH_ALIGN); + dumb.flags = 0; + dumb.bpp = fmtInfo->bitsPerPixel; + ret = drmIoctl(gbm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &dumb); + DISPLAY_LOGI("fmt 0x%{public}x create dumb width: %{public}d height: %{public}d bpp: %{public}u pitch %{public}d " + "size %{public}llu", + format, dumb.width, dumb.height, dumb.bpp, dumb.pitch, dumb.size); + DISPLAY_CHK_RETURN((ret != 0), NULL, DISPLAY_LOGE("DRM_IOCTL_MODE_CREATE_DUMB failed errno %{public}d", errno)); + InitGbmBo(bo, &dumb); + DISPLAY_LOGI( + "fmt 0x%{public}x create dumb width: %{public}d height: %{public}d stride %{public}d size %{public}u", format, + bo->width, bo->height, bo->stride, bo->size); + return bo; +} + +struct gbm_device *hdi_gbm_create_device(int fd) +{ + struct gbm_device *gbm; + gbm = (struct gbm_device *)calloc(1, sizeof(struct gbm_device)); + DISPLAY_CHK_RETURN((gbm == NULL), NULL, DISPLAY_LOGE("memory calloc failed")); + gbm->fd = fd; + return gbm; +} + +void hdi_gbm_device_destroy(struct gbm_device *gbm) +{ + free(gbm); +} + +uint32_t hdi_gbm_bo_get_stride(struct gbm_bo *bo) +{ + DISPLAY_CHK_RETURN((bo == NULL), 0, DISPLAY_LOGE("the bo is null")); + return bo->stride; +} + +uint32_t hdi_gbm_bo_get_width(struct gbm_bo *bo) +{ + DISPLAY_CHK_RETURN((bo == NULL), 0, DISPLAY_LOGE("the bo is null")); + return bo->width; +} + +uint32_t hdi_gbm_bo_get_height(struct gbm_bo *bo) +{ + DISPLAY_CHK_RETURN((bo == NULL), 0, DISPLAY_LOGE("the bo is null")); + return bo->height; +} + +void hdi_gbm_bo_destroy(struct gbm_bo *bo) +{ + int ret; + DISPLAY_CHK_RETURN_NOT_VALUE((bo == NULL), DISPLAY_LOGE("the bo is null")); + struct drm_mode_destroy_dumb dumb = { 0 }; + dumb.handle = bo->handle; + ret = drmIoctl(bo->gbm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dumb); + DISPLAY_CHK_RETURN_NOT_VALUE((ret), DISPLAY_LOGE("dumb buffer destroy failed errno %{public}d", errno)); + free(bo); +} + +int hdi_gbm_bo_get_fd(struct gbm_bo *bo) +{ + int fd, ret; + ret = drmPrimeHandleToFD(bo->gbm->fd, bo->handle, DRM_CLOEXEC | DRM_RDWR, &fd); + DISPLAY_CHK_RETURN((ret), -1, + DISPLAY_LOGE("drmPrimeHandleToFD failed ret: %{public}d errno: %{public}d", ret, errno)); + return fd; +} \ No newline at end of file diff --git a/hardware/display/src/display_gralloc/hi_gbm.h b/hardware/display/src/display_gralloc/hi_gbm.h new file mode 100644 index 0000000..259aa37 --- /dev/null +++ b/hardware/display/src/display_gralloc/hi_gbm.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HI_GBM_H +#define HI_GBM_H +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct gbm_device; +struct gbm_bo; + +enum gbm_bo_flags { + /* * + * Buffer is going to be presented to the screen using an API such as KMS + */ + GBM_BO_USE_SCANOUT = (1 << 0), + /* * + * Buffer is going to be used as cursor + */ + GBM_BO_USE_CURSOR = (1 << 1), + /* * + * Deprecated + */ + GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR, + /* * + * Buffer is to be used for rendering - for example it is going to be used + * as the storage for a color buffer + */ + GBM_BO_USE_RENDERING = (1 << 2), + /* * + * Deprecated + */ + GBM_BO_USE_WRITE = (1 << 3), + /* * + * Buffer is guaranteed to be laid out linearly in memory. That is, the + * buffer is laid out as an array with 'height' blocks, each block with + * length 'stride'. Each stride is in the same order as the rows of the + * buffer. This is intended to be used with buffers that will be accessed + * via dma-buf mmap(). + */ + GBM_BO_USE_LINEAR = (1 << 4), + /* * + * The buffer will be used as a texture that will be sampled from. + */ + GBM_BO_USE_TEXTURING = (1 << 5), + /* * + * The buffer will be written to by a camera subsystem. + */ + GBM_BO_USE_CAMERA_WRITE = (1 << 6), + /* * + * The buffer will be read from by a camera subsystem. + */ + GBM_BO_USE_CAMERA_READ = (1 << 7), + /* * + * Buffer inaccessible to unprivileged users. + */ + GBM_BO_USE_PROTECTED = (1 << 8), + /* * + * These flags specify the frequency of software access. These flags do not + * guarantee the buffer is linear, but do guarantee gbm_bo_map(..) will + * present a linear view. + */ + GBM_BO_USE_SW_READ_OFTEN = (1 << 9), + GBM_BO_USE_SW_READ_RARELY = (1 << 10), + GBM_BO_USE_SW_WRITE_OFTEN = (1 << 11), + GBM_BO_USE_SW_WRITE_RARELY = (1 << 12), + /* * + * The buffer will be written by a video decode accelerator. + */ + GBM_BO_USE_HW_VIDEO_DECODER = (1 << 13), +}; + +struct gbm_device *hdi_gbm_create_device(int fd); +void hdi_gbm_device_destroy(struct gbm_device *gbm); +struct gbm_bo *hdi_gbm_bo_create(struct gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, uint32_t usage); +uint32_t hdi_gbm_bo_get_stride(struct gbm_bo *bo); +uint32_t hdi_gbm_bo_get_width(struct gbm_bo *bo); +uint32_t hdi_gbm_bo_get_height(struct gbm_bo *bo); +void hdi_gbm_bo_destroy(struct gbm_bo *bo); +int hdi_gbm_bo_get_fd(struct gbm_bo *bo); + +#if defined(__cplusplus) +} +#endif +#endif // HI_GBM_H \ No newline at end of file diff --git a/hardware/display/src/display_gralloc/hi_gbm_internal.h b/hardware/display/src/display_gralloc/hi_gbm_internal.h new file mode 100644 index 0000000..3155bb6 --- /dev/null +++ b/hardware/display/src/display_gralloc/hi_gbm_internal.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HI_GBM_INTERNEL_H +#define HI_GBM_INTERNEL_H + +#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d)) +#define ALIGN_UP(x, a) ((((x) + ((a)-1)) / (a)) * (a)) +#define HEIGHT_ALIGN 2U +#define WIDTH_ALIGN 8U + +#define MAX_PLANES 3 + +struct gbm_device { + int fd; +}; + +struct gbm_bo { + struct gbm_device *gbm; + uint32_t width; + uint32_t height; + uint32_t format; + uint32_t handle; + uint32_t stride; + uint32_t size; +}; + +#endif // HI_GBM_INTERNEL_H \ No newline at end of file diff --git a/hardware/display/src/display_gralloc/hisilicon_drm.h b/hardware/display/src/display_gralloc/hisilicon_drm.h new file mode 100644 index 0000000..eed389e --- /dev/null +++ b/hardware/display/src/display_gralloc/hisilicon_drm.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HISILICON_DRM_H +#define HISILICON_DRM_H + +#include "drm.h" + +#if defined(__cplusplus) +extern "C" { +#endif +#define DRM_HISILICON_GEM_FD_TO_PHYADDR 0x1 + +struct DrmHisiliconPhyaddr { + /* * return the physical address */ + __u64 phyaddr; + /* * dmabuf file descriptor */ + __s32 fd; +}; + +#define DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_HISILICON_GEM_FD_TO_PHYADDR, struct DrmHisiliconPhyaddr) + +#if defined(__cplusplus) +} +#endif + +#endif // HISILICON_DRM_H diff --git a/hardware/display/src/display_gralloc/wayland_drm_auth_client.c b/hardware/display/src/display_gralloc/wayland_drm_auth_client.c new file mode 100644 index 0000000..4f30624 --- /dev/null +++ b/hardware/display/src/display_gralloc/wayland_drm_auth_client.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wayland_drm_auth_client.h" +#include +#include +#include "xf86drm.h" +#include "wayland-client.h" +#include "drm-auth-client-protocol.h" +#include "display_common.h" + +typedef struct { + struct wl_display *display; + struct wl_registry *registry; + struct wl_drm_auth *drmAuth; + enum wl_drm_auth_status authStatus; +} WaylandDisplay; + +const char *AUTH_INTERFACE_NAME = "wl_drm_auth"; + +static void AuthenticationStatus(void *data, struct wl_drm_auth *wlDrmAuth, uint32_t status) +{ + (void)wlDrmAuth; + DISPLAY_LOGD("AuthenticationStatus the status %{public}d", status); + WaylandDisplay *display = data; + display->authStatus = status; +} + +static const struct wl_drm_auth_listener g_drmAuthListener = { AuthenticationStatus }; + +static void RegistryHandleGlobal(void *data, struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version) +{ + WaylandDisplay *display = data; + DISPLAY_LOGD("interface global : %{public}s", interface); + if (strcmp(interface, wl_drm_auth_interface.name) == 0) { + display->drmAuth = wl_registry_bind(registry, id, &wl_drm_auth_interface, 1); + wl_drm_auth_add_listener(display->drmAuth, &g_drmAuthListener, display); + } +} + +static void RegistryHandleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) +{ + DISPLAY_LOGD("RegistryHandleGlobalRemove %{publuc}d name ", name); +} + +static const struct wl_registry_listener g_registrListener = { RegistryHandleGlobal, RegistryHandleGlobalRemove }; + +void DeInitWaylandClient(WaylandDisplay *display) +{ + DISPLAY_LOGD("DeInitWaylandClient"); + DISPLAY_CHK_RETURN_NOT_VALUE((display == NULL), DISPLAY_LOGD("display is NULL")); + if (display->registry != NULL) { + wl_registry_destroy(display->registry); + } + + if (display->display != NULL) { + wl_display_flush(display->display); + wl_display_disconnect(display->display); + } + free(display); +} + +WaylandDisplay *InitWaylandClient() +{ + WaylandDisplay *dsp; + int ret; + dsp = calloc(1, sizeof(WaylandDisplay)); + DISPLAY_CHK_RETURN((dsp == NULL), NULL, DISPLAY_LOGE("can not alloc memory errno : %{public}d", errno)); + dsp->display = wl_display_connect(NULL); + DISPLAY_CHK_RETURN((dsp->display == NULL), NULL, DISPLAY_LOGE("display connect failed, errno: %{public}d", errno); + DeInitWaylandClient(dsp)); + dsp->registry = wl_display_get_registry(dsp->display); + DISPLAY_CHK_RETURN((dsp->registry == NULL), NULL, DISPLAY_LOGE("can not get registry"); DeInitWaylandClient(dsp)); + ret = wl_registry_add_listener(dsp->registry, &g_registrListener, dsp); + DISPLAY_CHK_RETURN((ret < 0), NULL, DISPLAY_LOGE("add listener failed")); + wl_display_roundtrip(dsp->display); // for get registry + wl_display_roundtrip(dsp->display); // for the listener will bind the service + return dsp; +} + +int32_t WaylandDrmAuth(int drmFd) +{ + WaylandDisplay *dsp; + drm_magic_t magic; + int ret; + dsp = InitWaylandClient(); + DISPLAY_CHK_RETURN((dsp == NULL), AUTH_FAILED, DISPLAY_LOGE("init wayland client failed")); + ret = drmGetMagic(drmFd, &magic); + DISPLAY_CHK_RETURN((ret != 0), AUTH_FAILED, DISPLAY_LOGE("can not get magic")); + DISPLAY_CHK_RETURN((dsp->drmAuth == NULL), AUTH_FAILED, DISPLAY_LOGE("drm auth service no find")); + wl_drm_auth_authenticate(dsp->drmAuth, magic); + wl_display_roundtrip(dsp->display); // wait for authenticate status return + DISPLAY_LOGD("the status of authenticate is %{public}d", dsp->authStatus); + if (dsp->authStatus == WL_DRM_AUTH_STATUS_SUCCESS) { + ret = AUTH_SCUCCESS; + } + DeInitWaylandClient(dsp); + return ret; +} diff --git a/hardware/display/src/display_gralloc/wayland_drm_auth_client.h b/hardware/display/src/display_gralloc/wayland_drm_auth_client.h new file mode 100644 index 0000000..69be0fa --- /dev/null +++ b/hardware/display/src/display_gralloc/wayland_drm_auth_client.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WAYLAND_DRM_AUTH_CLIENT_H +#define WAYLAND_DRM_AUTH_CLIENT_H +#include + +typedef enum { + AUTH_SCUCCESS = 0, /* * authenticate sucess */ + AUTH_FAILED = 1 /* * authenticate failed */ +} AuthStatus; + +/* * + * @brief authenticate the drm fd + * + * it will connect to the wayland server, and will block to authenticate the drm fd, then disconnect the wayland + * + * @param display Indicates the pointer of wayland display + * + * @param drmFd Indicates the file descriptor of drm device + * + * @return Returns AUTH_SCUCCESS if the operation is successful else returns AUTH_FAILED + * otherwise. + * @since 1.0 + * @version 1.0 + */ +int32_t WaylandDrmAuth(int drmFd); + +#endif // WAYLAND_DRM_AUTH_CLIENT_H diff --git a/rk3568/build/rootfs/BUILD.gn b/rk3568/build/rootfs/BUILD.gn index 319a930..73ecdf5 100755 --- a/rk3568/build/rootfs/BUILD.gn +++ b/rk3568/build/rootfs/BUILD.gn @@ -20,6 +20,14 @@ ohos_prebuilt_etc("init.rk3568.cfg") { install_enable = true } +ohos_prebuilt_executable("weston.cfg") { + install_enable = true + source = "weston.cfg" + module_install_dir = "etc/init" + install_images = [ "system" ] + part_name = "rockchip_products" +} + ohos_prebuilt_etc("init.rk3568.usb.cfg") { source = "init.rk3568.usb.cfg" install_images = [ "system" ] @@ -51,6 +59,7 @@ ohos_prebuilt_executable("display-hotplug.sh") { group("init_configs") { deps = [ ":display-hotplug.sh", + ":weston.cfg", ":init.rk3568.cfg", ":init.rk3568.usb.cfg", ":init.cfg", diff --git a/rk3568/build/rootfs/weston.cfg b/rk3568/build/rootfs/weston.cfg new file mode 100755 index 0000000..f01a769 --- /dev/null +++ b/rk3568/build/rootfs/weston.cfg @@ -0,0 +1,42 @@ +{ + "jobs" : [{ + "name" : "post-fs", + "cmds" : [ + "start udevd_service", + "sleep 1", + "start mmi_uinput_service", + "sleep 2", + "export XDG_RUNTIME_DIR /data/weston", + "export XKB_CONFIG_ROOT /etc/xkb", + "export XKB_CONFIG_EXTRA_PATH /etc/xkb", + "mkdir /data/weston", + "chmod 777 /data/weston", + "start weston", + "trigger weston_start", + "sleep 2", + "exec /system/bin/udevadm trigger", + "exec /system/bin/udevadm settle --timeout=30" + ] + } + ], + "services" : [{ + "name" : "weston", + "path" : ["/system/bin/weston", "-c", "/system/etc/weston.ini", "-B", "drm-backend.so", "--tty=1"], + "disabled" : 1 + }, { + "name" : "hdi_weston", + "path" : ["/system/bin/weston", "-c", "/system/etc/weston.ini", "-B", "hdi-backend.so"], + "disabled" : 1 + }, { + "name" : "mmi_uinput_service", + "path" : ["/system/bin/uinput_inject"], + "uid" : "root", + "gid" : ["system", "shell", "uhid"] + }, { + "name" : "udevd_service", + "path" : ["/system/bin/udevd"], + "uid" : "root", + "gid" : ["system"] + } + ] +} diff --git a/rk3568/camera/BUILD.gn b/rk3568/camera/BUILD.gn index 50850a9..ce080cd 100755 --- a/rk3568/camera/BUILD.gn +++ b/rk3568/camera/BUILD.gn @@ -13,17 +13,11 @@ import("//build/ohos.gni") import("//drivers/adapter/uhdf2/uhdf.gni") import("//drivers/peripheral/camera/hal/camera.gni") +import("$hdf_framework_path/tools/hc-gen/hc_gen.gni") -action("build_camera_host_config") { - script = "$hdf_framework_path/tools/hc-gen/build_hcs.py" +hc_gen("build_camera_host_config") { sources = [ rebase_path( "$camera_product_name_path/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs") ] - outputs = [ "$target_gen_dir/config/hdi_impl/camera_host_config.hcb" ] - args = [ - "-o", - rebase_path(outputs[0]), - sources[0], - ] } ohos_prebuilt_etc("camera_host_config.hcb") { @@ -35,48 +29,55 @@ ohos_prebuilt_etc("camera_host_config.hcb") { part_name = "hdf" } +hc_gen_c("generate_source") { + sources = [ + "$camera_product_name_path/hdf_config/uhdf/camera/pipeline_core/config.hcs", + "$camera_product_name_path/hdf_config/uhdf/camera/pipeline_core/params.hcs", + ] +} + +action("copy_source") { + script = "/usr/bin/env" + outputs = [ "$target_out_dir/tmp.c" ] # no use, just for gn complains + args = [ + "cp", + "-f", + ] + args += rebase_path(get_target_outputs(":generate_source")) + args += [ rebase_path( + "$camera_path/pipeline_core/pipeline_impl/src/strategy/config/") ] + deps = [ ":generate_source" ] +} + ohos_prebuilt_etc("config.c") { + deps = [ ":copy_source" ] source = "$camera_path/pipeline_core/pipeline_impl/src/strategy/config/config.c" exec_script( - "//drivers/framework/tools/hc-gen/build_hcs.py", + "/usr/bin/env", [ - "-o", + "touch", rebase_path( "$camera_path/pipeline_core/pipeline_impl/src/strategy/config/config.c"), - "-t", - rebase_path( - "$camera_product_name_path/hdf_config/uhdf/camera/pipeline_core/config.hcs"), - ], - "") + ]) } ohos_prebuilt_etc("params.c") { + deps = [ ":copy_source" ] source = "$camera_path/pipeline_core/pipeline_impl/src/strategy/config/params.c" exec_script( - "//drivers/framework/tools/hc-gen/build_hcs.py", + "/usr/bin/env", [ - "-o", + "touch", rebase_path( "$camera_path/pipeline_core/pipeline_impl/src/strategy/config/params.c"), - "-t", - rebase_path( - "$camera_product_name_path/hdf_config/uhdf/camera/pipeline_core/params.hcs"), - ], - "") + ]) } -action("build_ipp_algo_config") { - script = "$hdf_framework_path/tools/hc-gen/build_hcs.py" +hc_gen("build_ipp_algo_config") { sources = [ rebase_path( "$camera_product_name_path/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs") ] - outputs = [ "$target_gen_dir/pipeline_core/ipp_algo_config.hcb" ] - args = [ - "-o", - rebase_path(outputs[0]), - sources[0], - ] } ohos_prebuilt_etc("ipp_algo_config.hcb") { -- Gitee