From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/vkms/Kconfig | 32 + drivers/gpu/drm/vkms/Makefile | 17 + drivers/gpu/drm/vkms/tests/.kunitconfig | 4 + drivers/gpu/drm/vkms/tests/Makefile | 8 + drivers/gpu/drm/vkms/tests/vkms_color_test.c | 414 ++++++++++ drivers/gpu/drm/vkms/tests/vkms_config_test.c | 1029 +++++++++++++++++++++++++ drivers/gpu/drm/vkms/tests/vkms_format_test.c | 279 +++++++ drivers/gpu/drm/vkms/vkms_colorop.c | 129 ++++ drivers/gpu/drm/vkms/vkms_composer.c | 735 ++++++++++++++++++ drivers/gpu/drm/vkms/vkms_composer.h | 28 + drivers/gpu/drm/vkms/vkms_config.c | 649 ++++++++++++++++ drivers/gpu/drm/vkms/vkms_config.h | 489 ++++++++++++ drivers/gpu/drm/vkms/vkms_configfs.c | 843 ++++++++++++++++++++ drivers/gpu/drm/vkms/vkms_configfs.h | 8 + drivers/gpu/drm/vkms/vkms_connector.c | 96 +++ drivers/gpu/drm/vkms/vkms_connector.h | 35 + drivers/gpu/drm/vkms/vkms_crtc.c | 241 ++++++ drivers/gpu/drm/vkms/vkms_drv.c | 287 +++++++ drivers/gpu/drm/vkms/vkms_drv.h | 329 ++++++++ drivers/gpu/drm/vkms/vkms_formats.c | 971 +++++++++++++++++++++++ drivers/gpu/drm/vkms/vkms_formats.h | 21 + drivers/gpu/drm/vkms/vkms_luts.c | 811 +++++++++++++++++++ drivers/gpu/drm/vkms/vkms_luts.h | 12 + drivers/gpu/drm/vkms/vkms_output.c | 121 +++ drivers/gpu/drm/vkms/vkms_plane.c | 253 ++++++ drivers/gpu/drm/vkms/vkms_writeback.c | 188 +++++ 26 files changed, 8029 insertions(+) create mode 100644 drivers/gpu/drm/vkms/Kconfig create mode 100644 drivers/gpu/drm/vkms/Makefile create mode 100644 drivers/gpu/drm/vkms/tests/.kunitconfig create mode 100644 drivers/gpu/drm/vkms/tests/Makefile create mode 100644 drivers/gpu/drm/vkms/tests/vkms_color_test.c create mode 100644 drivers/gpu/drm/vkms/tests/vkms_config_test.c create mode 100644 drivers/gpu/drm/vkms/tests/vkms_format_test.c create mode 100644 drivers/gpu/drm/vkms/vkms_colorop.c create mode 100644 drivers/gpu/drm/vkms/vkms_composer.c create mode 100644 drivers/gpu/drm/vkms/vkms_composer.h create mode 100644 drivers/gpu/drm/vkms/vkms_config.c create mode 100644 drivers/gpu/drm/vkms/vkms_config.h create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.c create mode 100644 drivers/gpu/drm/vkms/vkms_configfs.h create mode 100644 drivers/gpu/drm/vkms/vkms_connector.c create mode 100644 drivers/gpu/drm/vkms/vkms_connector.h create mode 100644 drivers/gpu/drm/vkms/vkms_crtc.c create mode 100644 drivers/gpu/drm/vkms/vkms_drv.c create mode 100644 drivers/gpu/drm/vkms/vkms_drv.h create mode 100644 drivers/gpu/drm/vkms/vkms_formats.c create mode 100644 drivers/gpu/drm/vkms/vkms_formats.h create mode 100644 drivers/gpu/drm/vkms/vkms_luts.c create mode 100644 drivers/gpu/drm/vkms/vkms_luts.h create mode 100644 drivers/gpu/drm/vkms/vkms_output.c create mode 100644 drivers/gpu/drm/vkms/vkms_plane.c create mode 100644 drivers/gpu/drm/vkms/vkms_writeback.c (limited to 'drivers/gpu/drm/vkms') diff --git a/drivers/gpu/drm/vkms/Kconfig b/drivers/gpu/drm/vkms/Kconfig new file mode 100644 index 000000000..3977bbb99 --- /dev/null +++ b/drivers/gpu/drm/vkms/Kconfig @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config DRM_VKMS + tristate "Virtual KMS (EXPERIMENTAL)" + depends on DRM && MMU + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_SHMEM_HELPER + select CRC32 + select CONFIGFS_FS + default n + help + Virtual Kernel Mode-Setting (VKMS) is used for testing or for + running GPU in a headless machines. Choose this option to get + a VKMS. + + If M is selected the module will be called vkms. + +config DRM_VKMS_KUNIT_TEST + tristate "KUnit tests for VKMS" if !KUNIT_ALL_TESTS + depends on DRM_VKMS && KUNIT + default KUNIT_ALL_TESTS + help + This builds unit tests for VKMS. This option is not useful for + distributions or general kernels, but only for kernel + developers working on VKMS. + + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + + If in doubt, say "N". diff --git a/drivers/gpu/drm/vkms/Makefile b/drivers/gpu/drm/vkms/Makefile new file mode 100644 index 000000000..9bb264091 --- /dev/null +++ b/drivers/gpu/drm/vkms/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +vkms-y := \ + vkms_drv.o \ + vkms_plane.o \ + vkms_output.o \ + vkms_formats.o \ + vkms_crtc.o \ + vkms_composer.o \ + vkms_writeback.o \ + vkms_connector.o \ + vkms_config.o \ + vkms_configfs.o \ + vkms_colorop.o \ + vkms_luts.o + +obj-$(CONFIG_DRM_VKMS) += vkms.o +obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += tests/ diff --git a/drivers/gpu/drm/vkms/tests/.kunitconfig b/drivers/gpu/drm/vkms/tests/.kunitconfig new file mode 100644 index 000000000..6a2d87068 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_DRM=y +CONFIG_DRM_VKMS=y +CONFIG_DRM_VKMS_KUNIT_TEST=y diff --git a/drivers/gpu/drm/vkms/tests/Makefile b/drivers/gpu/drm/vkms/tests/Makefile new file mode 100644 index 000000000..d4d9ba8d4 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +vkms-kunit-tests-y := \ + vkms_config_test.o \ + vkms_format_test.o \ + vkms_color_test.o + +obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += vkms-kunit-tests.o diff --git a/drivers/gpu/drm/vkms/tests/vkms_color_test.c b/drivers/gpu/drm/vkms/tests/vkms_color_test.c new file mode 100644 index 000000000..1a1c7cac2 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/vkms_color_test.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include +#include "../vkms_composer.h" +#include "../vkms_drv.h" +#include "../vkms_luts.h" + +#define TEST_LUT_SIZE 16 + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +static struct drm_color_lut test_linear_array[TEST_LUT_SIZE] = { + { 0x0, 0x0, 0x0, 0 }, + { 0x1111, 0x1111, 0x1111, 0 }, + { 0x2222, 0x2222, 0x2222, 0 }, + { 0x3333, 0x3333, 0x3333, 0 }, + { 0x4444, 0x4444, 0x4444, 0 }, + { 0x5555, 0x5555, 0x5555, 0 }, + { 0x6666, 0x6666, 0x6666, 0 }, + { 0x7777, 0x7777, 0x7777, 0 }, + { 0x8888, 0x8888, 0x8888, 0 }, + { 0x9999, 0x9999, 0x9999, 0 }, + { 0xaaaa, 0xaaaa, 0xaaaa, 0 }, + { 0xbbbb, 0xbbbb, 0xbbbb, 0 }, + { 0xcccc, 0xcccc, 0xcccc, 0 }, + { 0xdddd, 0xdddd, 0xdddd, 0 }, + { 0xeeee, 0xeeee, 0xeeee, 0 }, + { 0xffff, 0xffff, 0xffff, 0 }, +}; + +/* lerp test parameters */ +struct vkms_color_test_lerp_params { + s64 t; + __u16 a; + __u16 b; + __u16 expected; +}; + +/* lerp test cases */ +static const struct vkms_color_test_lerp_params color_test_lerp_cases[] = { + /* Half-way round down */ + { 0x80000000 - 1, 0x0, 0x10, 0x8 }, + { 0x80000000 - 1, 0x1, 0x10, 0x8 }, /* Odd a */ + { 0x80000000 - 1, 0x1, 0xf, 0x8 }, /* Odd b */ + { 0x80000000 - 1, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x80000000 - 1, 0x10, 0x11, 0x10 }, /* b = a + 1*/ + /* Half-way round up */ + { 0x80000000, 0x0, 0x10, 0x8 }, + { 0x80000000, 0x1, 0x10, 0x9 }, /* Odd a */ + { 0x80000000, 0x1, 0xf, 0x8 }, /* Odd b */ + { 0x80000000, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x80000000, 0x10, 0x11, 0x11 }, /* b = a + 1*/ + /* t = 0.0 */ + { 0x0, 0x0, 0x10, 0x0 }, + { 0x0, 0x1, 0x10, 0x1 }, /* Odd a */ + { 0x0, 0x1, 0xf, 0x1 }, /* Odd b */ + { 0x0, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x0, 0x10, 0x11, 0x10 }, /* b = a + 1*/ + /* t = 1.0 */ + { 0x100000000, 0x0, 0x10, 0x10 }, + { 0x100000000, 0x1, 0x10, 0x10 }, /* Odd a */ + { 0x100000000, 0x1, 0xf, 0xf }, /* Odd b */ + { 0x100000000, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x100000000, 0x10, 0x11, 0x11 }, /* b = a + 1*/ + /* t = 0.0 + 1 */ + { 0x0 + 1, 0x0, 0x10, 0x0 }, + { 0x0 + 1, 0x1, 0x10, 0x1 }, /* Odd a */ + { 0x0 + 1, 0x1, 0xf, 0x1 }, /* Odd b */ + { 0x0 + 1, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x0 + 1, 0x10, 0x11, 0x10 }, /* b = a + 1*/ + /* t = 1.0 - 1 */ + { 0x100000000 - 1, 0x0, 0x10, 0x10 }, + { 0x100000000 - 1, 0x1, 0x10, 0x10 }, /* Odd a */ + { 0x100000000 - 1, 0x1, 0xf, 0xf }, /* Odd b */ + { 0x100000000 - 1, 0x10, 0x10, 0x10 }, /* b = a */ + { 0x100000000 - 1, 0x10, 0x11, 0x11 }, /* b = a + 1*/ + /* t chosen to verify the flipping point of result a (or b) to a+1 (or b-1) */ + { 0x80000000 - 1, 0x0, 0x1, 0x0 }, + { 0x80000000, 0x0, 0x1, 0x1 }, +}; + +static const struct vkms_color_lut test_linear_lut = { + .base = test_linear_array, + .lut_length = TEST_LUT_SIZE, + .channel_value2index_ratio = 0xf000fll +}; + +static void vkms_color_test_get_lut_index(struct kunit *test) +{ + s64 lut_index; + int i; + + lut_index = get_lut_index(&test_linear_lut, test_linear_array[0].red); + KUNIT_EXPECT_EQ(test, drm_fixp2int(lut_index), 0); + + for (i = 0; i < TEST_LUT_SIZE; i++) { + lut_index = get_lut_index(&test_linear_lut, test_linear_array[i].red); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(lut_index), i); + } + + KUNIT_EXPECT_EQ(test, drm_fixp2int(get_lut_index(&srgb_eotf, 0x0)), 0x0); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_eotf, 0x0)), 0x0); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_eotf, 0x101)), 0x1); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_eotf, 0x202)), 0x2); + + KUNIT_EXPECT_EQ(test, drm_fixp2int(get_lut_index(&srgb_inv_eotf, 0x0)), 0x0); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_inv_eotf, 0x0)), 0x0); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_inv_eotf, 0x101)), 0x1); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_inv_eotf, 0x202)), 0x2); + + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_eotf, 0xfefe)), 0xfe); + KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_lut_index(&srgb_eotf, 0xffff)), 0xff); +} + +static void vkms_color_test_lerp(struct kunit *test) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(color_test_lerp_cases); i++) { + const struct vkms_color_test_lerp_params *params = &color_test_lerp_cases[i]; + + KUNIT_EXPECT_EQ(test, lerp_u16(params->a, params->b, params->t), params->expected); + } +} + +static void vkms_color_test_linear(struct kunit *test) +{ + for (int i = 0; i < LUT_SIZE; i++) { + int linear = apply_lut_to_channel_value(&linear_eotf, i * 0x101, LUT_RED); + + KUNIT_EXPECT_EQ(test, DIV_ROUND_CLOSEST(linear, 0x101), i); + } +} + +static void vkms_color_srgb_inv_srgb(struct kunit *test) +{ + u16 srgb, final; + + for (int i = 0; i < LUT_SIZE; i++) { + srgb = apply_lut_to_channel_value(&srgb_eotf, i * 0x101, LUT_RED); + final = apply_lut_to_channel_value(&srgb_inv_eotf, srgb, LUT_RED); + + KUNIT_EXPECT_GE(test, final / 0x101, i - 1); + KUNIT_EXPECT_LE(test, final / 0x101, i + 1); + } +} + +#define FIXPT_HALF (DRM_FIXED_ONE >> 1) +#define FIXPT_QUARTER (DRM_FIXED_ONE >> 2) + +static const struct drm_color_ctm_3x4 test_matrix_3x4_50_desat = { { + FIXPT_HALF, FIXPT_QUARTER, FIXPT_QUARTER, 0, + FIXPT_QUARTER, FIXPT_HALF, FIXPT_QUARTER, 0, + FIXPT_QUARTER, FIXPT_QUARTER, FIXPT_HALF, 0 +} }; + +static void vkms_color_ctm_3x4_50_desat(struct kunit *test) +{ + struct pixel_argb_s32 ref, out; + + /* full white */ + ref.a = 0xffff; + ref.r = 0xffff; + ref.g = 0xffff; + ref.b = 0xffff; + + memcpy(&out, &ref, sizeof(out)); + apply_3x4_matrix(&out, &test_matrix_3x4_50_desat); + + KUNIT_EXPECT_MEMEQ(test, &ref, &out, sizeof(out)); + + /* full black */ + ref.a = 0xffff; + ref.r = 0x0; + ref.g = 0x0; + ref.b = 0x0; + + memcpy(&out, &ref, sizeof(out)); + apply_3x4_matrix(&out, &test_matrix_3x4_50_desat); + + KUNIT_EXPECT_MEMEQ(test, &ref, &out, sizeof(out)); + + /* 50% grey */ + ref.a = 0xffff; + ref.r = 0x8000; + ref.g = 0x8000; + ref.b = 0x8000; + + memcpy(&out, &ref, sizeof(out)); + apply_3x4_matrix(&out, &test_matrix_3x4_50_desat); + + KUNIT_EXPECT_MEMEQ(test, &ref, &out, sizeof(out)); + + /* full red to 50% desat */ + ref.a = 0xffff; + ref.r = 0x8000; + ref.g = 0x4000; + ref.b = 0x4000; + + out.a = 0xffff; + out.r = 0xffff; + out.g = 0x0; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_50_desat); + + KUNIT_EXPECT_MEMEQ(test, &ref, &out, sizeof(out)); +} + +/* + * BT.709 encoding matrix + * + * Values printed from within IGT when converting + * igt_matrix_3x4_bt709_enc to the fixed-point format expected + * by DRM/KMS. + */ +static const struct drm_color_ctm_3x4 test_matrix_3x4_bt709_enc = { { + 0x00000000366cf400ull, 0x00000000b7175900ull, 0x0000000127bb300ull, 0, + 0x800000001993b3a0ull, 0x800000005609fe80ull, 0x000000006f9db200ull, 0, + 0x000000009d70a400ull, 0x800000008f011100ull, 0x800000000e6f9330ull, 0 +} }; + +static void vkms_color_ctm_3x4_bt709(struct kunit *test) +{ + struct pixel_argb_s32 out; + + /* full white to bt709 */ + out.a = 0xffff; + out.r = 0xffff; + out.g = 0xffff; + out.b = 0xffff; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 255 */ + KUNIT_EXPECT_GT(test, out.r, 0xfe00); + KUNIT_EXPECT_LT(test, out.r, 0x10000); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x0100); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x0100); + + /* full black to bt709 */ + out.a = 0xffff; + out.r = 0x0; + out.g = 0x0; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 0 */ + KUNIT_EXPECT_LT(test, out.r, 0x100); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x0100); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x0100); + + /* gray to bt709 */ + out.a = 0xffff; + out.r = 0x7fff; + out.g = 0x7fff; + out.b = 0x7fff; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 127 */ + KUNIT_EXPECT_GT(test, out.r, 0x7e00); + KUNIT_EXPECT_LT(test, out.r, 0x8000); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x0100); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x0100); + + /* == red 255 - bt709 enc == */ + out.a = 0xffff; + out.r = 0xffff; + out.g = 0x0; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 54 */ + KUNIT_EXPECT_GT(test, out.r, 0x3500); + KUNIT_EXPECT_LT(test, out.r, 0x3700); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x0100); + + /* V 157 */ + KUNIT_EXPECT_GT(test, out.b, 0x9C00); + KUNIT_EXPECT_LT(test, out.b, 0x9E00); + + /* == green 255 - bt709 enc == */ + out.a = 0xffff; + out.r = 0x0; + out.g = 0xffff; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 182 */ + KUNIT_EXPECT_GT(test, out.r, 0xB500); + KUNIT_EXPECT_LT(test, out.r, 0xB780); /* laxed by half*/ + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x0100); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x0100); + + /* == blue 255 - bt709 enc == */ + out.a = 0xffff; + out.r = 0x0; + out.g = 0x0; + out.b = 0xffff; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 18 */ + KUNIT_EXPECT_GT(test, out.r, 0x1100); + KUNIT_EXPECT_LT(test, out.r, 0x1300); + + /* U 111 */ + KUNIT_EXPECT_GT(test, out.g, 0x6E00); + KUNIT_EXPECT_LT(test, out.g, 0x7000); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x0100); + + /* == red 140 - bt709 enc == */ + out.a = 0xffff; + out.r = 0x8c8c; + out.g = 0x0; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 30 */ + KUNIT_EXPECT_GT(test, out.r, 0x1D00); + KUNIT_EXPECT_LT(test, out.r, 0x1F00); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x100); + + /* V 87 */ + KUNIT_EXPECT_GT(test, out.b, 0x5600); + KUNIT_EXPECT_LT(test, out.b, 0x5800); + + /* == green 140 - bt709 enc == */ + out.a = 0xffff; + out.r = 0x0; + out.g = 0x8c8c; + out.b = 0x0; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 30 */ + KUNIT_EXPECT_GT(test, out.r, 0x6400); + KUNIT_EXPECT_LT(test, out.r, 0x6600); + + /* U 0 */ + KUNIT_EXPECT_LT(test, out.g, 0x100); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x100); + + /* == blue 140 - bt709 enc == */ + out.a = 0xffff; + out.r = 0x0; + out.g = 0x0; + out.b = 0x8c8c; + + apply_3x4_matrix(&out, &test_matrix_3x4_bt709_enc); + + /* Y 30 */ + KUNIT_EXPECT_GT(test, out.r, 0x900); + KUNIT_EXPECT_LT(test, out.r, 0xB00); + + /* U 61 */ + KUNIT_EXPECT_GT(test, out.g, 0x3C00); + KUNIT_EXPECT_LT(test, out.g, 0x3E00); + + /* V 0 */ + KUNIT_EXPECT_LT(test, out.b, 0x100); +} + +static struct kunit_case vkms_color_test_cases[] = { + KUNIT_CASE(vkms_color_test_get_lut_index), + KUNIT_CASE(vkms_color_test_lerp), + KUNIT_CASE(vkms_color_test_linear), + KUNIT_CASE(vkms_color_srgb_inv_srgb), + KUNIT_CASE(vkms_color_ctm_3x4_50_desat), + KUNIT_CASE(vkms_color_ctm_3x4_bt709), + {} +}; + +static struct kunit_suite vkms_color_test_suite = { + .name = "vkms-color", + .test_cases = vkms_color_test_cases, +}; + +kunit_test_suite(vkms_color_test_suite); + +MODULE_DESCRIPTION("Kunit test for VKMS LUT handling"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c new file mode 100644 index 000000000..1e4ea1863 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include "../vkms_config.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +static size_t vkms_config_get_num_planes(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + size_t count = 0; + + vkms_config_for_each_plane(config, plane_cfg) + count++; + + return count; +} + +static size_t vkms_config_get_num_encoders(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + size_t count = 0; + + vkms_config_for_each_encoder(config, encoder_cfg) + count++; + + return count; +} + +static size_t vkms_config_get_num_connectors(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + size_t count = 0; + + vkms_config_for_each_connector(config, connector_cfg) + count++; + + return count; +} + +static struct vkms_config_plane *get_first_plane(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + + vkms_config_for_each_plane(config, plane_cfg) + return plane_cfg; + + return NULL; +} + +static struct vkms_config_crtc *get_first_crtc(struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + vkms_config_for_each_crtc(config, crtc_cfg) + return crtc_cfg; + + return NULL; +} + +static struct vkms_config_encoder *get_first_encoder(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_encoder(config, encoder_cfg) + return encoder_cfg; + + return NULL; +} + +static struct vkms_config_connector *get_first_connector(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) + return connector_cfg; + + return NULL; +} + +struct default_config_case { + bool enable_cursor; + bool enable_writeback; + bool enable_overlay; + bool enable_plane_pipeline; +}; + +static void vkms_config_test_empty_config(struct kunit *test) +{ + struct vkms_config *config; + const char *dev_name = "test"; + + config = vkms_config_create(dev_name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* The dev_name string and the config have different lifetimes */ + dev_name = NULL; + KUNIT_EXPECT_STREQ(test, vkms_config_get_device_name(config), "test"); + + KUNIT_EXPECT_EQ(test, vkms_config_get_num_planes(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_crtcs(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_encoders(config), 0); + KUNIT_EXPECT_EQ(test, vkms_config_get_num_connectors(config), 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static struct default_config_case default_config_cases[] = { + { false, false, false, false }, + { true, false, false, false }, + { true, true, false, false }, + { true, false, true, false }, + { false, true, false, false }, + { false, true, true, false }, + { false, false, true, false }, + { true, true, true, false }, + { false, false, false, true }, + { true, false, false, true }, + { true, true, false, true }, + { true, false, true, true }, + { false, true, false, true }, + { false, true, true, true }, + { false, false, true, true }, + { true, true, true, true }, +}; + +KUNIT_ARRAY_PARAM(default_config, default_config_cases, NULL); + +static void vkms_config_test_default_config(struct kunit *test) +{ + const struct default_config_case *params = test->param_value; + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + int n_primaries = 0; + int n_cursors = 0; + int n_overlays = 0; + + config = vkms_config_default_create(params->enable_cursor, + params->enable_writeback, + params->enable_overlay, + params->enable_plane_pipeline); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Planes */ + vkms_config_for_each_plane(config, plane_cfg) { + KUNIT_EXPECT_EQ(test, + vkms_config_plane_get_default_pipeline(plane_cfg), + params->enable_plane_pipeline); + switch (vkms_config_plane_get_type(plane_cfg)) { + case DRM_PLANE_TYPE_PRIMARY: + n_primaries++; + break; + case DRM_PLANE_TYPE_CURSOR: + n_cursors++; + break; + case DRM_PLANE_TYPE_OVERLAY: + n_overlays++; + break; + default: + KUNIT_FAIL_AND_ABORT(test, "Unknown plane type"); + } + } + KUNIT_EXPECT_EQ(test, n_primaries, 1); + KUNIT_EXPECT_EQ(test, n_cursors, params->enable_cursor ? 1 : 0); + KUNIT_EXPECT_EQ(test, n_overlays, params->enable_overlay ? 8 : 0); + + /* CRTCs */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_crtcs(config), 1); + + crtc_cfg = get_first_crtc(config); + KUNIT_EXPECT_EQ(test, vkms_config_crtc_get_writeback(crtc_cfg), + params->enable_writeback); + + vkms_config_for_each_plane(config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + int n_possible_crtcs = 0; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + KUNIT_EXPECT_PTR_EQ(test, crtc_cfg, possible_crtc); + n_possible_crtcs++; + } + KUNIT_EXPECT_EQ(test, n_possible_crtcs, 1); + } + + /* Encoders */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_encoders(config), 1); + + /* Connectors */ + KUNIT_EXPECT_EQ(test, vkms_config_get_num_connectors(config), 1); + + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_planes(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + int n_planes = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_plane(config, plane_cfg) + n_planes++; + KUNIT_ASSERT_EQ(test, n_planes, 0); + + plane_cfg1 = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg1); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg1) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 1); + n_planes = 0; + + plane_cfg2 = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg2); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg1 && plane_cfg != plane_cfg2) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 2); + n_planes = 0; + + vkms_config_destroy_plane(plane_cfg1); + vkms_config_for_each_plane(config, plane_cfg) { + n_planes++; + if (plane_cfg != plane_cfg2) + KUNIT_FAIL(test, "Unexpected plane"); + } + KUNIT_ASSERT_EQ(test, n_planes, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 0); + vkms_config_for_each_crtc(config, crtc_cfg) + KUNIT_FAIL(test, "Unexpected CRTC"); + + crtc_cfg1 = vkms_config_create_crtc(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg1); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 1); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + crtc_cfg2 = vkms_config_create_crtc(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 2); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1 && crtc_cfg != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + vkms_config_destroy_crtc(config, crtc_cfg2); + KUNIT_ASSERT_EQ(test, vkms_config_get_num_crtcs(config), 1); + vkms_config_for_each_crtc(config, crtc_cfg) { + if (crtc_cfg != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected CRTC"); + } + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + int n_encoders = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_encoder(config, encoder_cfg) + n_encoders++; + KUNIT_ASSERT_EQ(test, n_encoders, 0); + + encoder_cfg1 = vkms_config_create_encoder(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg1); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + encoder_cfg2 = vkms_config_create_encoder(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg2); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1 && encoder_cfg != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 2); + n_encoders = 0; + + vkms_config_destroy_encoder(config, encoder_cfg2); + vkms_config_for_each_encoder(config, encoder_cfg) { + n_encoders++; + if (encoder_cfg != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + vkms_config_destroy(config); +} + +static void vkms_config_test_get_connectors(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + int n_connectors = 0; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + vkms_config_for_each_connector(config, connector_cfg) + n_connectors++; + KUNIT_ASSERT_EQ(test, n_connectors, 0); + + connector_cfg1 = vkms_config_create_connector(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 1); + n_connectors = 0; + + connector_cfg2 = vkms_config_create_connector(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1 && + connector_cfg != connector_cfg2) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 2); + n_connectors = 0; + + vkms_config_destroy_connector(connector_cfg2); + vkms_config_for_each_connector(config, connector_cfg) { + n_connectors++; + if (connector_cfg != connector_cfg1) + KUNIT_FAIL(test, "Unexpected connector"); + } + KUNIT_ASSERT_EQ(test, n_connectors, 1); + n_connectors = 0; + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_plane_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + int n; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No planes */ + plane_cfg = get_first_plane(config); + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many planes */ + for (n = 0; n <= 32; n++) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vkms_config_create_plane(config)); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_plane_type(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + int err; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg = get_first_plane(config); + vkms_config_destroy_plane(plane_cfg); + + crtc_cfg = get_first_crtc(config); + + /* Invalid: No primary plane */ + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_OVERLAY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Multiple primary planes */ + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: One primary plane */ + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Multiple cursor planes */ + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: One primary and one cursor plane */ + vkms_config_destroy_plane(plane_cfg); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Second CRTC without primary plane */ + crtc_cfg = vkms_config_create_crtc(config); + encoder_cfg = vkms_config_create_encoder(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg); + + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: Second CRTC with a primary plane */ + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_plane_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg = get_first_plane(config); + crtc_cfg = get_first_crtc(config); + + /* Invalid: Primary plane without a possible CRTC */ + vkms_config_plane_detach_crtc(plane_cfg, crtc_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_crtc_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_crtc *crtc_cfg; + int n; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No CRTCs */ + crtc_cfg = get_first_crtc(config); + vkms_config_destroy_crtc(config, crtc_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many CRTCs */ + for (n = 0; n <= 32; n++) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vkms_config_create_crtc(config)); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_encoder_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + int n; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No encoders */ + encoder_cfg = get_first_encoder(config); + vkms_config_destroy_encoder(config, encoder_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many encoders */ + for (n = 0; n <= 32; n++) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vkms_config_create_encoder(config)); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_encoder_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_encoder *encoder_cfg; + int err; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + crtc_cfg1 = get_first_crtc(config); + + /* Invalid: Encoder without a possible CRTC */ + encoder_cfg = vkms_config_create_encoder(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: Second CRTC with shared encoder */ + crtc_cfg2 = vkms_config_create_crtc(config); + plane_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg); + + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + /* Invalid: Second CRTC without encoders */ + vkms_config_encoder_detach_crtc(encoder_cfg, crtc_cfg2); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Valid: First CRTC with 2 possible encoder */ + vkms_config_destroy_plane(plane_cfg); + vkms_config_destroy_crtc(config, crtc_cfg2); + KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_invalid_connector_number(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg; + int n; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + /* Invalid: No connectors */ + connector_cfg = get_first_connector(config); + vkms_config_destroy_connector(connector_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + /* Invalid: Too many connectors */ + for (n = 0; n <= 32; n++) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vkms_config_create_connector(config)); + + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_valid_connector_possible_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + + config = vkms_config_default_create(false, false, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + encoder_cfg = get_first_encoder(config); + connector_cfg = get_first_connector(config); + + /* Invalid: Connector without a possible encoder */ + vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg); + KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_attach_different_configs(struct kunit *test) +{ + struct vkms_config *config1, *config2; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + int err; + + config1 = vkms_config_create("test1"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config1); + + config2 = vkms_config_create("test2"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config2); + + plane_cfg1 = vkms_config_create_plane(config1); + crtc_cfg1 = vkms_config_create_crtc(config1); + encoder_cfg1 = vkms_config_create_encoder(config1); + connector_cfg1 = vkms_config_create_connector(config1); + + plane_cfg2 = vkms_config_create_plane(config2); + crtc_cfg2 = vkms_config_create_crtc(config2); + encoder_cfg2 = vkms_config_create_encoder(config2); + connector_cfg2 = vkms_config_create_connector(config2); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2); + + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_plane_attach_crtc(plane_cfg2, crtc_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_encoder_attach_crtc(encoder_cfg2, crtc_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg2); + KUNIT_EXPECT_NE(test, err, 0); + err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg1); + KUNIT_EXPECT_NE(test, err, 0); + + vkms_config_destroy(config1); + vkms_config_destroy(config2); +} + +static void vkms_config_test_plane_attach_crtc(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *overlay_cfg; + struct vkms_config_plane *primary_cfg; + struct vkms_config_plane *cursor_cfg; + struct vkms_config_crtc *crtc_cfg; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + overlay_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, overlay_cfg); + vkms_config_plane_set_type(overlay_cfg, DRM_PLANE_TYPE_OVERLAY); + + primary_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary_cfg); + vkms_config_plane_set_type(primary_cfg, DRM_PLANE_TYPE_PRIMARY); + + cursor_cfg = vkms_config_create_plane(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cursor_cfg); + vkms_config_plane_set_type(cursor_cfg, DRM_PLANE_TYPE_CURSOR); + + crtc_cfg = vkms_config_create_crtc(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg); + + /* No primary or cursor planes */ + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Overlay plane, but no primary or cursor planes */ + err = vkms_config_plane_attach_crtc(overlay_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Primary plane, attaching it twice must fail */ + err = vkms_config_plane_attach_crtc(primary_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_plane_attach_crtc(primary_cfg, crtc_cfg); + KUNIT_EXPECT_NE(test, err, 0); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_primary_plane(config, crtc_cfg), + primary_cfg); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + /* Primary and cursor planes */ + err = vkms_config_plane_attach_crtc(cursor_cfg, crtc_cfg); + KUNIT_EXPECT_EQ(test, err, 0); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_primary_plane(config, crtc_cfg), + primary_cfg); + KUNIT_EXPECT_PTR_EQ(test, + vkms_config_crtc_cursor_plane(config, crtc_cfg), + cursor_cfg); + + /* Detach primary and destroy cursor plane */ + vkms_config_plane_detach_crtc(overlay_cfg, crtc_cfg); + vkms_config_plane_detach_crtc(primary_cfg, crtc_cfg); + vkms_config_destroy_plane(cursor_cfg); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_primary_plane(config, crtc_cfg)); + KUNIT_EXPECT_NULL(test, vkms_config_crtc_cursor_plane(config, crtc_cfg)); + + vkms_config_destroy(config); +} + +static void vkms_config_test_plane_get_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg1, *plane_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + int n_crtcs = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + plane_cfg1 = vkms_config_create_plane(config); + plane_cfg2 = vkms_config_create_plane(config); + crtc_cfg1 = vkms_config_create_crtc(config); + crtc_cfg2 = vkms_config_create_crtc(config); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + + /* No possible CRTCs */ + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Plane 1 attached to CRTC 1 and 2 */ + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_plane_attach_crtc(plane_cfg1, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1 && possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 2); + n_crtcs = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Plane 1 attached to CRTC 1 and plane 2 to CRTC 2 */ + vkms_config_plane_detach_crtc(plane_cfg1, crtc_cfg2); + vkms_config_plane_for_each_possible_crtc(plane_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + n_crtcs = 0; + + err = vkms_config_plane_attach_crtc(plane_cfg2, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_plane_for_each_possible_crtc(plane_cfg2, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_encoder_get_possible_crtcs(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_crtc *crtc_cfg1, *crtc_cfg2; + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + int n_crtcs = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + encoder_cfg1 = vkms_config_create_encoder(config); + encoder_cfg2 = vkms_config_create_encoder(config); + crtc_cfg1 = vkms_config_create_crtc(config); + crtc_cfg2 = vkms_config_create_crtc(config); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg2); + + /* No possible CRTCs */ + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Encoder 1 attached to CRTC 1 and 2 */ + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_encoder_attach_crtc(encoder_cfg1, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1 && possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 2); + n_crtcs = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + + /* Encoder 1 attached to CRTC 1 and encoder 2 to CRTC 2 */ + vkms_config_encoder_detach_crtc(encoder_cfg1, crtc_cfg2); + vkms_config_encoder_for_each_possible_crtc(encoder_cfg1, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg1) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + n_crtcs = 0; + + err = vkms_config_encoder_attach_crtc(encoder_cfg2, crtc_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_encoder_for_each_possible_crtc(encoder_cfg2, idx, possible_crtc) { + n_crtcs++; + if (possible_crtc != crtc_cfg2) + KUNIT_FAIL(test, "Unexpected possible CRTC"); + } + KUNIT_ASSERT_EQ(test, n_crtcs, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_connector_get_possible_encoders(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg1, *connector_cfg2; + struct vkms_config_encoder *encoder_cfg1, *encoder_cfg2; + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + int n_encoders = 0; + int err; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + connector_cfg1 = vkms_config_create_connector(config); + connector_cfg2 = vkms_config_create_connector(config); + encoder_cfg1 = vkms_config_create_encoder(config); + encoder_cfg2 = vkms_config_create_encoder(config); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg2); + + /* No possible encoders */ + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + /* Connector 1 attached to encoders 1 and 2 */ + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg1); + KUNIT_EXPECT_EQ(test, err, 0); + err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg1 && + possible_encoder != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 2); + n_encoders = 0; + + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) + KUNIT_FAIL(test, "Unexpected possible encoder"); + + /* Connector 1 attached to encoder 1 and connector 2 to encoder 2 */ + vkms_config_connector_detach_encoder(connector_cfg1, encoder_cfg2); + vkms_config_connector_for_each_possible_encoder(connector_cfg1, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg1) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + n_encoders = 0; + + err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg2); + KUNIT_EXPECT_EQ(test, err, 0); + vkms_config_connector_for_each_possible_encoder(connector_cfg2, idx, + possible_encoder) { + n_encoders++; + if (possible_encoder != encoder_cfg2) + KUNIT_FAIL(test, "Unexpected possible encoder"); + } + KUNIT_ASSERT_EQ(test, n_encoders, 1); + + vkms_config_destroy(config); +} + +static void vkms_config_test_connector_status(struct kunit *test) +{ + struct vkms_config *config; + struct vkms_config_connector *connector_cfg; + enum drm_connector_status status; + + config = vkms_config_create("test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + + connector_cfg = vkms_config_create_connector(config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg); + + status = vkms_config_connector_get_status(connector_cfg); + KUNIT_EXPECT_EQ(test, status, connector_status_connected); + + vkms_config_connector_set_status(connector_cfg, + connector_status_disconnected); + status = vkms_config_connector_get_status(connector_cfg); + KUNIT_EXPECT_EQ(test, status, connector_status_disconnected); + + vkms_config_destroy(config); +} + +static struct kunit_case vkms_config_test_cases[] = { + KUNIT_CASE(vkms_config_test_empty_config), + KUNIT_CASE_PARAM(vkms_config_test_default_config, + default_config_gen_params), + KUNIT_CASE(vkms_config_test_get_planes), + KUNIT_CASE(vkms_config_test_get_crtcs), + KUNIT_CASE(vkms_config_test_get_encoders), + KUNIT_CASE(vkms_config_test_get_connectors), + KUNIT_CASE(vkms_config_test_invalid_plane_number), + KUNIT_CASE(vkms_config_test_valid_plane_type), + KUNIT_CASE(vkms_config_test_valid_plane_possible_crtcs), + KUNIT_CASE(vkms_config_test_invalid_crtc_number), + KUNIT_CASE(vkms_config_test_invalid_encoder_number), + KUNIT_CASE(vkms_config_test_valid_encoder_possible_crtcs), + KUNIT_CASE(vkms_config_test_invalid_connector_number), + KUNIT_CASE(vkms_config_test_valid_connector_possible_encoders), + KUNIT_CASE(vkms_config_test_attach_different_configs), + KUNIT_CASE(vkms_config_test_plane_attach_crtc), + KUNIT_CASE(vkms_config_test_plane_get_possible_crtcs), + KUNIT_CASE(vkms_config_test_encoder_get_possible_crtcs), + KUNIT_CASE(vkms_config_test_connector_get_possible_encoders), + KUNIT_CASE(vkms_config_test_connector_status), + {} +}; + +static struct kunit_suite vkms_config_test_suite = { + .name = "vkms-config", + .test_cases = vkms_config_test_cases, +}; + +kunit_test_suite(vkms_config_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Kunit test for vkms config utility"); diff --git a/drivers/gpu/drm/vkms/tests/vkms_format_test.c b/drivers/gpu/drm/vkms/tests/vkms_format_test.c new file mode 100644 index 000000000..a7788fbc4 --- /dev/null +++ b/drivers/gpu/drm/vkms/tests/vkms_format_test.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include + +#include "../../drm_crtc_internal.h" + +#include "../vkms_formats.h" + +#define TEST_BUFF_SIZE 50 + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +/** + * struct pixel_yuv_u16 - Internal representation of a pixel color. + * @y: Luma value, stored in 16 bits, without padding, using + * machine endianness + * @u: Blue difference chroma value, stored in 16 bits, without padding, using + * machine endianness + * @v: Red difference chroma value, stored in 16 bits, without padding, using + * machine endianness + */ +struct pixel_yuv_u16 { + u16 y, u, v; +}; + +/* + * struct yuv_u16_to_argb_u16_case - Reference values to test the color + * conversions in VKMS between YUV to ARGB + * + * @encoding: Encoding used to convert RGB to YUV + * @range: Range used to convert RGB to YUV + * @n_colors: Count of test colors in this case + * @format_pair.name: Name used for this color conversion, used to + * clarify the test results + * @format_pair.rgb: RGB color tested + * @format_pair.yuv: Same color as @format_pair.rgb, but converted to + * YUV using @encoding and @range. + */ +struct yuv_u16_to_argb_u16_case { + enum drm_color_encoding encoding; + enum drm_color_range range; + size_t n_colors; + struct format_pair { + char *name; + struct pixel_yuv_u16 yuv; + struct pixel_argb_u16 argb; + } colors[TEST_BUFF_SIZE]; +}; + +/* + * The YUV color representation were acquired via the colour python framework. + * Below are the function calls used for generating each case. + * + * For more information got to the docs: + * https://colour.readthedocs.io/en/master/generated/colour.RGB_to_YCbCr.html + */ +static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = { + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.601"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = False, + * out_int = True) + * + * Tests cases for color conversion generated by converting RGB + * values to YUV BT601 full range using the ITU-R BT.601 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT601, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xffff, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x8080, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x0000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x4c8b, 0x54ce, 0xffff }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0x9645, 0x2b33, 0x14d1 }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x1d2f, 0xffff, 0x6b2f }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.601"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = True, + * out_int = True) + * Tests cases for color conversion generated by converting RGB + * values to YUV BT601 limited range using the ITU-R BT.601 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT601, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x517b, 0x5a34, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0x908e, 0x35cc, 0x2237 }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x28f7, 0xf000, 0x6dc9 }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.709"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = False, + * out_int = True) + * Tests cases for color conversion generated by converting RGB + * values to YUV BT709 full range using the ITU-R BT.709 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT709, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xffff, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x8080, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x0000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x366d, 0x62ac, 0xffff }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0xb717, 0x1d55, 0x0bbd }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x127c, 0xffff, 0x7443 }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.709"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = True, + * out_int = True) + * Tests cases for color conversion generated by converting RGB + * values to YUV BT709 limited range using the ITU-R BT.709 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT709, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x3e8f, 0x6656, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0xaca1, 0x29aa, 0x1a45 }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x1fd0, 0xf000, 0x75bb }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = False, + * out_int = True) + * Tests cases for color conversion generated by converting RGB + * values to YUV BT2020 full range using the ITU-R BT.2020 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT2020, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xffff, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x8080, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x0000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x4340, 0x5c41, 0xffff }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0xad91, 0x23bf, 0x0a4c }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x0f2e, 0xffff, 0x75b5 }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, + /* + * colour.RGB_to_YCbCr(, + * K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"], + * in_bits = 16, + * in_legal = False, + * in_int = True, + * out_bits = 16, + * out_legal = True, + * out_int = True) + * Tests cases for color conversion generated by converting RGB + * values to YUV BT2020 limited range using the ITU-R BT.2020 weights. + */ + { + .encoding = DRM_COLOR_YCBCR_BT2020, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .n_colors = 6, + .colors = { + { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }}, + { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }}, + { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }}, + { "red", { 0x4988, 0x60b9, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }}, + { "green", { 0xa47b, 0x2f47, 0x1902 }, { 0xffff, 0x0000, 0xffff, 0x0000 }}, + { "blue", { 0x1cfd, 0xf000, 0x76fe }, { 0xffff, 0x0000, 0x0000, 0xffff }}, + } + }, +}; + +/* + * vkms_format_test_yuv_u16_to_argb_u16 - Testing the conversion between YUV + * colors to ARGB colors in VKMS + * + * This test will use the functions get_conversion_matrix_to_argb_u16 and + * argb_u16_from_yuv161616 to convert YUV colors (stored in + * yuv_u16_to_argb_u16_cases) into ARGB colors. + * + * The conversion between YUV and RGB is not totally reversible, so there may be + * some difference between the expected value and the result. + */ +static void vkms_format_test_yuv_u16_to_argb_u16(struct kunit *test) +{ + const struct yuv_u16_to_argb_u16_case *param = test->param_value; + struct pixel_argb_u16 argb; + + for (size_t i = 0; i < param->n_colors; i++) { + const struct format_pair *color = ¶m->colors[i]; + struct conversion_matrix matrix; + + get_conversion_matrix_to_argb_u16 + (DRM_FORMAT_NV12, param->encoding, param->range, &matrix); + + argb = argb_u16_from_yuv161616(&matrix, color->yuv.y, color->yuv.u, + color->yuv.v); + + KUNIT_EXPECT_LE_MSG(test, abs_diff(argb.a, color->argb.a), 0x1ff, + "On the A channel of the color %s expected 0x%04x, got 0x%04x", + color->name, color->argb.a, argb.a); + KUNIT_EXPECT_LE_MSG(test, abs_diff(argb.r, color->argb.r), 0x1ff, + "On the R channel of the color %s expected 0x%04x, got 0x%04x", + color->name, color->argb.r, argb.r); + KUNIT_EXPECT_LE_MSG(test, abs_diff(argb.g, color->argb.g), 0x1ff, + "On the G channel of the color %s expected 0x%04x, got 0x%04x", + color->name, color->argb.g, argb.g); + KUNIT_EXPECT_LE_MSG(test, abs_diff(argb.b, color->argb.b), 0x1ff, + "On the B channel of the color %s expected 0x%04x, got 0x%04x", + color->name, color->argb.b, argb.b); + } +} + +static void vkms_format_test_yuv_u16_to_argb_u16_case_desc(struct yuv_u16_to_argb_u16_case *t, + char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s - %s", + drm_get_color_encoding_name(t->encoding), drm_get_color_range_name(t->range)); +} + +KUNIT_ARRAY_PARAM(yuv_u16_to_argb_u16, yuv_u16_to_argb_u16_cases, + vkms_format_test_yuv_u16_to_argb_u16_case_desc +); + +static struct kunit_case vkms_format_test_cases[] = { + KUNIT_CASE_PARAM(vkms_format_test_yuv_u16_to_argb_u16, yuv_u16_to_argb_u16_gen_params), + {} +}; + +static struct kunit_suite vkms_format_test_suite = { + .name = "vkms-format", + .test_cases = vkms_format_test_cases, +}; + +kunit_test_suite(vkms_format_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Kunit test for vkms format conversion"); diff --git a/drivers/gpu/drm/vkms/vkms_colorop.c b/drivers/gpu/drm/vkms/vkms_colorop.c new file mode 100644 index 000000000..071f3a8d2 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_colorop.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include + +#include "vkms_drv.h" + +static const u64 supported_tfs = + BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) | + BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF); + +static const struct drm_colorop_funcs vkms_colorop_funcs = { + .destroy = drm_colorop_destroy, +}; + +#define MAX_COLOR_PIPELINE_OPS 4 + +static int vkms_initialize_color_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list) +{ + struct drm_colorop *ops[MAX_COLOR_PIPELINE_OPS]; + struct drm_device *dev = plane->dev; + int ret; + int i = 0, j = 0; + + memset(ops, 0, sizeof(ops)); + + /* 1st op: 1d curve */ + ops[i] = kzalloc_obj(*ops[i]); + if (!ops[i]) { + drm_err(dev, "KMS: Failed to allocate colorop\n"); + ret = -ENOMEM; + goto cleanup; + } + + ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &vkms_colorop_funcs, + supported_tfs, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + if (ret) + goto cleanup; + + list->type = ops[i]->base.id; + + i++; + + /* 2nd op: 3x4 matrix */ + ops[i] = kzalloc_obj(*ops[i]); + if (!ops[i]) { + drm_err(dev, "KMS: Failed to allocate colorop\n"); + ret = -ENOMEM; + goto cleanup; + } + + ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane, &vkms_colorop_funcs, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + if (ret) + goto cleanup; + + drm_colorop_set_next_property(ops[i - 1], ops[i]); + + i++; + + /* 3rd op: 3x4 matrix */ + ops[i] = kzalloc_obj(*ops[i]); + if (!ops[i]) { + drm_err(dev, "KMS: Failed to allocate colorop\n"); + ret = -ENOMEM; + goto cleanup; + } + + ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane, &vkms_colorop_funcs, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + if (ret) + goto cleanup; + + drm_colorop_set_next_property(ops[i - 1], ops[i]); + + i++; + + /* 4th op: 1d curve */ + ops[i] = kzalloc_obj(*ops[i]); + if (!ops[i]) { + drm_err(dev, "KMS: Failed to allocate colorop\n"); + ret = -ENOMEM; + goto cleanup; + } + + ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &vkms_colorop_funcs, + supported_tfs, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + if (ret) + goto cleanup; + + drm_colorop_set_next_property(ops[i - 1], ops[i]); + + list->name = kasprintf(GFP_KERNEL, "Color Pipeline %d", ops[0]->base.id); + + return 0; + +cleanup: + for (j = 0; j < i; j++) { + if (ops[j]) { + drm_colorop_cleanup(ops[j]); + kfree(ops[j]); + } + } + + return ret; +} + +int vkms_initialize_colorops(struct drm_plane *plane) +{ + struct drm_prop_enum_list pipeline = {}; + int ret = 0; + + /* Add color pipeline */ + ret = vkms_initialize_color_pipeline(plane, &pipeline); + if (ret) + goto out; + + /* Create COLOR_PIPELINE property and attach */ + ret = drm_plane_create_color_pipeline_property(plane, &pipeline, 1); + + kfree(pipeline.name); +out: + return ret; +} diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c new file mode 100644 index 000000000..83d217085 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_composer.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vkms_composer.h" +#include "vkms_luts.h" + +static u16 pre_mul_blend_channel(u16 src, u16 dst, u16 alpha) +{ + u32 new_color; + + new_color = (src * 0xffff + dst * (0xffff - alpha)); + + return DIV_ROUND_CLOSEST(new_color, 0xffff); +} + +/** + * pre_mul_alpha_blend - alpha blending equation + * @stage_buffer: The line with the pixels from src_plane + * @output_buffer: A line buffer that receives all the blends output + * @x_start: The start offset + * @pixel_count: The number of pixels to blend + * + * The pixels [@x_start;@x_start+@pixel_count) in stage_buffer are blended at + * [@x_start;@x_start+@pixel_count) in output_buffer. + * + * The current DRM assumption is that pixel color values have been already + * pre-multiplied with the alpha channel values. See more + * drm_plane_create_blend_mode_property(). Also, this formula assumes a + * completely opaque background. + */ +static void pre_mul_alpha_blend(const struct line_buffer *stage_buffer, + struct line_buffer *output_buffer, int x_start, int pixel_count) +{ + struct pixel_argb_u16 *out = &output_buffer->pixels[x_start]; + const struct pixel_argb_u16 *in = &stage_buffer->pixels[x_start]; + + for (int i = 0; i < pixel_count; i++) { + out[i].a = (u16)0xffff; + out[i].r = pre_mul_blend_channel(in[i].r, out[i].r, in[i].a); + out[i].g = pre_mul_blend_channel(in[i].g, out[i].g, in[i].a); + out[i].b = pre_mul_blend_channel(in[i].b, out[i].b, in[i].a); + } +} + + +static void fill_background(const struct pixel_argb_u16 *background_color, + struct line_buffer *output_buffer) +{ + for (size_t i = 0; i < output_buffer->n_pixels; i++) + output_buffer->pixels[i] = *background_color; +} + +// lerp(a, b, t) = a + (b - a) * t +VISIBLE_IF_KUNIT u16 lerp_u16(u16 a, u16 b, s64 t) +{ + s64 a_fp = drm_int2fixp(a); + s64 b_fp = drm_int2fixp(b); + + s64 delta = drm_fixp_mul(b_fp - a_fp, t); + + return drm_fixp2int_round(a_fp + delta); +} +EXPORT_SYMBOL_IF_KUNIT(lerp_u16); + +VISIBLE_IF_KUNIT s64 get_lut_index(const struct vkms_color_lut *lut, u16 channel_value) +{ + s64 color_channel_fp = drm_int2fixp(channel_value); + + return drm_fixp_mul(color_channel_fp, lut->channel_value2index_ratio); +} +EXPORT_SYMBOL_IF_KUNIT(get_lut_index); + +VISIBLE_IF_KUNIT u16 apply_lut_to_channel_value(const struct vkms_color_lut *lut, u16 channel_value, + enum lut_channel channel) +{ + s64 lut_index = get_lut_index(lut, channel_value); + u16 *floor_lut_value, *ceil_lut_value; + u16 floor_channel_value, ceil_channel_value; + + /* + * This checks if `struct drm_color_lut` has any gap added by the compiler + * between the struct fields. + */ + static_assert(sizeof(struct drm_color_lut) == sizeof(__u16) * 4); + + floor_lut_value = (__u16 *)&lut->base[drm_fixp2int(lut_index)]; + if (drm_fixp2int(lut_index) == (lut->lut_length - 1)) + /* We're at the end of the LUT array, use same value for ceil and floor */ + ceil_lut_value = floor_lut_value; + else + ceil_lut_value = (__u16 *)&lut->base[drm_fixp2int_ceil(lut_index)]; + + floor_channel_value = floor_lut_value[channel]; + ceil_channel_value = ceil_lut_value[channel]; + + return lerp_u16(floor_channel_value, ceil_channel_value, + lut_index & DRM_FIXED_DECIMAL_MASK); +} +EXPORT_SYMBOL_IF_KUNIT(apply_lut_to_channel_value); + + +static void apply_lut(const struct vkms_crtc_state *crtc_state, struct line_buffer *output_buffer) +{ + if (!crtc_state->gamma_lut.base) + return; + + if (!crtc_state->gamma_lut.lut_length) + return; + + for (size_t x = 0; x < output_buffer->n_pixels; x++) { + struct pixel_argb_u16 *pixel = &output_buffer->pixels[x]; + + pixel->r = apply_lut_to_channel_value(&crtc_state->gamma_lut, pixel->r, LUT_RED); + pixel->g = apply_lut_to_channel_value(&crtc_state->gamma_lut, pixel->g, LUT_GREEN); + pixel->b = apply_lut_to_channel_value(&crtc_state->gamma_lut, pixel->b, LUT_BLUE); + } +} + +VISIBLE_IF_KUNIT void apply_3x4_matrix(struct pixel_argb_s32 *pixel, + const struct drm_color_ctm_3x4 *matrix) +{ + s64 rf, gf, bf; + s64 r, g, b; + + r = drm_int2fixp(pixel->r); + g = drm_int2fixp(pixel->g); + b = drm_int2fixp(pixel->b); + + rf = drm_fixp_mul(drm_sm2fixp(matrix->matrix[0]), r) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[1]), g) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[2]), b) + + drm_sm2fixp(matrix->matrix[3]); + + gf = drm_fixp_mul(drm_sm2fixp(matrix->matrix[4]), r) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[5]), g) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[6]), b) + + drm_sm2fixp(matrix->matrix[7]); + + bf = drm_fixp_mul(drm_sm2fixp(matrix->matrix[8]), r) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[9]), g) + + drm_fixp_mul(drm_sm2fixp(matrix->matrix[10]), b) + + drm_sm2fixp(matrix->matrix[11]); + + pixel->r = drm_fixp2int_round(rf); + pixel->g = drm_fixp2int_round(gf); + pixel->b = drm_fixp2int_round(bf); +} +EXPORT_SYMBOL_IF_KUNIT(apply_3x4_matrix); + +static void apply_colorop(struct pixel_argb_s32 *pixel, struct drm_colorop *colorop) +{ + struct drm_colorop_state *colorop_state = colorop->state; + struct drm_device *dev = colorop->dev; + + if (colorop->type == DRM_COLOROP_1D_CURVE) { + switch (colorop_state->curve_1d_type) { + case DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF: + pixel->r = apply_lut_to_channel_value(&srgb_inv_eotf, pixel->r, LUT_RED); + pixel->g = apply_lut_to_channel_value(&srgb_inv_eotf, pixel->g, LUT_GREEN); + pixel->b = apply_lut_to_channel_value(&srgb_inv_eotf, pixel->b, LUT_BLUE); + break; + case DRM_COLOROP_1D_CURVE_SRGB_EOTF: + pixel->r = apply_lut_to_channel_value(&srgb_eotf, pixel->r, LUT_RED); + pixel->g = apply_lut_to_channel_value(&srgb_eotf, pixel->g, LUT_GREEN); + pixel->b = apply_lut_to_channel_value(&srgb_eotf, pixel->b, LUT_BLUE); + break; + default: + drm_WARN_ONCE(dev, true, + "unknown colorop 1D curve type %d\n", + colorop_state->curve_1d_type); + break; + } + } else if (colorop->type == DRM_COLOROP_CTM_3X4) { + if (colorop_state->data) + apply_3x4_matrix(pixel, + (struct drm_color_ctm_3x4 *)colorop_state->data->data); + } +} + +static void pre_blend_color_transform(const struct vkms_plane_state *plane_state, + struct line_buffer *output_buffer) +{ + struct pixel_argb_s32 pixel; + + for (size_t x = 0; x < output_buffer->n_pixels; x++) { + struct drm_colorop *colorop = plane_state->base.base.color_pipeline; + + /* + * Some operations, such as applying a BT709 encoding matrix, + * followed by a decoding matrix, require that we preserve + * values above 1.0 and below 0.0 until the end of the pipeline. + * + * Pack the 16-bit UNORM values into s32 to give us head-room to + * avoid clipping until we're at the end of the pipeline. Clip + * intentionally at the end of the pipeline before packing + * UNORM values back into u16. + */ + pixel.a = output_buffer->pixels[x].a; + pixel.r = output_buffer->pixels[x].r; + pixel.g = output_buffer->pixels[x].g; + pixel.b = output_buffer->pixels[x].b; + + while (colorop) { + struct drm_colorop_state *colorop_state; + + colorop_state = colorop->state; + + if (!colorop_state) + return; + + if (!colorop_state->bypass) + apply_colorop(&pixel, colorop); + + colorop = colorop->next; + } + + /* clamp values */ + output_buffer->pixels[x].a = clamp_val(pixel.a, 0, 0xffff); + output_buffer->pixels[x].r = clamp_val(pixel.r, 0, 0xffff); + output_buffer->pixels[x].g = clamp_val(pixel.g, 0, 0xffff); + output_buffer->pixels[x].b = clamp_val(pixel.b, 0, 0xffff); + } +} + +/** + * direction_for_rotation() - Get the correct reading direction for a given rotation + * + * @rotation: Rotation to analyze. It correspond the field @frame_info.rotation. + * + * This function will use the @rotation setting of a source plane to compute the reading + * direction in this plane which correspond to a "left to right writing" in the CRTC. + * For example, if the buffer is reflected on X axis, the pixel must be read from right to left + * to be written from left to right on the CRTC. + */ +static enum pixel_read_direction direction_for_rotation(unsigned int rotation) +{ + struct drm_rect tmp_a, tmp_b; + int x, y; + + /* + * Points A and B are depicted as zero-size rectangles on the CRTC. + * The CRTC writing direction is from A to B. The plane reading direction + * is discovered by inverse-transforming A and B. + * The reading direction is computed by rotating the vector AB (top-left to top-right) in a + * 1x1 square. + */ + + tmp_a = DRM_RECT_INIT(0, 0, 0, 0); + tmp_b = DRM_RECT_INIT(1, 0, 0, 0); + drm_rect_rotate_inv(&tmp_a, 1, 1, rotation); + drm_rect_rotate_inv(&tmp_b, 1, 1, rotation); + + x = tmp_b.x1 - tmp_a.x1; + y = tmp_b.y1 - tmp_a.y1; + + if (x == 1 && y == 0) + return READ_LEFT_TO_RIGHT; + else if (x == -1 && y == 0) + return READ_RIGHT_TO_LEFT; + else if (y == 1 && x == 0) + return READ_TOP_TO_BOTTOM; + else if (y == -1 && x == 0) + return READ_BOTTOM_TO_TOP; + + WARN_ONCE(true, "The inverse of the rotation gives an incorrect direction."); + return READ_LEFT_TO_RIGHT; +} + +/** + * clamp_line_coordinates() - Compute and clamp the coordinate to read and write during the blend + * process. + * + * @direction: direction of the reading + * @current_plane: current plane blended + * @src_line: source line of the reading. Only the top-left coordinate is used. This rectangle + * must be rotated and have a shape of 1*pixel_count if @direction is vertical and a shape of + * pixel_count*1 if @direction is horizontal. + * @src_x_start: x start coordinate for the line reading + * @src_y_start: y start coordinate for the line reading + * @dst_x_start: x coordinate to blend the read line + * @pixel_count: number of pixels to blend + * + * This function is mainly a safety net to avoid reading outside the source buffer. As the + * userspace should never ask to read outside the source plane, all the cases covered here should + * be dead code. + */ +static void clamp_line_coordinates(enum pixel_read_direction direction, + const struct vkms_plane_state *current_plane, + const struct drm_rect *src_line, int *src_x_start, + int *src_y_start, int *dst_x_start, int *pixel_count) +{ + /* By default the start points are correct */ + *src_x_start = src_line->x1; + *src_y_start = src_line->y1; + *dst_x_start = current_plane->frame_info->dst.x1; + + /* Get the correct number of pixel to blend, it depends of the direction */ + switch (direction) { + case READ_LEFT_TO_RIGHT: + case READ_RIGHT_TO_LEFT: + *pixel_count = drm_rect_width(src_line); + break; + case READ_BOTTOM_TO_TOP: + case READ_TOP_TO_BOTTOM: + *pixel_count = drm_rect_height(src_line); + break; + } + + /* + * Clamp the coordinates to avoid reading outside the buffer + * + * This is mainly a security check to avoid reading outside the buffer, the userspace + * should never request to read outside the source buffer. + */ + switch (direction) { + case READ_LEFT_TO_RIGHT: + case READ_RIGHT_TO_LEFT: + if (*src_x_start < 0) { + *pixel_count += *src_x_start; + *dst_x_start -= *src_x_start; + *src_x_start = 0; + } + if (*src_x_start + *pixel_count > current_plane->frame_info->fb->width) + *pixel_count = max(0, (int)current_plane->frame_info->fb->width - + *src_x_start); + break; + case READ_BOTTOM_TO_TOP: + case READ_TOP_TO_BOTTOM: + if (*src_y_start < 0) { + *pixel_count += *src_y_start; + *dst_x_start -= *src_y_start; + *src_y_start = 0; + } + if (*src_y_start + *pixel_count > current_plane->frame_info->fb->height) + *pixel_count = max(0, (int)current_plane->frame_info->fb->height - + *src_y_start); + break; + } +} + +/** + * blend_line() - Blend a line from a plane to the output buffer + * + * @current_plane: current plane to work on + * @y: line to write in the output buffer + * @crtc_x_limit: width of the output buffer + * @stage_buffer: temporary buffer to convert the pixel line from the source buffer + * @output_buffer: buffer to blend the read line into. + */ +static void blend_line(struct vkms_plane_state *current_plane, int y, + int crtc_x_limit, struct line_buffer *stage_buffer, + struct line_buffer *output_buffer) +{ + int src_x_start, src_y_start, dst_x_start, pixel_count; + struct drm_rect dst_line, tmp_src, src_line; + + /* Avoid rendering useless lines */ + if (y < current_plane->frame_info->dst.y1 || + y >= current_plane->frame_info->dst.y2) + return; + + /* + * dst_line is the line to copy. The initial coordinates are inside the + * destination framebuffer, and then drm_rect_* helpers are used to + * compute the correct position into the source framebuffer. + */ + dst_line = DRM_RECT_INIT(current_plane->frame_info->dst.x1, y, + drm_rect_width(¤t_plane->frame_info->dst), + 1); + + drm_rect_fp_to_int(&tmp_src, ¤t_plane->frame_info->src); + + /* + * [1]: Clamping src_line to the crtc_x_limit to avoid writing outside of + * the destination buffer + */ + dst_line.x1 = max_t(int, dst_line.x1, 0); + dst_line.x2 = min_t(int, dst_line.x2, crtc_x_limit); + /* The destination is completely outside of the crtc. */ + if (dst_line.x2 <= dst_line.x1) + return; + + src_line = dst_line; + + /* + * Transform the coordinate x/y from the crtc to coordinates into + * coordinates for the src buffer. + * + * - Cancel the offset of the dst buffer. + * - Invert the rotation. This assumes that + * dst = drm_rect_rotate(src, rotation) (dst and src have the + * same size, but can be rotated). + * - Apply the offset of the source rectangle to the coordinate. + */ + drm_rect_translate(&src_line, -current_plane->frame_info->dst.x1, + -current_plane->frame_info->dst.y1); + drm_rect_rotate_inv(&src_line, drm_rect_width(&tmp_src), + drm_rect_height(&tmp_src), + current_plane->frame_info->rotation); + drm_rect_translate(&src_line, tmp_src.x1, tmp_src.y1); + + /* Get the correct reading direction in the source buffer. */ + + enum pixel_read_direction direction = + direction_for_rotation(current_plane->frame_info->rotation); + + /* [2]: Compute and clamp the number of pixel to read */ + clamp_line_coordinates(direction, current_plane, &src_line, &src_x_start, &src_y_start, + &dst_x_start, &pixel_count); + + if (pixel_count <= 0) { + /* Nothing to read, so avoid multiple function calls */ + return; + } + + /* + * Modify the starting point to take in account the rotation + * + * src_line is the top-left corner, so when reading READ_RIGHT_TO_LEFT or + * READ_BOTTOM_TO_TOP, it must be changed to the top-right/bottom-left + * corner. + */ + if (direction == READ_RIGHT_TO_LEFT) { + // src_x_start is now the right point + src_x_start += pixel_count - 1; + } else if (direction == READ_BOTTOM_TO_TOP) { + // src_y_start is now the bottom point + src_y_start += pixel_count - 1; + } + + /* + * Perform the conversion and the blending + * + * Here we know that the read line (x_start, y_start, pixel_count) is + * inside the source buffer [2] and we don't write outside the stage + * buffer [1]. + */ + current_plane->pixel_read_line(current_plane, src_x_start, src_y_start, direction, + pixel_count, &stage_buffer->pixels[dst_x_start]); + pre_blend_color_transform(current_plane, stage_buffer); + pre_mul_alpha_blend(stage_buffer, output_buffer, + dst_x_start, pixel_count); +} + +/** + * blend - blend the pixels from all planes and compute crc + * @wb: The writeback frame buffer metadata + * @crtc_state: The crtc state + * @crc32: The crc output of the final frame + * @output_buffer: A buffer of a row that will receive the result of the blend(s) + * @stage_buffer: The line with the pixels from plane being blend to the output + * @row_size: The size, in bytes, of a single row + * + * This function blends the pixels (Using the `pre_mul_alpha_blend`) + * from all planes, calculates the crc32 of the output from the former step, + * and, if necessary, convert and store the output to the writeback buffer. + */ +static void blend(struct vkms_writeback_job *wb, + struct vkms_crtc_state *crtc_state, + u32 *crc32, struct line_buffer *stage_buffer, + struct line_buffer *output_buffer, size_t row_size) +{ + struct vkms_plane_state **plane = crtc_state->active_planes; + u32 n_active_planes = crtc_state->num_active_planes; + u64 bgcolor = crtc_state->base.background_color; + + const struct pixel_argb_u16 background_color = { + .a = 0xffff, + .r = DRM_ARGB64_GETR(bgcolor), + .g = DRM_ARGB64_GETG(bgcolor), + .b = DRM_ARGB64_GETB(bgcolor), + }; + + int crtc_y_limit = crtc_state->base.mode.vdisplay; + int crtc_x_limit = crtc_state->base.mode.hdisplay; + + /* + * The planes are composed line-by-line to avoid heavy memory usage. It is a necessary + * complexity to avoid poor blending performance. + * + * The function pixel_read_line callback is used to read a line, using an efficient + * algorithm for a specific format, into the staging buffer. + */ + for (int y = 0; y < crtc_y_limit; y++) { + fill_background(&background_color, output_buffer); + + /* The active planes are composed associatively in z-order. */ + for (size_t i = 0; i < n_active_planes; i++) { + blend_line(plane[i], y, crtc_x_limit, stage_buffer, output_buffer); + } + + apply_lut(crtc_state, output_buffer); + + *crc32 = crc32_le(*crc32, (void *)output_buffer->pixels, row_size); + + if (wb) + vkms_writeback_row(wb, output_buffer, y); + } +} + +static int check_format_funcs(struct vkms_crtc_state *crtc_state, + struct vkms_writeback_job *active_wb) +{ + struct vkms_plane_state **planes = crtc_state->active_planes; + u32 n_active_planes = crtc_state->num_active_planes; + + for (size_t i = 0; i < n_active_planes; i++) + if (!planes[i]->pixel_read_line) + return -1; + + if (active_wb && !active_wb->pixel_write) + return -1; + + return 0; +} + +static int check_iosys_map(struct vkms_crtc_state *crtc_state) +{ + struct vkms_plane_state **plane_state = crtc_state->active_planes; + u32 n_active_planes = crtc_state->num_active_planes; + + for (size_t i = 0; i < n_active_planes; i++) + if (iosys_map_is_null(&plane_state[i]->frame_info->map[0])) + return -1; + + return 0; +} + +static int compose_active_planes(struct vkms_writeback_job *active_wb, + struct vkms_crtc_state *crtc_state, + u32 *crc32) +{ + size_t line_width, pixel_size = sizeof(struct pixel_argb_u16); + struct line_buffer output_buffer, stage_buffer; + int ret = 0; + + /* + * This check exists so we can call `crc32_le` for the entire line + * instead doing it for each channel of each pixel in case + * `struct `pixel_argb_u16` had any gap added by the compiler + * between the struct fields. + */ + static_assert(sizeof(struct pixel_argb_u16) == 8); + + if (WARN_ON(check_iosys_map(crtc_state))) + return -EINVAL; + + if (WARN_ON(check_format_funcs(crtc_state, active_wb))) + return -EINVAL; + + line_width = crtc_state->base.mode.hdisplay; + stage_buffer.n_pixels = line_width; + output_buffer.n_pixels = line_width; + + stage_buffer.pixels = kvmalloc(line_width * pixel_size, GFP_KERNEL); + if (!stage_buffer.pixels) { + DRM_ERROR("Cannot allocate memory for the output line buffer"); + return -ENOMEM; + } + + output_buffer.pixels = kvmalloc(line_width * pixel_size, GFP_KERNEL); + if (!output_buffer.pixels) { + DRM_ERROR("Cannot allocate memory for intermediate line buffer"); + ret = -ENOMEM; + goto free_stage_buffer; + } + + blend(active_wb, crtc_state, crc32, &stage_buffer, + &output_buffer, line_width * pixel_size); + + kvfree(output_buffer.pixels); +free_stage_buffer: + kvfree(stage_buffer.pixels); + + return ret; +} + +/** + * vkms_composer_worker - ordered work_struct to compute CRC + * + * @work: work_struct + * + * Work handler for composing and computing CRCs. work_struct scheduled in + * an ordered workqueue that's periodically scheduled to run by + * vkms_vblank_simulate() and flushed at vkms_atomic_commit_tail(). + */ +void vkms_composer_worker(struct work_struct *work) +{ + struct vkms_crtc_state *crtc_state = container_of(work, + struct vkms_crtc_state, + composer_work); + struct drm_crtc *crtc = crtc_state->base.crtc; + struct vkms_writeback_job *active_wb = crtc_state->active_writeback; + struct vkms_output *out = drm_crtc_to_vkms_output(crtc); + bool crc_pending, wb_pending; + u64 frame_start, frame_end; + u32 crc32 = 0; + int ret; + + spin_lock_irq(&out->composer_lock); + frame_start = crtc_state->frame_start; + frame_end = crtc_state->frame_end; + crc_pending = crtc_state->crc_pending; + wb_pending = crtc_state->wb_pending; + crtc_state->frame_start = 0; + crtc_state->frame_end = 0; + crtc_state->crc_pending = false; + + if (crtc->state->gamma_lut) { + s64 max_lut_index_fp; + s64 u16_max_fp = drm_int2fixp(0xffff); + + crtc_state->gamma_lut.base = (struct drm_color_lut *)crtc->state->gamma_lut->data; + crtc_state->gamma_lut.lut_length = + crtc->state->gamma_lut->length / sizeof(struct drm_color_lut); + max_lut_index_fp = drm_int2fixp(crtc_state->gamma_lut.lut_length - 1); + crtc_state->gamma_lut.channel_value2index_ratio = drm_fixp_div(max_lut_index_fp, + u16_max_fp); + + } else { + crtc_state->gamma_lut.base = NULL; + } + + spin_unlock_irq(&out->composer_lock); + + /* + * We raced with the vblank hrtimer and previous work already computed + * the crc, nothing to do. + */ + if (!crc_pending) + return; + + if (wb_pending) + ret = compose_active_planes(active_wb, crtc_state, &crc32); + else + ret = compose_active_planes(NULL, crtc_state, &crc32); + + if (ret) + return; + + if (wb_pending) { + drm_writeback_signal_completion(&out->wb_connector, 0); + spin_lock_irq(&out->composer_lock); + crtc_state->wb_pending = false; + spin_unlock_irq(&out->composer_lock); + } + + /* + * The worker can fall behind the vblank hrtimer, make sure we catch up. + */ + while (frame_start <= frame_end) + drm_crtc_add_crc_entry(crtc, true, frame_start++, &crc32); +} + +static const char *const pipe_crc_sources[] = { "auto" }; + +const char *const *vkms_get_crc_sources(struct drm_crtc *crtc, + size_t *count) +{ + *count = ARRAY_SIZE(pipe_crc_sources); + return pipe_crc_sources; +} + +static int vkms_crc_parse_source(const char *src_name, bool *enabled) +{ + int ret = 0; + + if (!src_name) { + *enabled = false; + } else if (strcmp(src_name, "auto") == 0) { + *enabled = true; + } else { + *enabled = false; + ret = -EINVAL; + } + + return ret; +} + +int vkms_verify_crc_source(struct drm_crtc *crtc, const char *src_name, + size_t *values_cnt) +{ + bool enabled; + + if (vkms_crc_parse_source(src_name, &enabled) < 0) { + DRM_DEBUG_DRIVER("unknown source %s\n", src_name); + return -EINVAL; + } + + *values_cnt = 1; + + return 0; +} + +void vkms_set_composer(struct vkms_output *out, bool enabled) +{ + bool old_enabled; + + if (enabled) + drm_crtc_vblank_get(&out->crtc); + + spin_lock_irq(&out->lock); + old_enabled = out->composer_enabled; + out->composer_enabled = enabled; + spin_unlock_irq(&out->lock); + + if (old_enabled) + drm_crtc_vblank_put(&out->crtc); +} + +int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name) +{ + struct vkms_output *out = drm_crtc_to_vkms_output(crtc); + bool enabled = false; + int ret = 0; + + ret = vkms_crc_parse_source(src_name, &enabled); + + vkms_set_composer(out, enabled); + + return ret; +} diff --git a/drivers/gpu/drm/vkms/vkms_composer.h b/drivers/gpu/drm/vkms/vkms_composer.h new file mode 100644 index 000000000..04dd5646f --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_composer.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_COMPOSER_H_ +#define _VKMS_COMPOSER_H_ + +#include +#include "vkms_drv.h" + +/* + * This enum is related to the positions of the variables inside + * `struct drm_color_lut`, so the order of both needs to be the same. + */ +enum lut_channel { + LUT_RED = 0, + LUT_GREEN, + LUT_BLUE, + LUT_RESERVED +}; + +#if IS_ENABLED(CONFIG_KUNIT) +u16 lerp_u16(u16 a, u16 b, s64 t); +s64 get_lut_index(const struct vkms_color_lut *lut, u16 channel_value); +u16 apply_lut_to_channel_value(const struct vkms_color_lut *lut, u16 channel_value, + enum lut_channel channel); +void apply_3x4_matrix(struct pixel_argb_s32 *pixel, const struct drm_color_ctm_3x4 *matrix); +#endif + +#endif /* _VKMS_COMPOSER_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c new file mode 100644 index 000000000..5a654d6de --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_config.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include +#include + +#include "vkms_config.h" + +struct vkms_config *vkms_config_create(const char *dev_name) +{ + struct vkms_config *config; + + config = kzalloc_obj(*config); + if (!config) + return ERR_PTR(-ENOMEM); + + config->dev_name = kstrdup_const(dev_name, GFP_KERNEL); + if (!config->dev_name) { + kfree(config); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&config->planes); + INIT_LIST_HEAD(&config->crtcs); + INIT_LIST_HEAD(&config->encoders); + INIT_LIST_HEAD(&config->connectors); + + return config; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create); + +struct vkms_config *vkms_config_default_create(bool enable_cursor, + bool enable_writeback, + bool enable_overlay, + bool enable_plane_pipeline) +{ + struct vkms_config *config; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + int n; + + config = vkms_config_create(DEFAULT_DEVICE_NAME); + if (IS_ERR(config)) + return config; + + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY); + + crtc_cfg = vkms_config_create_crtc(config); + if (IS_ERR(crtc_cfg)) + goto err_alloc; + vkms_config_crtc_set_writeback(crtc_cfg, enable_writeback); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline); + + if (enable_overlay) { + for (n = 0; n < NUM_OVERLAY_PLANES; n++) { + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + + vkms_config_plane_set_type(plane_cfg, + DRM_PLANE_TYPE_OVERLAY); + vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + } + } + + if (enable_cursor) { + plane_cfg = vkms_config_create_plane(config); + if (IS_ERR(plane_cfg)) + goto err_alloc; + + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR); + vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline); + + if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg)) + goto err_alloc; + } + + encoder_cfg = vkms_config_create_encoder(config); + if (IS_ERR(encoder_cfg)) + goto err_alloc; + + if (vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg)) + goto err_alloc; + + connector_cfg = vkms_config_create_connector(config); + if (IS_ERR(connector_cfg)) + goto err_alloc; + + if (vkms_config_connector_attach_encoder(connector_cfg, encoder_cfg)) + goto err_alloc; + + return config; + +err_alloc: + vkms_config_destroy(config); + return ERR_PTR(-ENOMEM); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_default_create); + +void vkms_config_destroy(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg, *plane_tmp; + struct vkms_config_crtc *crtc_cfg, *crtc_tmp; + struct vkms_config_encoder *encoder_cfg, *encoder_tmp; + struct vkms_config_connector *connector_cfg, *connector_tmp; + + list_for_each_entry_safe(plane_cfg, plane_tmp, &config->planes, link) + vkms_config_destroy_plane(plane_cfg); + + list_for_each_entry_safe(crtc_cfg, crtc_tmp, &config->crtcs, link) + vkms_config_destroy_crtc(config, crtc_cfg); + + list_for_each_entry_safe(encoder_cfg, encoder_tmp, &config->encoders, link) + vkms_config_destroy_encoder(config, encoder_cfg); + + list_for_each_entry_safe(connector_cfg, connector_tmp, &config->connectors, link) + vkms_config_destroy_connector(connector_cfg); + + kfree_const(config->dev_name); + kfree(config); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy); + +static bool valid_plane_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_planes; + + n_planes = list_count_nodes((struct list_head *)&config->planes); + if (n_planes <= 0 || n_planes >= 32) { + drm_info(dev, "The number of planes must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_planes_for_crtc(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_plane *plane_cfg; + bool has_primary_plane = false; + bool has_cursor_plane = false; + + vkms_config_for_each_plane(config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + enum drm_plane_type type; + + type = vkms_config_plane_get_type(plane_cfg); + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc != crtc_cfg) + continue; + + if (type == DRM_PLANE_TYPE_PRIMARY) { + if (has_primary_plane) { + drm_info(dev, "Multiple primary planes\n"); + return false; + } + + has_primary_plane = true; + } else if (type == DRM_PLANE_TYPE_CURSOR) { + if (has_cursor_plane) { + drm_info(dev, "Multiple cursor planes\n"); + return false; + } + + has_cursor_plane = true; + } + } + } + + if (!has_primary_plane) { + drm_info(dev, "Primary plane not found\n"); + return false; + } + + return true; +} + +static bool valid_plane_possible_crtcs(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_plane *plane_cfg; + + vkms_config_for_each_plane(config, plane_cfg) { + if (xa_empty(&plane_cfg->possible_crtcs)) { + drm_info(dev, "All planes must have at least one possible CRTC\n"); + return false; + } + } + + return true; +} + +static bool valid_crtc_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_crtcs; + + n_crtcs = list_count_nodes((struct list_head *)&config->crtcs); + if (n_crtcs <= 0 || n_crtcs >= 32) { + drm_info(dev, "The number of CRTCs must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_encoder_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_encoders; + + n_encoders = list_count_nodes((struct list_head *)&config->encoders); + if (n_encoders <= 0 || n_encoders >= 32) { + drm_info(dev, "The number of encoders must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_encoder_possible_crtcs(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_encoder(config, encoder_cfg) { + if (xa_empty(&encoder_cfg->possible_crtcs)) { + drm_info(dev, "All encoders must have at least one possible CRTC\n"); + return false; + } + } + + vkms_config_for_each_crtc(config, crtc_cfg) { + bool crtc_has_encoder = false; + + vkms_config_for_each_encoder(config, encoder_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, + idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + crtc_has_encoder = true; + } + } + + if (!crtc_has_encoder) { + drm_info(dev, "All CRTCs must have at least one possible encoder\n"); + return false; + } + } + + return true; +} + +static bool valid_connector_number(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + size_t n_connectors; + + n_connectors = list_count_nodes((struct list_head *)&config->connectors); + if (n_connectors <= 0 || n_connectors >= 32) { + drm_info(dev, "The number of connectors must be between 1 and 31\n"); + return false; + } + + return true; +} + +static bool valid_connector_possible_encoders(const struct vkms_config *config) +{ + struct drm_device *dev = config->dev ? &config->dev->drm : NULL; + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) { + if (xa_empty(&connector_cfg->possible_encoders)) { + drm_info(dev, + "All connectors must have at least one possible encoder\n"); + return false; + } + } + + return true; +} + +bool vkms_config_is_valid(const struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + if (!valid_plane_number(config)) + return false; + + if (!valid_crtc_number(config)) + return false; + + if (!valid_encoder_number(config)) + return false; + + if (!valid_connector_number(config)) + return false; + + if (!valid_plane_possible_crtcs(config)) + return false; + + vkms_config_for_each_crtc(config, crtc_cfg) { + if (!valid_planes_for_crtc(config, crtc_cfg)) + return false; + } + + if (!valid_encoder_possible_crtcs(config)) + return false; + + if (!valid_connector_possible_encoders(config)) + return false; + + return true; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_is_valid); + +static int vkms_config_show(struct seq_file *m, void *data) +{ + struct drm_debugfs_entry *entry = m->private; + struct drm_device *dev = entry->dev; + struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); + const char *dev_name; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + + dev_name = vkms_config_get_device_name((struct vkms_config *)vkmsdev->config); + seq_printf(m, "dev_name=%s\n", dev_name); + + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + seq_puts(m, "plane:\n"); + seq_printf(m, "\ttype=%d\n", + vkms_config_plane_get_type(plane_cfg)); + } + + vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) { + seq_puts(m, "crtc:\n"); + seq_printf(m, "\twriteback=%d\n", + vkms_config_crtc_get_writeback(crtc_cfg)); + } + + vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg) + seq_puts(m, "encoder\n"); + + vkms_config_for_each_connector(vkmsdev->config, connector_cfg) { + seq_puts(m, "connector:\n"); + seq_printf(m, "\tstatus=%d\n", + vkms_config_connector_get_status(connector_cfg)); + } + + return 0; +} + +static const struct drm_debugfs_info vkms_config_debugfs_list[] = { + { "vkms_config", vkms_config_show, 0 }, +}; + +void vkms_config_register_debugfs(struct vkms_device *vkms_device) +{ + drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list, + ARRAY_SIZE(vkms_config_debugfs_list)); +} + +struct vkms_config_plane *vkms_config_create_plane(struct vkms_config *config) +{ + struct vkms_config_plane *plane_cfg; + + plane_cfg = kzalloc_obj(*plane_cfg); + if (!plane_cfg) + return ERR_PTR(-ENOMEM); + + plane_cfg->config = config; + plane_cfg->default_pipeline = false; + vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_OVERLAY); + xa_init_flags(&plane_cfg->possible_crtcs, XA_FLAGS_ALLOC); + + list_add_tail(&plane_cfg->link, &config->planes); + + return plane_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_plane); + +void vkms_config_destroy_plane(struct vkms_config_plane *plane_cfg) +{ + xa_destroy(&plane_cfg->possible_crtcs); + list_del(&plane_cfg->link); + kfree(plane_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_plane); + +int __must_check vkms_config_plane_attach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + u32 crtc_idx = 0; + + if (plane_cfg->config != crtc_cfg->config) + return -EINVAL; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + return -EEXIST; + } + + return xa_alloc(&plane_cfg->possible_crtcs, &crtc_idx, crtc_cfg, + xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_attach_crtc); + +void vkms_config_plane_detach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + xa_erase(&plane_cfg->possible_crtcs, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_detach_crtc); + +struct vkms_config_crtc *vkms_config_create_crtc(struct vkms_config *config) +{ + struct vkms_config_crtc *crtc_cfg; + + crtc_cfg = kzalloc_obj(*crtc_cfg); + if (!crtc_cfg) + return ERR_PTR(-ENOMEM); + + crtc_cfg->config = config; + vkms_config_crtc_set_writeback(crtc_cfg, false); + + list_add_tail(&crtc_cfg->link, &config->crtcs); + + return crtc_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_crtc); + +void vkms_config_destroy_crtc(struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_plane *plane_cfg; + struct vkms_config_encoder *encoder_cfg; + + vkms_config_for_each_plane(config, plane_cfg) + vkms_config_plane_detach_crtc(plane_cfg, crtc_cfg); + + vkms_config_for_each_encoder(config, encoder_cfg) + vkms_config_encoder_detach_crtc(encoder_cfg, crtc_cfg); + + list_del(&crtc_cfg->link); + kfree(crtc_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_crtc); + +/** + * vkms_config_crtc_get_plane() - Return the first attached plane to a CRTC with + * the specific type + * @config: Configuration containing the CRTC and the plane + * @crtc_cfg: Only find planes attached to this CRTC + * @type: Plane type to search + * + * Returns: + * The first plane found attached to @crtc_cfg with the type @type. + */ +static struct vkms_config_plane *vkms_config_crtc_get_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg, + enum drm_plane_type type) +{ + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *possible_crtc; + enum drm_plane_type current_type; + unsigned long idx = 0; + + vkms_config_for_each_plane(config, plane_cfg) { + current_type = vkms_config_plane_get_type(plane_cfg); + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg && current_type == type) + return plane_cfg; + } + } + + return NULL; +} + +struct vkms_config_plane *vkms_config_crtc_primary_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_PRIMARY); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_primary_plane); + +struct vkms_config_plane *vkms_config_crtc_cursor_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg) +{ + return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_CURSOR); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_cursor_plane); + +struct vkms_config_encoder *vkms_config_create_encoder(struct vkms_config *config) +{ + struct vkms_config_encoder *encoder_cfg; + + encoder_cfg = kzalloc_obj(*encoder_cfg); + if (!encoder_cfg) + return ERR_PTR(-ENOMEM); + + encoder_cfg->config = config; + xa_init_flags(&encoder_cfg->possible_crtcs, XA_FLAGS_ALLOC); + + list_add_tail(&encoder_cfg->link, &config->encoders); + + return encoder_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_encoder); + +void vkms_config_destroy_encoder(struct vkms_config *config, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_connector *connector_cfg; + + vkms_config_for_each_connector(config, connector_cfg) + vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg); + + xa_destroy(&encoder_cfg->possible_crtcs); + list_del(&encoder_cfg->link); + kfree(encoder_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_encoder); + +int __must_check vkms_config_encoder_attach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + u32 crtc_idx = 0; + + if (encoder_cfg->config != crtc_cfg->config) + return -EINVAL; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + return -EEXIST; + } + + return xa_alloc(&encoder_cfg->possible_crtcs, &crtc_idx, crtc_cfg, + xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_attach_crtc); + +void vkms_config_encoder_detach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg) +{ + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + if (possible_crtc == crtc_cfg) + xa_erase(&encoder_cfg->possible_crtcs, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_detach_crtc); + +struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *config) +{ + struct vkms_config_connector *connector_cfg; + + connector_cfg = kzalloc_obj(*connector_cfg); + if (!connector_cfg) + return ERR_PTR(-ENOMEM); + + connector_cfg->config = config; + connector_cfg->status = connector_status_connected; + xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC); + + list_add_tail(&connector_cfg->link, &config->connectors); + + return connector_cfg; +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_connector); + +void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg) +{ + xa_destroy(&connector_cfg->possible_encoders); + list_del(&connector_cfg->link); + kfree(connector_cfg); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_connector); + +int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + u32 encoder_idx = 0; + + if (connector_cfg->config != encoder_cfg->config) + return -EINVAL; + + vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, + possible_encoder) { + if (possible_encoder == encoder_cfg) + return -EEXIST; + } + + return xa_alloc(&connector_cfg->possible_encoders, &encoder_idx, + encoder_cfg, xa_limit_32b, GFP_KERNEL); +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_attach_encoder); + +void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg) +{ + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + + vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, + possible_encoder) { + if (possible_encoder == encoder_cfg) + xa_erase(&connector_cfg->possible_encoders, idx); + } +} +EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_detach_encoder); diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h new file mode 100644 index 000000000..8f7f286a4 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_config.h @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_CONFIG_H_ +#define _VKMS_CONFIG_H_ + +#include +#include +#include + +#include + +#include "vkms_drv.h" + +/** + * struct vkms_config - General configuration for VKMS driver + * + * @dev_name: Name of the device + * @planes: List of planes configured for the device + * @crtcs: List of CRTCs configured for the device + * @encoders: List of encoders configured for the device + * @connectors: List of connectors configured for the device + * @dev: Used to store the current VKMS device. Only set when the device is instantiated. + */ +struct vkms_config { + const char *dev_name; + struct list_head planes; + struct list_head crtcs; + struct list_head encoders; + struct list_head connectors; + struct vkms_device *dev; +}; + +/** + * struct vkms_config_plane + * + * @link: Link to the others planes in vkms_config + * @config: The vkms_config this plane belongs to + * @type: Type of the plane. The creator of configuration needs to ensures that + * at least one primary plane is present. + * @possible_crtcs: Array of CRTCs that can be used with this plane + * @plane: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS plane during + * device creation. This pointer is not managed by the configuration and + * must be managed by other means. + */ +struct vkms_config_plane { + struct list_head link; + struct vkms_config *config; + + enum drm_plane_type type; + struct xarray possible_crtcs; + bool default_pipeline; + + /* Internal usage */ + struct vkms_plane *plane; +}; + +/** + * struct vkms_config_crtc + * + * @link: Link to the others CRTCs in vkms_config + * @config: The vkms_config this CRTC belongs to + * @writeback: If true, a writeback buffer can be attached to the CRTC + * @crtc: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS CRTC during + * device creation. This pointer is not managed by the configuration and + * must be managed by other means. + */ +struct vkms_config_crtc { + struct list_head link; + struct vkms_config *config; + + bool writeback; + + /* Internal usage */ + struct vkms_output *crtc; +}; + +/** + * struct vkms_config_encoder + * + * @link: Link to the others encoders in vkms_config + * @config: The vkms_config this CRTC belongs to + * @possible_crtcs: Array of CRTCs that can be used with this encoder + * @encoder: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS encoder + * during device creation. This pointer is not managed by the + * configuration and must be managed by other means. + */ +struct vkms_config_encoder { + struct list_head link; + struct vkms_config *config; + + struct xarray possible_crtcs; + + /* Internal usage */ + struct drm_encoder *encoder; +}; + +/** + * struct vkms_config_connector + * + * @link: Link to the others connector in vkms_config + * @config: The vkms_config this connector belongs to + * @status: Status (connected, disconnected...) of the connector + * @possible_encoders: Array of encoders that can be used with this connector + * @connector: Internal usage. This pointer should never be considered as valid. + * It can be used to store a temporary reference to a VKMS connector + * during device creation. This pointer is not managed by the + * configuration and must be managed by other means. + */ +struct vkms_config_connector { + struct list_head link; + struct vkms_config *config; + + enum drm_connector_status status; + struct xarray possible_encoders; + + /* Internal usage */ + struct vkms_connector *connector; +}; + +/** + * vkms_config_for_each_plane - Iterate over the vkms_config planes + * @config: &struct vkms_config pointer + * @plane_cfg: &struct vkms_config_plane pointer used as cursor + */ +#define vkms_config_for_each_plane(config, plane_cfg) \ + list_for_each_entry((plane_cfg), &(config)->planes, link) + +/** + * vkms_config_for_each_crtc - Iterate over the vkms_config CRTCs + * @config: &struct vkms_config pointer + * @crtc_cfg: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_for_each_crtc(config, crtc_cfg) \ + list_for_each_entry((crtc_cfg), &(config)->crtcs, link) + +/** + * vkms_config_for_each_encoder - Iterate over the vkms_config encoders + * @config: &struct vkms_config pointer + * @encoder_cfg: &struct vkms_config_encoder pointer used as cursor + */ +#define vkms_config_for_each_encoder(config, encoder_cfg) \ + list_for_each_entry((encoder_cfg), &(config)->encoders, link) + +/** + * vkms_config_for_each_connector - Iterate over the vkms_config connectors + * @config: &struct vkms_config pointer + * @connector_cfg: &struct vkms_config_connector pointer used as cursor + */ +#define vkms_config_for_each_connector(config, connector_cfg) \ + list_for_each_entry((connector_cfg), &(config)->connectors, link) + +/** + * vkms_config_plane_for_each_possible_crtc - Iterate over the vkms_config_plane + * possible CRTCs + * @plane_cfg: &struct vkms_config_plane pointer + * @idx: Index of the cursor + * @possible_crtc: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) \ + xa_for_each(&(plane_cfg)->possible_crtcs, idx, (possible_crtc)) + +/** + * vkms_config_encoder_for_each_possible_crtc - Iterate over the + * vkms_config_encoder possible CRTCs + * @encoder_cfg: &struct vkms_config_encoder pointer + * @idx: Index of the cursor + * @possible_crtc: &struct vkms_config_crtc pointer used as cursor + */ +#define vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) \ + xa_for_each(&(encoder_cfg)->possible_crtcs, idx, (possible_crtc)) + +/** + * vkms_config_connector_for_each_possible_encoder - Iterate over the + * vkms_config_connector possible encoders + * @connector_cfg: &struct vkms_config_connector pointer + * @idx: Index of the cursor + * @possible_encoder: &struct vkms_config_encoder pointer used as cursor + */ +#define vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, possible_encoder) \ + xa_for_each(&(connector_cfg)->possible_encoders, idx, (possible_encoder)) + +/** + * vkms_config_create() - Create a new VKMS configuration + * @dev_name: Name of the device + * + * Returns: + * The new vkms_config or an error. Call vkms_config_destroy() to free the + * returned configuration. + */ +struct vkms_config *vkms_config_create(const char *dev_name); + +/** + * vkms_config_default_create() - Create the configuration for the default device + * @enable_cursor: Create or not a cursor plane + * @enable_writeback: Create or not a writeback connector + * @enable_overlay: Create or not overlay planes + * + * Returns: + * The default vkms_config or an error. Call vkms_config_destroy() to free the + * returned configuration. + */ +struct vkms_config *vkms_config_default_create(bool enable_cursor, + bool enable_writeback, + bool enable_overlay, + bool enable_plane_pipeline); + +/** + * vkms_config_destroy() - Free a VKMS configuration + * @config: vkms_config to free + */ +void vkms_config_destroy(struct vkms_config *config); + +/** + * vkms_config_get_device_name() - Return the name of the device + * @config: Configuration to get the device name from + * + * Returns: + * The device name. Only valid while @config is valid. + */ +static inline const char * +vkms_config_get_device_name(struct vkms_config *config) +{ + return config->dev_name; +} + +/** + * vkms_config_get_num_crtcs() - Return the number of CRTCs in the configuration + * @config: Configuration to get the number of CRTCs from + */ +static inline size_t vkms_config_get_num_crtcs(struct vkms_config *config) +{ + return list_count_nodes(&config->crtcs); +} + +/** + * vkms_config_is_valid() - Validate a configuration + * @config: Configuration to validate + * + * Returns: + * Whether the configuration is valid or not. + * For example, a configuration without primary planes is not valid. + */ +bool vkms_config_is_valid(const struct vkms_config *config); + +/** + * vkms_config_register_debugfs() - Register a debugfs file to show the device's + * configuration + * @vkms_device: Device to register + */ +void vkms_config_register_debugfs(struct vkms_device *vkms_device); + +/** + * vkms_config_create_plane() - Add a new plane configuration + * @config: Configuration to add the plane to + * + * Returns: + * The new plane configuration or an error. Call vkms_config_destroy_plane() to + * free the returned plane configuration. + */ +struct vkms_config_plane *vkms_config_create_plane(struct vkms_config *config); + +/** + * vkms_config_destroy_plane() - Remove and free a plane configuration + * @plane_cfg: Plane configuration to destroy + */ +void vkms_config_destroy_plane(struct vkms_config_plane *plane_cfg); + +/** + * vkms_config_plane_type() - Return the plane type + * @plane_cfg: Plane to get the type from + */ +static inline enum drm_plane_type +vkms_config_plane_get_type(struct vkms_config_plane *plane_cfg) +{ + return plane_cfg->type; +} + +/** + * vkms_config_plane_set_type() - Set the plane type + * @plane_cfg: Plane to set the type to + * @type: New plane type + */ +static inline void +vkms_config_plane_set_type(struct vkms_config_plane *plane_cfg, + enum drm_plane_type type) +{ + plane_cfg->type = type; +} + +/** + * vkms_config_plane_get_default_pipeline() - Return if the plane will + * be created with the default pipeline + * @plane_cfg: Plane to get the information from + */ +static inline bool +vkms_config_plane_get_default_pipeline(struct vkms_config_plane *plane_cfg) +{ + return plane_cfg->default_pipeline; +} + +/** + * vkms_config_plane_set_default_pipeline() - Set if the plane will + * be created with the default pipeline + * @plane_cfg: Plane to configure the pipeline + * @default_pipeline: New default pipeline value + */ +static inline void +vkms_config_plane_set_default_pipeline(struct vkms_config_plane *plane_cfg, + bool default_pipeline) +{ + plane_cfg->default_pipeline = default_pipeline; +} + +/** + * vkms_config_plane_attach_crtc - Attach a plane to a CRTC + * @plane_cfg: Plane to attach + * @crtc_cfg: CRTC to attach @plane_cfg to + */ +int __must_check vkms_config_plane_attach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_plane_detach_crtc - Detach a plane from a CRTC + * @plane_cfg: Plane to detach + * @crtc_cfg: CRTC to detach @plane_cfg from + */ +void vkms_config_plane_detach_crtc(struct vkms_config_plane *plane_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_crtc() - Add a new CRTC configuration + * @config: Configuration to add the CRTC to + * + * Returns: + * The new CRTC configuration or an error. Call vkms_config_destroy_crtc() to + * free the returned CRTC configuration. + */ +struct vkms_config_crtc *vkms_config_create_crtc(struct vkms_config *config); + +/** + * vkms_config_destroy_crtc() - Remove and free a CRTC configuration + * @config: Configuration to remove the CRTC from + * @crtc_cfg: CRTC configuration to destroy + */ +void vkms_config_destroy_crtc(struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_crtc_get_writeback() - If a writeback connector will be created + * @crtc_cfg: CRTC with or without a writeback connector + */ +static inline bool +vkms_config_crtc_get_writeback(struct vkms_config_crtc *crtc_cfg) +{ + return crtc_cfg->writeback; +} + +/** + * vkms_config_crtc_set_writeback() - If a writeback connector will be created + * @crtc_cfg: Target CRTC + * @writeback: Enable or disable the writeback connector + */ +static inline void +vkms_config_crtc_set_writeback(struct vkms_config_crtc *crtc_cfg, + bool writeback) +{ + crtc_cfg->writeback = writeback; +} + +/** + * vkms_config_crtc_primary_plane() - Return the primary plane for a CRTC + * @config: Configuration containing the CRTC + * @crtc_config: Target CRTC + * + * Note that, if multiple primary planes are found, the first one is returned. + * In this case, the configuration will be invalid. See vkms_config_is_valid(). + * + * Returns: + * The primary plane or NULL if none is assigned yet. + */ +struct vkms_config_plane *vkms_config_crtc_primary_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_crtc_cursor_plane() - Return the cursor plane for a CRTC + * @config: Configuration containing the CRTC + * @crtc_config: Target CRTC + * + * Note that, if multiple cursor planes are found, the first one is returned. + * In this case, the configuration will be invalid. See vkms_config_is_valid(). + * + * Returns: + * The cursor plane or NULL if none is assigned yet. + */ +struct vkms_config_plane *vkms_config_crtc_cursor_plane(const struct vkms_config *config, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_encoder() - Add a new encoder configuration + * @config: Configuration to add the encoder to + * + * Returns: + * The new encoder configuration or an error. Call vkms_config_destroy_encoder() + * to free the returned encoder configuration. + */ +struct vkms_config_encoder *vkms_config_create_encoder(struct vkms_config *config); + +/** + * vkms_config_destroy_encoder() - Remove and free a encoder configuration + * @config: Configuration to remove the encoder from + * @encoder_cfg: Encoder configuration to destroy + */ +void vkms_config_destroy_encoder(struct vkms_config *config, + struct vkms_config_encoder *encoder_cfg); + +/** + * vkms_config_encoder_attach_crtc - Attach a encoder to a CRTC + * @encoder_cfg: Encoder to attach + * @crtc_cfg: CRTC to attach @encoder_cfg to + */ +int __must_check vkms_config_encoder_attach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_encoder_detach_crtc - Detach a encoder from a CRTC + * @encoder_cfg: Encoder to detach + * @crtc_cfg: CRTC to detach @encoder_cfg from + */ +void vkms_config_encoder_detach_crtc(struct vkms_config_encoder *encoder_cfg, + struct vkms_config_crtc *crtc_cfg); + +/** + * vkms_config_create_connector() - Add a new connector configuration + * @config: Configuration to add the connector to + * + * Returns: + * The new connector configuration or an error. Call + * vkms_config_destroy_connector() to free the returned connector configuration. + */ +struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *config); + +/** + * vkms_config_destroy_connector() - Remove and free a connector configuration + * @connector_cfg: Connector configuration to destroy + */ +void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg); + +/** + * vkms_config_connector_attach_encoder - Attach a connector to an encoder + * @connector_cfg: Connector to attach + * @encoder_cfg: Encoder to attach @connector_cfg to + */ +int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg); + +/** + * vkms_config_connector_detach_encoder - Detach a connector from an encoder + * @connector_cfg: Connector to detach + * @encoder_cfg: Encoder to detach @connector_cfg from + */ +void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg, + struct vkms_config_encoder *encoder_cfg); + +/** + * vkms_config_connector_get_status() - Return the status of the connector + * @connector_cfg: Connector to get the status from + */ +static inline enum drm_connector_status +vkms_config_connector_get_status(struct vkms_config_connector *connector_cfg) +{ + return connector_cfg->status; +} + +/** + * vkms_config_connector_set_status() - Set the status of the connector + * @connector_cfg: Connector to set the status to + * @status: New connector status + */ +static inline void +vkms_config_connector_set_status(struct vkms_config_connector *connector_cfg, + enum drm_connector_status status) +{ + connector_cfg->status = status; +} + +#endif /* _VKMS_CONFIG_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c new file mode 100644 index 000000000..601e3f128 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_configfs.c @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include +#include + +#include "vkms_drv.h" +#include "vkms_config.h" +#include "vkms_configfs.h" +#include "vkms_connector.h" + +/* To avoid registering configfs more than once or unregistering on error */ +static bool is_configfs_registered; + +/** + * struct vkms_configfs_device - Configfs representation of a VKMS device + * + * @group: Top level configuration group that represents a VKMS device. + * Initialized when a new directory is created under "/config/vkms/" + * @planes_group: Default subgroup of @group at "/config/vkms/planes" + * @crtcs_group: Default subgroup of @group at "/config/vkms/crtcs" + * @encoders_group: Default subgroup of @group at "/config/vkms/encoders" + * @connectors_group: Default subgroup of @group at "/config/vkms/connectors" + * @lock: Lock used to project concurrent access to the configuration attributes + * @config: Protected by @lock. Configuration of the VKMS device + * @enabled: Protected by @lock. The device is created or destroyed when this + * option changes + */ +struct vkms_configfs_device { + struct config_group group; + struct config_group planes_group; + struct config_group crtcs_group; + struct config_group encoders_group; + struct config_group connectors_group; + + struct mutex lock; + struct vkms_config *config; + bool enabled; +}; + +/** + * struct vkms_configfs_plane - Configfs representation of a plane + * + * @group: Top level configuration group that represents a plane. + * Initialized when a new directory is created under "/config/vkms/planes" + * @possible_crtcs_group: Default subgroup of @group at "plane/possible_crtcs" + * @dev: The vkms_configfs_device this plane belongs to + * @config: Configuration of the VKMS plane + */ +struct vkms_configfs_plane { + struct config_group group; + struct config_group possible_crtcs_group; + struct vkms_configfs_device *dev; + struct vkms_config_plane *config; +}; + +/** + * struct vkms_configfs_crtc - Configfs representation of a CRTC + * + * @group: Top level configuration group that represents a CRTC. + * Initialized when a new directory is created under "/config/vkms/crtcs" + * @dev: The vkms_configfs_device this CRTC belongs to + * @config: Configuration of the VKMS CRTC + */ +struct vkms_configfs_crtc { + struct config_group group; + struct vkms_configfs_device *dev; + struct vkms_config_crtc *config; +}; + +/** + * struct vkms_configfs_encoder - Configfs representation of a encoder + * + * @group: Top level configuration group that represents a encoder. + * Initialized when a new directory is created under "/config/vkms/encoders" + * @possible_crtcs_group: Default subgroup of @group at "encoder/possible_crtcs" + * @dev: The vkms_configfs_device this encoder belongs to + * @config: Configuration of the VKMS encoder + */ +struct vkms_configfs_encoder { + struct config_group group; + struct config_group possible_crtcs_group; + struct vkms_configfs_device *dev; + struct vkms_config_encoder *config; +}; + +/** + * struct vkms_configfs_connector - Configfs representation of a connector + * + * @group: Top level configuration group that represents a connector. + * Initialized when a new directory is created under "/config/vkms/connectors" + * @possible_encoders_group: Default subgroup of @group at + * "connector/possible_encoders" + * @dev: The vkms_configfs_device this connector belongs to + * @config: Configuration of the VKMS connector + */ +struct vkms_configfs_connector { + struct config_group group; + struct config_group possible_encoders_group; + struct vkms_configfs_device *dev; + struct vkms_config_connector *config; +}; + +#define device_item_to_vkms_configfs_device(item) \ + container_of(to_config_group((item)), struct vkms_configfs_device, \ + group) + +#define child_group_to_vkms_configfs_device(group) \ + device_item_to_vkms_configfs_device((&(group)->cg_item)->ci_parent) + +#define plane_item_to_vkms_configfs_plane(item) \ + container_of(to_config_group((item)), struct vkms_configfs_plane, group) + +#define plane_possible_crtcs_item_to_vkms_configfs_plane(item) \ + container_of(to_config_group((item)), struct vkms_configfs_plane, \ + possible_crtcs_group) + +#define crtc_item_to_vkms_configfs_crtc(item) \ + container_of(to_config_group((item)), struct vkms_configfs_crtc, group) + +#define encoder_item_to_vkms_configfs_encoder(item) \ + container_of(to_config_group((item)), struct vkms_configfs_encoder, \ + group) + +#define encoder_possible_crtcs_item_to_vkms_configfs_encoder(item) \ + container_of(to_config_group((item)), struct vkms_configfs_encoder, \ + possible_crtcs_group) + +#define connector_item_to_vkms_configfs_connector(item) \ + container_of(to_config_group((item)), struct vkms_configfs_connector, \ + group) + +#define connector_possible_encoders_item_to_vkms_configfs_connector(item) \ + container_of(to_config_group((item)), struct vkms_configfs_connector, \ + possible_encoders_group) + +static ssize_t crtc_writeback_show(struct config_item *item, char *page) +{ + struct vkms_configfs_crtc *crtc; + bool writeback; + + crtc = crtc_item_to_vkms_configfs_crtc(item); + + scoped_guard(mutex, &crtc->dev->lock) + writeback = vkms_config_crtc_get_writeback(crtc->config); + + return sprintf(page, "%d\n", writeback); +} + +static ssize_t crtc_writeback_store(struct config_item *item, const char *page, + size_t count) +{ + struct vkms_configfs_crtc *crtc; + bool writeback; + + crtc = crtc_item_to_vkms_configfs_crtc(item); + + if (kstrtobool(page, &writeback)) + return -EINVAL; + + scoped_guard(mutex, &crtc->dev->lock) { + if (crtc->dev->enabled) + return -EBUSY; + + vkms_config_crtc_set_writeback(crtc->config, writeback); + } + + return (ssize_t)count; +} + +CONFIGFS_ATTR(crtc_, writeback); + +static struct configfs_attribute *crtc_item_attrs[] = { + &crtc_attr_writeback, + NULL, +}; + +static void crtc_release(struct config_item *item) +{ + struct vkms_configfs_crtc *crtc; + struct mutex *lock; + + crtc = crtc_item_to_vkms_configfs_crtc(item); + lock = &crtc->dev->lock; + + scoped_guard(mutex, lock) { + vkms_config_destroy_crtc(crtc->dev->config, crtc->config); + kfree(crtc); + } +} + +static struct configfs_item_operations crtc_item_operations = { + .release = &crtc_release, +}; + +static const struct config_item_type crtc_item_type = { + .ct_attrs = crtc_item_attrs, + .ct_item_ops = &crtc_item_operations, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_crtc_group(struct config_group *group, + const char *name) +{ + struct vkms_configfs_device *dev; + struct vkms_configfs_crtc *crtc; + int ret; + + dev = child_group_to_vkms_configfs_device(group); + + scoped_guard(mutex, &dev->lock) { + if (dev->enabled) + return ERR_PTR(-EBUSY); + + crtc = kzalloc_obj(*crtc); + if (!crtc) + return ERR_PTR(-ENOMEM); + + crtc->dev = dev; + + crtc->config = vkms_config_create_crtc(dev->config); + if (IS_ERR(crtc->config)) { + ret = PTR_ERR(crtc->config); + kfree(crtc); + return ERR_PTR(ret); + } + + config_group_init_type_name(&crtc->group, name, &crtc_item_type); + } + + return &crtc->group; +} + +static struct configfs_group_operations crtcs_group_operations = { + .make_group = &make_crtc_group, +}; + +static const struct config_item_type crtc_group_type = { + .ct_group_ops = &crtcs_group_operations, + .ct_owner = THIS_MODULE, +}; + +static int plane_possible_crtcs_allow_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_plane *plane; + struct vkms_configfs_crtc *crtc; + int ret; + + if (target->ci_type != &crtc_item_type) + return -EINVAL; + + plane = plane_possible_crtcs_item_to_vkms_configfs_plane(src); + crtc = crtc_item_to_vkms_configfs_crtc(target); + + scoped_guard(mutex, &plane->dev->lock) { + if (plane->dev->enabled) + return -EBUSY; + + ret = vkms_config_plane_attach_crtc(plane->config, crtc->config); + } + + return ret; +} + +static void plane_possible_crtcs_drop_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_plane *plane; + struct vkms_configfs_crtc *crtc; + + plane = plane_possible_crtcs_item_to_vkms_configfs_plane(src); + crtc = crtc_item_to_vkms_configfs_crtc(target); + + scoped_guard(mutex, &plane->dev->lock) + vkms_config_plane_detach_crtc(plane->config, crtc->config); +} + +static struct configfs_item_operations plane_possible_crtcs_item_operations = { + .allow_link = plane_possible_crtcs_allow_link, + .drop_link = plane_possible_crtcs_drop_link, +}; + +static const struct config_item_type plane_possible_crtcs_group_type = { + .ct_item_ops = &plane_possible_crtcs_item_operations, + .ct_owner = THIS_MODULE, +}; + +static ssize_t plane_type_show(struct config_item *item, char *page) +{ + struct vkms_configfs_plane *plane; + enum drm_plane_type type; + + plane = plane_item_to_vkms_configfs_plane(item); + + scoped_guard(mutex, &plane->dev->lock) + type = vkms_config_plane_get_type(plane->config); + + return sprintf(page, "%u", type); +} + +static ssize_t plane_type_store(struct config_item *item, const char *page, + size_t count) +{ + struct vkms_configfs_plane *plane; + enum drm_plane_type type; + + plane = plane_item_to_vkms_configfs_plane(item); + + if (kstrtouint(page, 10, &type)) + return -EINVAL; + + if (type != DRM_PLANE_TYPE_OVERLAY && type != DRM_PLANE_TYPE_PRIMARY && + type != DRM_PLANE_TYPE_CURSOR) + return -EINVAL; + + scoped_guard(mutex, &plane->dev->lock) { + if (plane->dev->enabled) + return -EBUSY; + + vkms_config_plane_set_type(plane->config, type); + } + + return (ssize_t)count; +} + +CONFIGFS_ATTR(plane_, type); + +static struct configfs_attribute *plane_item_attrs[] = { + &plane_attr_type, + NULL, +}; + +static void plane_release(struct config_item *item) +{ + struct vkms_configfs_plane *plane; + struct mutex *lock; + + plane = plane_item_to_vkms_configfs_plane(item); + lock = &plane->dev->lock; + + scoped_guard(mutex, lock) { + vkms_config_destroy_plane(plane->config); + kfree(plane); + } +} + +static struct configfs_item_operations plane_item_operations = { + .release = &plane_release, +}; + +static const struct config_item_type plane_item_type = { + .ct_attrs = plane_item_attrs, + .ct_item_ops = &plane_item_operations, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_plane_group(struct config_group *group, + const char *name) +{ + struct vkms_configfs_device *dev; + struct vkms_configfs_plane *plane; + int ret; + + dev = child_group_to_vkms_configfs_device(group); + + scoped_guard(mutex, &dev->lock) { + if (dev->enabled) + return ERR_PTR(-EBUSY); + + plane = kzalloc_obj(*plane); + if (!plane) + return ERR_PTR(-ENOMEM); + + plane->dev = dev; + + plane->config = vkms_config_create_plane(dev->config); + if (IS_ERR(plane->config)) { + ret = PTR_ERR(plane->config); + kfree(plane); + return ERR_PTR(ret); + } + + config_group_init_type_name(&plane->group, name, &plane_item_type); + + config_group_init_type_name(&plane->possible_crtcs_group, + "possible_crtcs", + &plane_possible_crtcs_group_type); + configfs_add_default_group(&plane->possible_crtcs_group, + &plane->group); + } + + return &plane->group; +} + +static struct configfs_group_operations planes_group_operations = { + .make_group = &make_plane_group, +}; + +static const struct config_item_type plane_group_type = { + .ct_group_ops = &planes_group_operations, + .ct_owner = THIS_MODULE, +}; + +static int encoder_possible_crtcs_allow_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_encoder *encoder; + struct vkms_configfs_crtc *crtc; + int ret; + + if (target->ci_type != &crtc_item_type) + return -EINVAL; + + encoder = encoder_possible_crtcs_item_to_vkms_configfs_encoder(src); + crtc = crtc_item_to_vkms_configfs_crtc(target); + + scoped_guard(mutex, &encoder->dev->lock) { + if (encoder->dev->enabled) + return -EBUSY; + + ret = vkms_config_encoder_attach_crtc(encoder->config, crtc->config); + } + + return ret; +} + +static void encoder_possible_crtcs_drop_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_encoder *encoder; + struct vkms_configfs_crtc *crtc; + + encoder = encoder_possible_crtcs_item_to_vkms_configfs_encoder(src); + crtc = crtc_item_to_vkms_configfs_crtc(target); + + scoped_guard(mutex, &encoder->dev->lock) + vkms_config_encoder_detach_crtc(encoder->config, crtc->config); +} + +static struct configfs_item_operations encoder_possible_crtcs_item_operations = { + .allow_link = encoder_possible_crtcs_allow_link, + .drop_link = encoder_possible_crtcs_drop_link, +}; + +static const struct config_item_type encoder_possible_crtcs_group_type = { + .ct_item_ops = &encoder_possible_crtcs_item_operations, + .ct_owner = THIS_MODULE, +}; + +static void encoder_release(struct config_item *item) +{ + struct vkms_configfs_encoder *encoder; + struct mutex *lock; + + encoder = encoder_item_to_vkms_configfs_encoder(item); + lock = &encoder->dev->lock; + + scoped_guard(mutex, lock) { + vkms_config_destroy_encoder(encoder->dev->config, encoder->config); + kfree(encoder); + } +} + +static struct configfs_item_operations encoder_item_operations = { + .release = &encoder_release, +}; + +static const struct config_item_type encoder_item_type = { + .ct_item_ops = &encoder_item_operations, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_encoder_group(struct config_group *group, + const char *name) +{ + struct vkms_configfs_device *dev; + struct vkms_configfs_encoder *encoder; + int ret; + + dev = child_group_to_vkms_configfs_device(group); + + scoped_guard(mutex, &dev->lock) { + if (dev->enabled) + return ERR_PTR(-EBUSY); + + encoder = kzalloc_obj(*encoder); + if (!encoder) + return ERR_PTR(-ENOMEM); + + encoder->dev = dev; + + encoder->config = vkms_config_create_encoder(dev->config); + if (IS_ERR(encoder->config)) { + ret = PTR_ERR(encoder->config); + kfree(encoder); + return ERR_PTR(ret); + } + + config_group_init_type_name(&encoder->group, name, + &encoder_item_type); + + config_group_init_type_name(&encoder->possible_crtcs_group, + "possible_crtcs", + &encoder_possible_crtcs_group_type); + configfs_add_default_group(&encoder->possible_crtcs_group, + &encoder->group); + } + + return &encoder->group; +} + +static struct configfs_group_operations encoders_group_operations = { + .make_group = &make_encoder_group, +}; + +static const struct config_item_type encoder_group_type = { + .ct_group_ops = &encoders_group_operations, + .ct_owner = THIS_MODULE, +}; + +static ssize_t connector_status_show(struct config_item *item, char *page) +{ + struct vkms_configfs_connector *connector; + enum drm_connector_status status; + + connector = connector_item_to_vkms_configfs_connector(item); + + scoped_guard(mutex, &connector->dev->lock) + status = vkms_config_connector_get_status(connector->config); + + return sprintf(page, "%u", status); +} + +static ssize_t connector_status_store(struct config_item *item, + const char *page, size_t count) +{ + struct vkms_configfs_connector *connector; + enum drm_connector_status status; + + connector = connector_item_to_vkms_configfs_connector(item); + + if (kstrtouint(page, 10, &status)) + return -EINVAL; + + if (status != connector_status_connected && + status != connector_status_disconnected && + status != connector_status_unknown) + return -EINVAL; + + scoped_guard(mutex, &connector->dev->lock) { + vkms_config_connector_set_status(connector->config, status); + + if (connector->dev->enabled) + vkms_trigger_connector_hotplug(connector->dev->config->dev); + } + + return (ssize_t)count; +} + +CONFIGFS_ATTR(connector_, status); + +static struct configfs_attribute *connector_item_attrs[] = { + &connector_attr_status, + NULL, +}; + +static void connector_release(struct config_item *item) +{ + struct vkms_configfs_connector *connector; + struct mutex *lock; + + connector = connector_item_to_vkms_configfs_connector(item); + lock = &connector->dev->lock; + + scoped_guard(mutex, lock) { + vkms_config_destroy_connector(connector->config); + kfree(connector); + } +} + +static struct configfs_item_operations connector_item_operations = { + .release = &connector_release, +}; + +static const struct config_item_type connector_item_type = { + .ct_attrs = connector_item_attrs, + .ct_item_ops = &connector_item_operations, + .ct_owner = THIS_MODULE, +}; + +static int connector_possible_encoders_allow_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_connector *connector; + struct vkms_configfs_encoder *encoder; + int ret; + + if (target->ci_type != &encoder_item_type) + return -EINVAL; + + connector = connector_possible_encoders_item_to_vkms_configfs_connector(src); + encoder = encoder_item_to_vkms_configfs_encoder(target); + + scoped_guard(mutex, &connector->dev->lock) { + if (connector->dev->enabled) + return -EBUSY; + + ret = vkms_config_connector_attach_encoder(connector->config, + encoder->config); + } + + return ret; +} + +static void connector_possible_encoders_drop_link(struct config_item *src, + struct config_item *target) +{ + struct vkms_configfs_connector *connector; + struct vkms_configfs_encoder *encoder; + + connector = connector_possible_encoders_item_to_vkms_configfs_connector(src); + encoder = encoder_item_to_vkms_configfs_encoder(target); + + scoped_guard(mutex, &connector->dev->lock) { + vkms_config_connector_detach_encoder(connector->config, + encoder->config); + } +} + +static struct configfs_item_operations connector_possible_encoders_item_operations = { + .allow_link = connector_possible_encoders_allow_link, + .drop_link = connector_possible_encoders_drop_link, +}; + +static const struct config_item_type connector_possible_encoders_group_type = { + .ct_item_ops = &connector_possible_encoders_item_operations, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_connector_group(struct config_group *group, + const char *name) +{ + struct vkms_configfs_device *dev; + struct vkms_configfs_connector *connector; + int ret; + + dev = child_group_to_vkms_configfs_device(group); + + scoped_guard(mutex, &dev->lock) { + if (dev->enabled) + return ERR_PTR(-EBUSY); + + connector = kzalloc_obj(*connector); + if (!connector) + return ERR_PTR(-ENOMEM); + + connector->dev = dev; + + connector->config = vkms_config_create_connector(dev->config); + if (IS_ERR(connector->config)) { + ret = PTR_ERR(connector->config); + kfree(connector); + return ERR_PTR(ret); + } + + config_group_init_type_name(&connector->group, name, + &connector_item_type); + + config_group_init_type_name(&connector->possible_encoders_group, + "possible_encoders", + &connector_possible_encoders_group_type); + configfs_add_default_group(&connector->possible_encoders_group, + &connector->group); + } + + return &connector->group; +} + +static struct configfs_group_operations connectors_group_operations = { + .make_group = &make_connector_group, +}; + +static const struct config_item_type connector_group_type = { + .ct_group_ops = &connectors_group_operations, + .ct_owner = THIS_MODULE, +}; + +static ssize_t device_enabled_show(struct config_item *item, char *page) +{ + struct vkms_configfs_device *dev; + bool enabled; + + dev = device_item_to_vkms_configfs_device(item); + + scoped_guard(mutex, &dev->lock) + enabled = dev->enabled; + + return sprintf(page, "%d\n", enabled); +} + +static ssize_t device_enabled_store(struct config_item *item, const char *page, + size_t count) +{ + struct vkms_configfs_device *dev; + bool enabled; + int ret = 0; + + dev = device_item_to_vkms_configfs_device(item); + + if (kstrtobool(page, &enabled)) + return -EINVAL; + + scoped_guard(mutex, &dev->lock) { + if (!dev->enabled && enabled) { + if (!vkms_config_is_valid(dev->config)) + return -EINVAL; + + ret = vkms_create(dev->config); + if (ret) + return ret; + } else if (dev->enabled && !enabled) { + vkms_destroy(dev->config); + } + + dev->enabled = enabled; + } + + return (ssize_t)count; +} + +CONFIGFS_ATTR(device_, enabled); + +static struct configfs_attribute *device_item_attrs[] = { + &device_attr_enabled, + NULL, +}; + +static void device_release(struct config_item *item) +{ + struct vkms_configfs_device *dev; + + dev = device_item_to_vkms_configfs_device(item); + + if (dev->enabled) + vkms_destroy(dev->config); + + mutex_destroy(&dev->lock); + vkms_config_destroy(dev->config); + kfree(dev); +} + +static struct configfs_item_operations device_item_operations = { + .release = &device_release, +}; + +static const struct config_item_type device_item_type = { + .ct_attrs = device_item_attrs, + .ct_item_ops = &device_item_operations, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_device_group(struct config_group *group, + const char *name) +{ + struct vkms_configfs_device *dev; + int ret; + + if (strcmp(name, DEFAULT_DEVICE_NAME) == 0) + return ERR_PTR(-EINVAL); + + dev = kzalloc_obj(*dev); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev->config = vkms_config_create(name); + if (IS_ERR(dev->config)) { + ret = PTR_ERR(dev->config); + kfree(dev); + return ERR_PTR(ret); + } + + config_group_init_type_name(&dev->group, name, &device_item_type); + mutex_init(&dev->lock); + + config_group_init_type_name(&dev->planes_group, "planes", + &plane_group_type); + configfs_add_default_group(&dev->planes_group, &dev->group); + + config_group_init_type_name(&dev->crtcs_group, "crtcs", + &crtc_group_type); + configfs_add_default_group(&dev->crtcs_group, &dev->group); + + config_group_init_type_name(&dev->encoders_group, "encoders", + &encoder_group_type); + configfs_add_default_group(&dev->encoders_group, &dev->group); + + config_group_init_type_name(&dev->connectors_group, "connectors", + &connector_group_type); + configfs_add_default_group(&dev->connectors_group, &dev->group); + + return &dev->group; +} + +static struct configfs_group_operations device_group_ops = { + .make_group = &make_device_group, +}; + +static const struct config_item_type device_group_type = { + .ct_group_ops = &device_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem vkms_subsys = { + .su_group = { + .cg_item = { + .ci_name = "vkms", + .ci_type = &device_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(vkms_subsys.su_mutex), +}; + +int vkms_configfs_register(void) +{ + int ret; + + if (is_configfs_registered) + return 0; + + config_group_init(&vkms_subsys.su_group); + ret = configfs_register_subsystem(&vkms_subsys); + + is_configfs_registered = ret == 0; + + return ret; +} + +void vkms_configfs_unregister(void) +{ + if (is_configfs_registered) + configfs_unregister_subsystem(&vkms_subsys); +} diff --git a/drivers/gpu/drm/vkms/vkms_configfs.h b/drivers/gpu/drm/vkms/vkms_configfs.h new file mode 100644 index 000000000..e9020b004 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_configfs.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef _VKMS_CONFIGFS_H_ +#define _VKMS_CONFIGFS_H_ + +int vkms_configfs_register(void); +void vkms_configfs_unregister(void); + +#endif /* _VKMS_CONFIGFS_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c new file mode 100644 index 000000000..b0a6b212d --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_connector.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include + +#include "vkms_config.h" +#include "vkms_connector.h" + +static enum drm_connector_status vkms_connector_detect(struct drm_connector *connector, + bool force) +{ + struct drm_device *dev = connector->dev; + struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); + struct vkms_connector *vkms_connector; + enum drm_connector_status status; + struct vkms_config_connector *connector_cfg; + + vkms_connector = drm_connector_to_vkms_connector(connector); + + /* + * The connector configuration might not exist if its configfs directory + * was deleted. Therefore, use the configuration if present or keep the + * current status if we can not access it anymore. + */ + status = connector->status; + + vkms_config_for_each_connector(vkmsdev->config, connector_cfg) { + if (connector_cfg->connector == vkms_connector) + status = vkms_config_connector_get_status(connector_cfg); + } + + return status; +} + +static const struct drm_connector_funcs vkms_connector_funcs = { + .detect = vkms_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int vkms_conn_get_modes(struct drm_connector *connector) +{ + int count; + + /* Use the default modes list from DRM */ + count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX); + drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF); + + return count; +} + +static struct drm_encoder *vkms_conn_best_encoder(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + + drm_connector_for_each_possible_encoder(connector, encoder) + return encoder; + + return NULL; +} + +static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = { + .get_modes = vkms_conn_get_modes, + .best_encoder = vkms_conn_best_encoder, +}; + +struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev) +{ + struct drm_device *dev = &vkmsdev->drm; + struct vkms_connector *connector; + int ret; + + connector = drmm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) + return ERR_PTR(-ENOMEM); + + ret = drmm_connector_init(dev, &connector->base, &vkms_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + if (ret) + return ERR_PTR(ret); + + drm_connector_helper_add(&connector->base, &vkms_conn_helper_funcs); + + return connector; +} + +void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev) +{ + struct drm_device *dev = &vkmsdev->drm; + + drm_kms_helper_hotplug_event(dev); +} diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h new file mode 100644 index 000000000..ed312f4ef --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_connector.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_CONNECTOR_H_ +#define _VKMS_CONNECTOR_H_ + +#include "vkms_drv.h" + +#define drm_connector_to_vkms_connector(target) \ + container_of(target, struct vkms_connector, base) + +/** + * struct vkms_connector - VKMS custom type wrapping around the DRM connector + * + * @drm: Base DRM connector + */ +struct vkms_connector { + struct drm_connector base; +}; + +/** + * vkms_connector_init() - Initialize a connector + * @vkmsdev: VKMS device containing the connector + * + * Returns: + * The connector or an error on failure. + */ +struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev); + +/** + * vkms_trigger_connector_hotplug() - Update the device's connectors status + * @vkmsdev: VKMS device to update + */ +void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev); + +#endif /* _VKMS_CONNECTOR_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c new file mode 100644 index 000000000..079abfba4 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_crtc.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vkms_drv.h" + +static bool vkms_crtc_handle_vblank_timeout(struct drm_crtc *crtc) +{ + struct vkms_output *output = drm_crtc_to_vkms_output(crtc); + struct vkms_crtc_state *state; + bool ret, fence_cookie; + + fence_cookie = dma_fence_begin_signalling(); + + spin_lock(&output->lock); + ret = drm_crtc_handle_vblank(crtc); + if (!ret) + DRM_ERROR("vkms failure on handling vblank"); + + state = output->composer_state; + spin_unlock(&output->lock); + + if (state && output->composer_enabled) { + u64 frame = drm_crtc_accurate_vblank_count(crtc); + + /* update frame_start only if a queued vkms_composer_worker() + * has read the data + */ + spin_lock(&output->composer_lock); + if (!state->crc_pending) + state->frame_start = frame; + else + DRM_DEBUG_DRIVER("crc worker falling behind, frame_start: %llu, frame_end: %llu\n", + state->frame_start, frame); + state->frame_end = frame; + state->crc_pending = true; + spin_unlock(&output->composer_lock); + + ret = queue_work(output->composer_workq, &state->composer_work); + if (!ret) + DRM_DEBUG_DRIVER("Composer worker already queued\n"); + } + + dma_fence_end_signalling(fence_cookie); + + return true; +} + +static struct drm_crtc_state * +vkms_atomic_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct vkms_crtc_state *vkms_state; + + if (WARN_ON(!crtc->state)) + return NULL; + + vkms_state = kzalloc_obj(*vkms_state); + if (!vkms_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &vkms_state->base); + + INIT_WORK(&vkms_state->composer_work, vkms_composer_worker); + + return &vkms_state->base; +} + +static void vkms_atomic_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(state); + + WARN_ON(work_pending(&vkms_state->composer_work)); + kfree(vkms_state->active_planes); + kfree(vkms_state); +} + +static void vkms_atomic_crtc_reset(struct drm_crtc *crtc) +{ + struct vkms_crtc_state *vkms_state = kzalloc_obj(*vkms_state); + + if (crtc->state) + vkms_atomic_crtc_destroy_state(crtc, crtc->state); + + __drm_atomic_helper_crtc_reset(crtc, &vkms_state->base); + if (vkms_state) + INIT_WORK(&vkms_state->composer_work, vkms_composer_worker); +} + +static const struct drm_crtc_funcs vkms_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = vkms_atomic_crtc_reset, + .atomic_duplicate_state = vkms_atomic_crtc_duplicate_state, + .atomic_destroy_state = vkms_atomic_crtc_destroy_state, + DRM_CRTC_VBLANK_TIMER_FUNCS, + .get_crc_sources = vkms_get_crc_sources, + .set_crc_source = vkms_set_crc_source, + .verify_crc_source = vkms_verify_crc_source, +}; + +static int vkms_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(crtc_state); + struct drm_plane *plane; + struct drm_plane_state *plane_state; + int i = 0, ret; + + if (vkms_state->active_planes) + return 0; + + ret = drm_atomic_add_affected_planes(crtc_state->state, crtc); + if (ret < 0) + return ret; + + drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) { + plane_state = drm_atomic_get_new_plane_state(crtc_state->state, plane); + WARN_ON(!plane_state); + + if (!plane_state->visible) + continue; + + i++; + } + + vkms_state->active_planes = kzalloc_objs(*vkms_state->active_planes, i); + if (!vkms_state->active_planes) + return -ENOMEM; + vkms_state->num_active_planes = i; + + i = 0; + drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) { + plane_state = drm_atomic_get_new_plane_state(crtc_state->state, plane); + + if (!plane_state->visible) + continue; + + vkms_state->active_planes[i++] = + to_vkms_plane_state(plane_state); + } + + return 0; +} + +static void vkms_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_commit *state) + __acquires(&vkms_output->lock) +{ + struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc); + + /* This lock is held across the atomic commit to block vblank timer + * from scheduling vkms_composer_worker until the composer is updated + */ + spin_lock_irq(&vkms_output->lock); +} + +static void vkms_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) + __releases(&vkms_output->lock) +{ + struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc); + + if (crtc->state->event) { + spin_lock(&crtc->dev->event_lock); + + if (drm_crtc_vblank_get(crtc) != 0) + drm_crtc_send_vblank_event(crtc, crtc->state->event); + else + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + + spin_unlock(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } + + vkms_output->composer_state = to_vkms_crtc_state(crtc->state); + + spin_unlock_irq(&vkms_output->lock); +} + +static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = { + .atomic_check = vkms_crtc_atomic_check, + .atomic_begin = vkms_crtc_atomic_begin, + .atomic_flush = vkms_crtc_atomic_flush, + .atomic_enable = drm_crtc_vblank_atomic_enable, + .atomic_disable = drm_crtc_vblank_atomic_disable, + .handle_vblank_timeout = vkms_crtc_handle_vblank_timeout, +}; + +struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *primary, + struct drm_plane *cursor) +{ + struct vkms_output *vkms_out; + struct drm_crtc *crtc; + int ret; + + vkms_out = drmm_crtc_alloc_with_planes(dev, struct vkms_output, crtc, + primary, cursor, + &vkms_crtc_funcs, NULL); + if (IS_ERR(vkms_out)) { + DRM_DEV_ERROR(dev->dev, "Failed to init CRTC\n"); + return vkms_out; + } + + crtc = &vkms_out->crtc; + + drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs); + + ret = drm_mode_crtc_set_gamma_size(crtc, VKMS_LUT_SIZE); + if (ret) { + DRM_ERROR("Failed to set gamma size\n"); + return ERR_PTR(ret); + } + + drm_crtc_enable_color_mgmt(crtc, 0, false, VKMS_LUT_SIZE); + + drm_crtc_attach_background_color_property(crtc); + + spin_lock_init(&vkms_out->lock); + spin_lock_init(&vkms_out->composer_lock); + + vkms_out->composer_workq = drmm_alloc_ordered_workqueue(dev, "vkms_composer", 0); + if (IS_ERR(vkms_out->composer_workq)) + return ERR_CAST(vkms_out->composer_workq); + + return vkms_out; +} diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c new file mode 100644 index 000000000..5a640b531 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_drv.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/** + * DOC: vkms (Virtual Kernel Modesetting) + * + * VKMS is a software-only model of a KMS driver that is useful for testing + * and for running X (or similar) on headless machines. VKMS aims to enable + * a virtual display with no need of a hardware display capability, releasing + * the GPU in DRM API tests. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vkms_config.h" +#include "vkms_configfs.h" +#include "vkms_drv.h" + +#define DRIVER_NAME "vkms" +#define DRIVER_DESC "Virtual Kernel Mode Setting" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static struct vkms_config *default_config; + +static bool enable_cursor = true; +module_param_named(enable_cursor, enable_cursor, bool, 0444); +MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support"); + +static bool enable_writeback = true; +module_param_named(enable_writeback, enable_writeback, bool, 0444); +MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support"); + +static bool enable_overlay; +module_param_named(enable_overlay, enable_overlay, bool, 0444); +MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support"); + +static bool enable_plane_pipeline; +module_param_named(enable_plane_pipeline, enable_plane_pipeline, bool, 0444); +MODULE_PARM_DESC(enable_plane_pipeline, "Enable/Disable plane pipeline support"); + +static bool create_default_dev = true; +module_param_named(create_default_dev, create_default_dev, bool, 0444); +MODULE_PARM_DESC(create_default_dev, "Create or not the default VKMS device"); + +DEFINE_DRM_GEM_FOPS(vkms_driver_fops); + +static void vkms_atomic_commit_tail(struct drm_atomic_commit *old_state) +{ + struct drm_device *dev = old_state->dev; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + int i; + + drm_atomic_helper_commit_modeset_disables(dev, old_state); + + drm_atomic_helper_commit_planes(dev, old_state, 0); + + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_fake_vblank(old_state); + + drm_atomic_helper_commit_hw_done(old_state); + + drm_atomic_helper_wait_for_flip_done(dev, old_state); + + for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { + struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(old_crtc_state); + + flush_work(&vkms_state->composer_work); + } + + drm_atomic_helper_cleanup_planes(dev, old_state); +} + +static const struct drm_driver vkms_driver = { + .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM, + .fops = &vkms_driver_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static int vkms_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + int i; + + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { + if (!new_crtc_state->gamma_lut || !new_crtc_state->color_mgmt_changed) + continue; + + if (new_crtc_state->gamma_lut->length / sizeof(struct drm_color_lut *) + > VKMS_LUT_SIZE) + return -EINVAL; + } + + return drm_atomic_helper_check(dev, state); +} + +static const struct drm_mode_config_funcs vkms_mode_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = vkms_atomic_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = { + .atomic_commit_tail = vkms_atomic_commit_tail, +}; + +static int vkms_modeset_init(struct vkms_device *vkmsdev) +{ + struct drm_device *dev = &vkmsdev->drm; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.funcs = &vkms_mode_funcs; + dev->mode_config.min_width = XRES_MIN; + dev->mode_config.min_height = YRES_MIN; + dev->mode_config.max_width = XRES_MAX; + dev->mode_config.max_height = YRES_MAX; + dev->mode_config.cursor_width = 512; + dev->mode_config.cursor_height = 512; + /* + * FIXME: There's a confusion between bpp and depth between this and + * fbdev helpers. We have to go with 0, meaning "pick the default", + * which is XRGB8888 in all cases. + */ + dev->mode_config.preferred_depth = 0; + dev->mode_config.helper_private = &vkms_mode_config_helpers; + + return vkms_output_init(vkmsdev); +} + +int vkms_create(struct vkms_config *config) +{ + int ret; + struct faux_device *fdev; + struct vkms_device *vkms_device; + const char *dev_name; + + dev_name = vkms_config_get_device_name(config); + fdev = faux_device_create(dev_name, NULL, NULL); + if (!fdev) + return -ENODEV; + + if (!devres_open_group(&fdev->dev, NULL, GFP_KERNEL)) { + ret = -ENOMEM; + goto out_unregister; + } + + vkms_device = devm_drm_dev_alloc(&fdev->dev, &vkms_driver, + struct vkms_device, drm); + if (IS_ERR(vkms_device)) { + ret = PTR_ERR(vkms_device); + goto out_devres; + } + vkms_device->faux_dev = fdev; + vkms_device->config = config; + config->dev = vkms_device; + + ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev, + DMA_BIT_MASK(64)); + + if (ret) { + DRM_ERROR("Could not initialize DMA support\n"); + goto out_devres; + } + + ret = drm_vblank_init(&vkms_device->drm, + vkms_config_get_num_crtcs(config)); + if (ret) { + DRM_ERROR("Failed to vblank\n"); + goto out_devres; + } + + ret = vkms_modeset_init(vkms_device); + if (ret) + goto out_devres; + + vkms_config_register_debugfs(vkms_device); + + ret = drm_dev_register(&vkms_device->drm, 0); + if (ret) + goto out_devres; + + drm_client_setup(&vkms_device->drm, NULL); + + return 0; + +out_devres: + devres_release_group(&fdev->dev, NULL); +out_unregister: + faux_device_destroy(fdev); + return ret; +} + +static int __init vkms_init(void) +{ + int ret; + struct vkms_config *config; + + ret = vkms_configfs_register(); + if (ret) + return ret; + + if (!create_default_dev) + return 0; + + config = vkms_config_default_create(enable_cursor, enable_writeback, + enable_overlay, enable_plane_pipeline); + if (IS_ERR(config)) + return PTR_ERR(config); + + ret = vkms_create(config); + if (ret) { + vkms_config_destroy(config); + return ret; + } + + default_config = config; + + return 0; +} + +void vkms_destroy(struct vkms_config *config) +{ + struct faux_device *fdev; + + if (!config->dev) { + DRM_INFO("vkms_device is NULL.\n"); + return; + } + + fdev = config->dev->faux_dev; + + drm_dev_unregister(&config->dev->drm); + drm_atomic_helper_shutdown(&config->dev->drm); + devres_release_group(&fdev->dev, NULL); + faux_device_destroy(fdev); + + config->dev = NULL; +} + +static void __exit vkms_exit(void) +{ + vkms_configfs_unregister(); + + if (!default_config) + return; + + vkms_destroy(default_config); + vkms_config_destroy(default_config); +} + +module_init(vkms_init); +module_exit(vkms_exit); + +MODULE_AUTHOR("Haneen Mohammed "); +MODULE_AUTHOR("Rodrigo Siqueira "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h new file mode 100644 index 000000000..0933e4ce0 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_drv.h @@ -0,0 +1,329 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_DRV_H_ +#define _VKMS_DRV_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#define DEFAULT_DEVICE_NAME "vkms" + +#define XRES_MIN 10 +#define YRES_MIN 10 + +#define XRES_DEF 1024 +#define YRES_DEF 768 + +#define XRES_MAX 8192 +#define YRES_MAX 8192 + +#define NUM_OVERLAY_PLANES 8 + +#define VKMS_LUT_SIZE 256 + +/** + * struct vkms_frame_info - Structure to store the state of a frame + * + * @fb: backing drm framebuffer + * @src: source rectangle of this frame in the source framebuffer, stored in 16.16 fixed-point form + * @dst: destination rectangle in the crtc buffer, stored in whole pixel units + * @map: see @drm_shadow_plane_state.data + * @rotation: rotation applied to the source. + * + * @src and @dst should have the same size modulo the rotation. + */ +struct vkms_frame_info { + struct drm_framebuffer *fb; + struct drm_rect src, dst; + struct iosys_map map[DRM_FORMAT_MAX_PLANES]; + unsigned int rotation; +}; + +struct pixel_argb_s32 { + s32 a, r, g, b; +}; + +/** + * struct pixel_argb_u16 - Internal representation of a pixel color. + * @a: Alpha component value, stored in 16 bits, without padding, using + * machine endianness + * @r: Red component value, stored in 16 bits, without padding, using + * machine endianness + * @g: Green component value, stored in 16 bits, without padding, using + * machine endianness + * @b: Blue component value, stored in 16 bits, without padding, using + * machine endianness + * + * The goal of this structure is to keep enough precision to ensure + * correct composition results in VKMS and simplifying color + * manipulation by splitting each component into its own field. + * Caution: the byte ordering of this structure is machine-dependent, + * you can't cast it directly to AR48 or xR48. + */ +struct pixel_argb_u16 { + u16 a, r, g, b; +}; + +struct line_buffer { + size_t n_pixels; + struct pixel_argb_u16 *pixels; +}; + +/** + * typedef pixel_write_t - These functions are used to read a pixel from a + * &struct pixel_argb_u16, convert it in a specific format and write it in the @out_pixel + * buffer. + * + * @out_pixel: destination address to write the pixel + * @in_pixel: pixel to write + */ +typedef void (*pixel_write_t)(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel); + +struct vkms_writeback_job { + struct iosys_map data[DRM_FORMAT_MAX_PLANES]; + struct vkms_frame_info wb_frame_info; + pixel_write_t pixel_write; +}; + +/** + * enum pixel_read_direction - Enum used internally by VKMS to represent a reading direction in a + * plane. + */ +enum pixel_read_direction { + READ_BOTTOM_TO_TOP, + READ_TOP_TO_BOTTOM, + READ_RIGHT_TO_LEFT, + READ_LEFT_TO_RIGHT +}; + +struct vkms_plane_state; + +/** + * typedef pixel_read_line_t - These functions are used to read a pixel line in the source frame, + * convert it to `struct pixel_argb_u16` and write it to @out_pixel. + * + * @plane: plane used as source for the pixel value + * @x_start: X (width) coordinate of the first pixel to copy. The caller must ensure that x_start + * is non-negative and smaller than @plane->frame_info->fb->width. + * @y_start: Y (height) coordinate of the first pixel to copy. The caller must ensure that y_start + * is non-negative and smaller than @plane->frame_info->fb->height. + * @direction: direction to use for the copy, starting at @x_start/@y_start + * @count: number of pixels to copy + * @out_pixel: pointer where to write the pixel values. They will be written from @out_pixel[0] + * (included) to @out_pixel[@count] (excluded). The caller must ensure that out_pixel have a + * length of at least @count. + */ +typedef void (*pixel_read_line_t)(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]); + +/** + * struct conversion_matrix - Matrix to use for a specific encoding and range + * + * @matrix: Conversion matrix from yuv to rgb. The matrix is stored in a row-major manner and is + * used to compute rgb values from yuv values: + * [[r],[g],[b]] = @matrix * [[y],[u],[v]] + * OR for yvu formats: + * [[r],[g],[b]] = @matrix * [[y],[v],[u]] + * The values of the matrix are signed fixed-point values with 32 bits fractional part. + * @y_offset: Offset to apply on the y value. + */ +struct conversion_matrix { + s64 matrix[3][3]; + int y_offset; +}; + +/** + * struct vkms_plane_state - Driver specific plane state + * @base: base plane state + * @frame_info: data required for composing computation + * @pixel_read_line: function to read a pixel line in this plane. The creator of a + * struct vkms_plane_state must ensure that this pointer is valid + * @conversion_matrix: matrix used for yuv formats to convert to rgb + */ +struct vkms_plane_state { + struct drm_shadow_plane_state base; + struct vkms_frame_info *frame_info; + pixel_read_line_t pixel_read_line; + struct conversion_matrix conversion_matrix; +}; + +struct vkms_plane { + struct drm_plane base; +}; + +struct vkms_color_lut { + struct drm_color_lut *base; + size_t lut_length; + s64 channel_value2index_ratio; +}; + +/** + * struct vkms_crtc_state - Driver specific CRTC state + * + * @base: base CRTC state + * @composer_work: work struct to compose and add CRC entries + * + * @num_active_planes: Number of active planes + * @active_planes: List containing all the active planes (counted by + * @num_active_planes). They should be stored in z-order. + * @active_writeback: Current active writeback job + * @gamma_lut: Look up table for gamma used in this CRTC + * @crc_pending: Protected by @vkms_output.composer_lock, true when the frame CRC is not computed + * yet. Used by vblank to detect if the composer is too slow. + * @wb_pending: Protected by @vkms_output.composer_lock, true when a writeback frame is requested. + * @frame_start: Protected by @vkms_output.composer_lock, saves the frame number before the start + * of the composition process. + * @frame_end: Protected by @vkms_output.composer_lock, saves the last requested frame number. + * This is used to generate enough CRC entries when the composition worker is too slow. + */ +struct vkms_crtc_state { + struct drm_crtc_state base; + struct work_struct composer_work; + + int num_active_planes; + struct vkms_plane_state **active_planes; + struct vkms_writeback_job *active_writeback; + struct vkms_color_lut gamma_lut; + + bool crc_pending; + bool wb_pending; + u64 frame_start; + u64 frame_end; +}; + +/** + * struct vkms_output - Internal representation of all output components in VKMS + * + * @crtc: Base CRTC in DRM + * @encoder: DRM encoder used for this output + * @connector: DRM connector used for this output + * @wb_connecter: DRM writeback connector used for this output + * @vblank_hrtimer: Timer used to trigger the vblank + * @period_ns: vblank period, in nanoseconds, used to configure @vblank_hrtimer and to compute + * vblank timestamps + * @composer_workq: Ordered workqueue for @composer_state.composer_work. + * @lock: Lock used to protect concurrent access to the composer + * @composer_enabled: Protected by @lock, true when the VKMS composer is active (crc needed or + * writeback) + * @composer_state: Protected by @lock, current state of this VKMS output + * @composer_lock: Lock used internally to protect @composer_state members + */ +struct vkms_output { + struct drm_crtc crtc; + struct drm_writeback_connector wb_connector; + struct drm_encoder wb_encoder; + struct workqueue_struct *composer_workq; + spinlock_t lock; + + bool composer_enabled; + struct vkms_crtc_state *composer_state; + + spinlock_t composer_lock; +}; + +struct vkms_config; +struct vkms_config_plane; + +/** + * struct vkms_device - Description of a VKMS device + * + * @drm - Base device in DRM + * @faux_dev - Associated faux device + * @output - Configuration and sub-components of the VKMS device + * @config: Configuration used in this VKMS device + */ +struct vkms_device { + struct drm_device drm; + struct faux_device *faux_dev; + const struct vkms_config *config; +}; + +/* + * The following helpers are used to convert a member of a struct into its parent. + */ + +#define drm_crtc_to_vkms_output(target) \ + container_of(target, struct vkms_output, crtc) + +#define drm_device_to_vkms_device(target) \ + container_of(target, struct vkms_device, drm) + +#define to_vkms_crtc_state(target)\ + container_of(target, struct vkms_crtc_state, base) + +#define to_vkms_plane_state(target)\ + container_of(target, struct vkms_plane_state, base.base) + +/** + * vkms_create() - Create a device from a configuration + * @config: Config used to configure the new device + * + * A pointer to the created vkms_device is stored in @config + * + * Returns: + * 0 on success or an error. + */ +int vkms_create(struct vkms_config *config); + +/** + * vkms_destroy() - Destroy a device + * @config: Config from which the device was created + * + * The device is completely removed, but the @config is not freed. It can be + * reused or destroyed with vkms_config_destroy(). + */ +void vkms_destroy(struct vkms_config *config); + +/** + * vkms_crtc_init() - Initialize a CRTC for VKMS + * @dev: DRM device associated with the VKMS buffer + * @crtc: uninitialized CRTC device + * @primary: primary plane to attach to the CRTC + * @cursor: plane to attach to the CRTC + */ +struct vkms_output *vkms_crtc_init(struct drm_device *dev, + struct drm_plane *primary, + struct drm_plane *cursor); + +/** + * vkms_output_init() - Initialize all sub-components needed for a VKMS device. + * + * @vkmsdev: VKMS device to initialize + */ +int vkms_output_init(struct vkms_device *vkmsdev); + +/** + * vkms_plane_init() - Initialize a plane + * + * @vkmsdev: VKMS device containing the plane + * @plane_cfg: plane configuration + */ +struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev, + struct vkms_config_plane *plane_cfg); + +/* CRC Support */ +const char *const *vkms_get_crc_sources(struct drm_crtc *crtc, + size_t *count); +int vkms_set_crc_source(struct drm_crtc *crtc, const char *src_name); +int vkms_verify_crc_source(struct drm_crtc *crtc, const char *source_name, + size_t *values_cnt); + +/* Composer Support */ +void vkms_composer_worker(struct work_struct *work); +void vkms_set_composer(struct vkms_output *out, bool enabled); +void vkms_writeback_row(struct vkms_writeback_job *wb, const struct line_buffer *src_buffer, int y); + +/* Writeback */ +int vkms_enable_writeback_connector(struct vkms_device *vkmsdev, struct vkms_output *vkms_out); + +/* Colorops */ +int vkms_initialize_colorops(struct drm_plane *plane); + +#endif /* _VKMS_DRV_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c new file mode 100644 index 000000000..dfb8e13cb --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_formats.c @@ -0,0 +1,971 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include + +#include +#include +#include + +#include + +#include "vkms_formats.h" + +/** + * packed_pixels_offset() - Get the offset of the block containing the pixel at coordinates x/y + * + * @frame_info: Buffer metadata + * @x: The x coordinate of the wanted pixel in the buffer + * @y: The y coordinate of the wanted pixel in the buffer + * @plane_index: The index of the plane to use + * @offset: The returned offset inside the buffer of the block + * @rem_x: The returned X coordinate of the requested pixel in the block + * @rem_y: The returned Y coordinate of the requested pixel in the block + * + * As some pixel formats store multiple pixels in a block (DRM_FORMAT_R* for example), some + * pixels are not individually addressable. This function return 3 values: the offset of the + * whole block, and the coordinate of the requested pixel inside this block. + * For example, if the format is DRM_FORMAT_R1 and the requested coordinate is 13,5, the offset + * will point to the byte 5*pitches + 13/8 (second byte of the 5th line), and the rem_x/rem_y + * coordinates will be (13 % 8, 5 % 1) = (5, 0) + * + * With this function, the caller just have to extract the correct pixel from the block. + */ +static void packed_pixels_offset(const struct vkms_frame_info *frame_info, int x, int y, + int plane_index, int *offset, int *rem_x, int *rem_y) +{ + struct drm_framebuffer *fb = frame_info->fb; + const struct drm_format_info *format = frame_info->fb->format; + /* Directly using x and y to multiply pitches and format->ccp is not sufficient because + * in some formats a block can represent multiple pixels. + * + * Dividing x and y by the block size allows to extract the correct offset of the block + * containing the pixel. + */ + + int block_x = x / drm_format_info_block_width(format, plane_index); + int block_y = y / drm_format_info_block_height(format, plane_index); + int block_pitch = fb->pitches[plane_index] * drm_format_info_block_height(format, + plane_index); + *rem_x = x % drm_format_info_block_width(format, plane_index); + *rem_y = y % drm_format_info_block_height(format, plane_index); + *offset = fb->offsets[plane_index] + + block_y * block_pitch + + block_x * format->char_per_block[plane_index]; +} + +/** + * packed_pixels_addr() - Get the pointer to the block containing the pixel at the given + * coordinates + * + * @frame_info: Buffer metadata + * @x: The x (width) coordinate inside the plane + * @y: The y (height) coordinate inside the plane + * @plane_index: The index of the plane + * @addr: The returned pointer + * @rem_x: The returned X coordinate of the requested pixel in the block + * @rem_y: The returned Y coordinate of the requested pixel in the block + * + * Takes the information stored in the frame_info, a pair of coordinates, and returns the address + * of the block containing this pixel and the pixel position inside this block. + * + * See @packed_pixels_offset for details about rem_x/rem_y behavior. + */ +static void packed_pixels_addr(const struct vkms_frame_info *frame_info, + int x, int y, int plane_index, u8 **addr, int *rem_x, + int *rem_y) +{ + int offset; + + packed_pixels_offset(frame_info, x, y, plane_index, &offset, rem_x, rem_y); + *addr = (u8 *)frame_info->map[0].vaddr + offset; +} + +/** + * get_block_step_bytes() - Common helper to compute the correct step value between each pixel block + * to read in a certain direction. + * + * @fb: Framebuffer to iter on + * @direction: Direction of the reading + * @plane_index: Plane to get the step from + * + * As the returned count is the number of bytes between two consecutive blocks in a direction, + * the caller may have to read multiple pixels before using the next one (for example, to read from + * left to right in a DRM_FORMAT_R1 plane, each block contains 8 pixels, so the step must be used + * only every 8 pixels). + */ +static int get_block_step_bytes(struct drm_framebuffer *fb, enum pixel_read_direction direction, + int plane_index) +{ + switch (direction) { + case READ_LEFT_TO_RIGHT: + return fb->format->char_per_block[plane_index]; + case READ_RIGHT_TO_LEFT: + return -fb->format->char_per_block[plane_index]; + case READ_TOP_TO_BOTTOM: + return (int)fb->pitches[plane_index] * drm_format_info_block_width(fb->format, + plane_index); + case READ_BOTTOM_TO_TOP: + return -(int)fb->pitches[plane_index] * drm_format_info_block_width(fb->format, + plane_index); + } + + return 0; +} + +/** + * packed_pixels_addr_1x1() - Get the pointer to the block containing the pixel at the given + * coordinates + * + * @frame_info: Buffer metadata + * @x: The x (width) coordinate inside the plane + * @y: The y (height) coordinate inside the plane + * @plane_index: The index of the plane + * @addr: The returned pointer + * + * This function can only be used with format where block_h == block_w == 1. + */ +static void packed_pixels_addr_1x1(const struct vkms_frame_info *frame_info, + int x, int y, int plane_index, u8 **addr) +{ + int offset, rem_x, rem_y; + + WARN_ONCE(drm_format_info_block_width(frame_info->fb->format, + plane_index) != 1, + "%s() only support formats with block_w == 1", __func__); + WARN_ONCE(drm_format_info_block_height(frame_info->fb->format, + plane_index) != 1, + "%s() only support formats with block_h == 1", __func__); + + packed_pixels_offset(frame_info, x, y, plane_index, &offset, &rem_x, + &rem_y); + *addr = (u8 *)frame_info->map[0].vaddr + offset; +} + +/** + * get_subsampling() - Get the subsampling divisor value on a specific direction + * + * @format: format to extarct the subsampling from + * @direction: direction of the subsampling requested + */ +static int get_subsampling(const struct drm_format_info *format, + enum pixel_read_direction direction) +{ + switch (direction) { + case READ_BOTTOM_TO_TOP: + case READ_TOP_TO_BOTTOM: + return format->vsub; + case READ_RIGHT_TO_LEFT: + case READ_LEFT_TO_RIGHT: + return format->hsub; + } + WARN_ONCE(true, "Invalid direction for pixel reading: %d\n", direction); + return 1; +} + +/** + * get_subsampling_offset() - An offset for keeping the chroma siting consistent regardless of + * x_start and y_start values + * + * @direction: direction of the reading to properly compute this offset + * @x_start: x coordinate of the starting point of the readed line + * @y_start: y coordinate of the starting point of the readed line + */ +static int get_subsampling_offset(enum pixel_read_direction direction, int x_start, int y_start) +{ + switch (direction) { + case READ_BOTTOM_TO_TOP: + return -y_start - 1; + case READ_TOP_TO_BOTTOM: + return y_start; + case READ_RIGHT_TO_LEFT: + return -x_start - 1; + case READ_LEFT_TO_RIGHT: + return x_start; + } + WARN_ONCE(true, "Invalid direction for pixel reading: %d\n", direction); + return 0; +} + +/* + * The following functions take pixel data (a, r, g, b, pixel, ...) and convert them to + * &struct pixel_argb_u16 + * + * They are used in the `read_line`s functions to avoid duplicate work for some pixel formats. + */ + +static struct pixel_argb_u16 argb_u16_from_u8888(u8 a, u8 r, u8 g, u8 b) +{ + struct pixel_argb_u16 out_pixel; + /* + * The 257 is the "conversion ratio". This number is obtained by the + * (2^16 - 1) / (2^8 - 1) division. Which, in this case, tries to get + * the best color value in a pixel format with more possibilities. + * A similar idea applies to others RGB color conversions. + */ + out_pixel.a = (u16)a * 257; + out_pixel.r = (u16)r * 257; + out_pixel.g = (u16)g * 257; + out_pixel.b = (u16)b * 257; + + return out_pixel; +} + +static struct pixel_argb_u16 argb_u16_from_u16161616(u16 a, u16 r, u16 g, u16 b) +{ + struct pixel_argb_u16 out_pixel; + + out_pixel.a = a; + out_pixel.r = r; + out_pixel.g = g; + out_pixel.b = b; + + return out_pixel; +} + +static struct pixel_argb_u16 argb_u16_from_le16161616(__le16 a, __le16 r, __le16 g, __le16 b) +{ + return argb_u16_from_u16161616(le16_to_cpu(a), le16_to_cpu(r), le16_to_cpu(g), + le16_to_cpu(b)); +} + +static struct pixel_argb_u16 argb_u16_from_RGB565(const __le16 *pixel) +{ + struct pixel_argb_u16 out_pixel; + + s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31)); + s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63)); + + u16 rgb_565 = le16_to_cpu(*pixel); + s64 fp_r = drm_int2fixp((rgb_565 >> 11) & 0x1f); + s64 fp_g = drm_int2fixp((rgb_565 >> 5) & 0x3f); + s64 fp_b = drm_int2fixp(rgb_565 & 0x1f); + + out_pixel.a = (u16)0xffff; + out_pixel.r = drm_fixp2int_round(drm_fixp_mul(fp_r, fp_rb_ratio)); + out_pixel.g = drm_fixp2int_round(drm_fixp_mul(fp_g, fp_g_ratio)); + out_pixel.b = drm_fixp2int_round(drm_fixp_mul(fp_b, fp_rb_ratio)); + + return out_pixel; +} + +static struct pixel_argb_u16 argb_u16_from_gray8(u8 gray) +{ + return argb_u16_from_u8888(255, gray, gray, gray); +} + +static struct pixel_argb_u16 argb_u16_from_grayu16(u16 gray) +{ + return argb_u16_from_u16161616(0xFFFF, gray, gray, gray); +} + +static struct pixel_argb_u16 argb_u16_from_BGR565(const __le16 *pixel) +{ + struct pixel_argb_u16 out_pixel; + + out_pixel = argb_u16_from_RGB565(pixel); + swap(out_pixel.r, out_pixel.b); + + return out_pixel; +} + +VISIBLE_IF_KUNIT +struct pixel_argb_u16 argb_u16_from_yuv161616(const struct conversion_matrix *matrix, + u16 y, u16 channel_1, u16 channel_2) +{ + u16 r, g, b; + s64 fp_y, fp_channel_1, fp_channel_2; + s64 fp_r, fp_g, fp_b; + + fp_y = drm_int2fixp((int)y - matrix->y_offset * 257); + fp_channel_1 = drm_int2fixp((int)channel_1 - 128 * 257); + fp_channel_2 = drm_int2fixp((int)channel_2 - 128 * 257); + + fp_r = drm_fixp_mul(matrix->matrix[0][0], fp_y) + + drm_fixp_mul(matrix->matrix[0][1], fp_channel_1) + + drm_fixp_mul(matrix->matrix[0][2], fp_channel_2); + fp_g = drm_fixp_mul(matrix->matrix[1][0], fp_y) + + drm_fixp_mul(matrix->matrix[1][1], fp_channel_1) + + drm_fixp_mul(matrix->matrix[1][2], fp_channel_2); + fp_b = drm_fixp_mul(matrix->matrix[2][0], fp_y) + + drm_fixp_mul(matrix->matrix[2][1], fp_channel_1) + + drm_fixp_mul(matrix->matrix[2][2], fp_channel_2); + + fp_r = drm_fixp2int_round(fp_r); + fp_g = drm_fixp2int_round(fp_g); + fp_b = drm_fixp2int_round(fp_b); + + r = clamp(fp_r, 0, 0xffff); + g = clamp(fp_g, 0, 0xffff); + b = clamp(fp_b, 0, 0xffff); + + return argb_u16_from_u16161616(0xffff, r, g, b); +} +EXPORT_SYMBOL_IF_KUNIT(argb_u16_from_yuv161616); + +/** + * READ_LINE() - Generic generator for a read_line function which can be used for format with one + * plane and a block_h == block_w == 1. + * + * @function_name: Function name to generate + * @pixel_name: Temporary pixel name used in the @__VA_ARGS__ parameters + * @pixel_type: Used to specify the type you want to cast the pixel pointer + * @callback: Callback to call for each pixels. This fonction should take @__VA_ARGS__ as parameter + * and return a pixel_argb_u16 + * __VA_ARGS__: Argument to pass inside the callback. You can use @pixel_name to access current + * pixel. + */ +#define READ_LINE(function_name, pixel_name, pixel_type, callback, ...) \ +static void function_name(const struct vkms_plane_state *plane, int x_start, \ + int y_start, enum pixel_read_direction direction, int count, \ + struct pixel_argb_u16 out_pixel[]) \ +{ \ + struct pixel_argb_u16 *end = out_pixel + count; \ + int step = get_block_step_bytes(plane->frame_info->fb, direction, 0); \ + u8 *src_pixels; \ + \ + packed_pixels_addr_1x1(plane->frame_info, x_start, y_start, 0, &src_pixels); \ + \ + while (out_pixel < end) { \ + pixel_type *(pixel_name) = (pixel_type *)src_pixels; \ + *out_pixel = (callback)(__VA_ARGS__); \ + out_pixel += 1; \ + src_pixels += step; \ + } \ +} + +/** + * READ_LINE_ARGB8888() - Generic generator for ARGB8888 formats. + * The pixel type used is u8, so pixel_name[0]..pixel_name[n] are the n components of the pixel. + * + * @function_name: Function name to generate + * @pixel_name: temporary pixel to use in @a, @r, @g and @b parameters + * @a: alpha value + * @r: red value + * @g: green value + * @b: blue value + */ +#define READ_LINE_ARGB8888(function_name, pixel_name, a, r, g, b) \ + READ_LINE(function_name, pixel_name, u8, argb_u16_from_u8888, a, r, g, b) +/** + * READ_LINE_le16161616() - Generic generator for ARGB16161616 formats. + * The pixel type used is u16, so pixel_name[0]..pixel_name[n] are the n components of the pixel. + * + * @function_name: Function name to generate + * @pixel_name: temporary pixel to use in @a, @r, @g and @b parameters + * @a: alpha value + * @r: red value + * @g: green value + * @b: blue value + */ +#define READ_LINE_le16161616(function_name, pixel_name, a, r, g, b) \ + READ_LINE(function_name, pixel_name, __le16, argb_u16_from_le16161616, a, r, g, b) + +/* + * The following functions are read_line function for each pixel format supported by VKMS. + * + * They read a line starting at the point @x_start,@y_start following the @direction. The result + * is stored in @out_pixel and in a 64 bits format, see struct pixel_argb_u16. + * + * These functions are very repetitive, but the innermost pixel loops must be kept inside these + * functions for performance reasons. Some benchmarking was done in [1] where having the innermost + * loop factored out of these functions showed a slowdown by a factor of three. + * + * [1]: https://lore.kernel.org/dri-devel/d258c8dc-78e9-4509-9037-a98f7f33b3a3@riseup.net/ + */ + +static void Rx_read_line(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]) +{ + struct pixel_argb_u16 *end = out_pixel + count; + int bits_per_pixel = drm_format_info_bpp(plane->frame_info->fb->format, 0); + u8 *src_pixels; + int rem_x, rem_y; + + WARN_ONCE(drm_format_info_block_height(plane->frame_info->fb->format, 0) != 1, + "%s() only support formats with block_h == 1", __func__); + + packed_pixels_addr(plane->frame_info, x_start, y_start, 0, &src_pixels, &rem_x, &rem_y); + int bit_offset = (8 - bits_per_pixel) - rem_x * bits_per_pixel; + int step = get_block_step_bytes(plane->frame_info->fb, direction, 0); + int mask = (0x1 << bits_per_pixel) - 1; + int lum_per_level = 0xFFFF / mask; + + if (direction == READ_LEFT_TO_RIGHT || direction == READ_RIGHT_TO_LEFT) { + int restart_bit_offset; + int step_bit_offset; + + if (direction == READ_LEFT_TO_RIGHT) { + restart_bit_offset = 8 - bits_per_pixel; + step_bit_offset = -bits_per_pixel; + } else { + restart_bit_offset = 0; + step_bit_offset = bits_per_pixel; + } + + while (out_pixel < end) { + u8 val = ((*src_pixels) >> bit_offset) & mask; + + *out_pixel = argb_u16_from_grayu16((int)val * lum_per_level); + + bit_offset += step_bit_offset; + if (bit_offset < 0 || 8 <= bit_offset) { + bit_offset = restart_bit_offset; + src_pixels += step; + } + out_pixel += 1; + } + } else if (direction == READ_TOP_TO_BOTTOM || direction == READ_BOTTOM_TO_TOP) { + while (out_pixel < end) { + u8 val = (*src_pixels >> bit_offset) & mask; + *out_pixel = argb_u16_from_grayu16((int)val * lum_per_level); + src_pixels += step; + out_pixel += 1; + } + } +} + +static void R1_read_line(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]) +{ + Rx_read_line(plane, x_start, y_start, direction, count, out_pixel); +} + +static void R2_read_line(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]) +{ + Rx_read_line(plane, x_start, y_start, direction, count, out_pixel); +} + +static void R4_read_line(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]) +{ + Rx_read_line(plane, x_start, y_start, direction, count, out_pixel); +} + + +READ_LINE_ARGB8888(XRGB8888_read_line, px, 0xFF, px[2], px[1], px[0]) +READ_LINE_ARGB8888(XBGR8888_read_line, px, 0xFF, px[0], px[1], px[2]) + +READ_LINE_ARGB8888(ARGB8888_read_line, px, px[3], px[2], px[1], px[0]) +READ_LINE_ARGB8888(ABGR8888_read_line, px, px[3], px[0], px[1], px[2]) +READ_LINE_ARGB8888(RGBA8888_read_line, px, px[0], px[3], px[2], px[1]) +READ_LINE_ARGB8888(BGRA8888_read_line, px, px[0], px[1], px[2], px[3]) + +READ_LINE_ARGB8888(RGB888_read_line, px, 0xFF, px[2], px[1], px[0]) +READ_LINE_ARGB8888(BGR888_read_line, px, 0xFF, px[0], px[1], px[2]) + +READ_LINE_le16161616(ARGB16161616_read_line, px, px[3], px[2], px[1], px[0]) +READ_LINE_le16161616(ABGR16161616_read_line, px, px[3], px[0], px[1], px[2]) +READ_LINE_le16161616(XRGB16161616_read_line, px, cpu_to_le16(0xFFFF), px[2], px[1], px[0]) +READ_LINE_le16161616(XBGR16161616_read_line, px, cpu_to_le16(0xFFFF), px[0], px[1], px[2]) + +READ_LINE(RGB565_read_line, px, __le16, argb_u16_from_RGB565, px) +READ_LINE(BGR565_read_line, px, __le16, argb_u16_from_BGR565, px) + +READ_LINE(R8_read_line, px, u8, argb_u16_from_gray8, *px) + +/* + * This callback can be used for YUV formats where U and V values are + * stored in the same plane (often called semi-planar formats). It will + * correctly handle subsampling as described in the drm_format_info of the plane. + * + * The conversion matrix stored in the @plane is used to: + * - Apply the correct color range and encoding + * - Convert YUV and YVU with the same function (a column swap is needed when setting up + * plane->conversion_matrix) + */ + +/** + * READ_LINE_YUV_SEMIPLANAR() - Generic generator for a read_line function which can be used for yuv + * formats with two planes and block_w == block_h == 1. + * + * @function_name: Function name to generate + * @pixel_1_name: temporary pixel name for the first plane used in the @__VA_ARGS__ parameters + * @pixel_2_name: temporary pixel name for the second plane used in the @__VA_ARGS__ parameters + * @pixel_1_type: Used to specify the type you want to cast the pixel pointer on the plane 1 + * @pixel_2_type: Used to specify the type you want to cast the pixel pointer on the plane 2 + * @callback: Callback to call for each pixels. This function should take + * (struct conversion_matrix*, @__VA_ARGS__) as parameter and return a pixel_argb_u16 + * __VA_ARGS__: Argument to pass inside the callback. You can use @pixel_1_name and @pixel_2_name + * to access current pixel values + */ +#define READ_LINE_YUV_SEMIPLANAR(function_name, pixel_1_name, pixel_2_name, pixel_1_type, \ + pixel_2_type, callback, ...) \ +static void function_name(const struct vkms_plane_state *plane, int x_start, \ + int y_start, enum pixel_read_direction direction, int count, \ + struct pixel_argb_u16 out_pixel[]) \ +{ \ + u8 *plane_1; \ + u8 *plane_2; \ + \ + packed_pixels_addr_1x1(plane->frame_info, x_start, y_start, 0, \ + &plane_1); \ + packed_pixels_addr_1x1(plane->frame_info, \ + x_start / plane->frame_info->fb->format->hsub, \ + y_start / plane->frame_info->fb->format->vsub, 1, \ + &plane_2); \ + int step_1 = get_block_step_bytes(plane->frame_info->fb, direction, 0); \ + int step_2 = get_block_step_bytes(plane->frame_info->fb, direction, 1); \ + int subsampling = get_subsampling(plane->frame_info->fb->format, direction); \ + int subsampling_offset = get_subsampling_offset(direction, x_start, y_start); \ + const struct conversion_matrix *conversion_matrix = &plane->conversion_matrix; \ + \ + for (int i = 0; i < count; i++) { \ + pixel_1_type *(pixel_1_name) = (pixel_1_type *)plane_1; \ + pixel_2_type *(pixel_2_name) = (pixel_2_type *)plane_2; \ + *out_pixel = (callback)(conversion_matrix, __VA_ARGS__); \ + out_pixel += 1; \ + plane_1 += step_1; \ + if ((i + subsampling_offset + 1) % subsampling == 0) \ + plane_2 += step_2; \ + } \ +} + +READ_LINE_YUV_SEMIPLANAR(YUV888_semiplanar_read_line, y, uv, u8, u8, argb_u16_from_yuv161616, + y[0] * 257, uv[0] * 257, uv[1] * 257) +READ_LINE_YUV_SEMIPLANAR(YUV161616_semiplanar_read_line, y, uv, u16, u16, argb_u16_from_yuv161616, + y[0], uv[0], uv[1]) +/* + * This callback can be used for YUV format where each color component is + * stored in a different plane (often called planar formats). It will + * correctly handle subsampling as described in the drm_format_info of the plane. + * + * The conversion matrix stored in the @plane is used to: + * - Apply the correct color range and encoding + * - Convert YUV and YVU with the same function (a column swap is needed when setting up + * plane->conversion_matrix) + */ +static void planar_yuv_read_line(const struct vkms_plane_state *plane, int x_start, + int y_start, enum pixel_read_direction direction, int count, + struct pixel_argb_u16 out_pixel[]) +{ + u8 *y_plane; + u8 *channel_1_plane; + u8 *channel_2_plane; + + packed_pixels_addr_1x1(plane->frame_info, x_start, y_start, 0, + &y_plane); + packed_pixels_addr_1x1(plane->frame_info, + x_start / plane->frame_info->fb->format->hsub, + y_start / plane->frame_info->fb->format->vsub, 1, + &channel_1_plane); + packed_pixels_addr_1x1(plane->frame_info, + x_start / plane->frame_info->fb->format->hsub, + y_start / plane->frame_info->fb->format->vsub, 2, + &channel_2_plane); + int step_y = get_block_step_bytes(plane->frame_info->fb, direction, 0); + int step_channel_1 = get_block_step_bytes(plane->frame_info->fb, direction, 1); + int step_channel_2 = get_block_step_bytes(plane->frame_info->fb, direction, 2); + int subsampling = get_subsampling(plane->frame_info->fb->format, direction); + int subsampling_offset = get_subsampling_offset(direction, x_start, y_start); + const struct conversion_matrix *conversion_matrix = &plane->conversion_matrix; + + for (int i = 0; i < count; i++) { + *out_pixel = argb_u16_from_yuv161616(conversion_matrix, + *y_plane * 257, *channel_1_plane * 257, + *channel_2_plane * 257); + out_pixel += 1; + y_plane += step_y; + if ((i + subsampling_offset + 1) % subsampling == 0) { + channel_1_plane += step_channel_1; + channel_2_plane += step_channel_2; + } + } +} + +/* + * The following functions take one &struct pixel_argb_u16 and convert it to a specific format. + * The result is stored in @out_pixel. + * + * They are used in vkms_writeback_row() to convert and store a pixel from the src_buffer to + * the writeback buffer. + */ +static void argb_u16_to_ARGB8888(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + /* + * This sequence below is important because the format's byte order is + * in little-endian. In the case of the ARGB8888 the memory is + * organized this way: + * + * | Addr | = blue channel + * | Addr + 1 | = green channel + * | Addr + 2 | = Red channel + * | Addr + 3 | = Alpha channel + */ + out_pixel[3] = DIV_ROUND_CLOSEST(in_pixel->a, 257); + out_pixel[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257); + out_pixel[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257); + out_pixel[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257); +} + +static void argb_u16_to_XRGB8888(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + out_pixel[3] = 0xff; + out_pixel[2] = DIV_ROUND_CLOSEST(in_pixel->r, 257); + out_pixel[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257); + out_pixel[0] = DIV_ROUND_CLOSEST(in_pixel->b, 257); +} + +static void argb_u16_to_ABGR8888(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + out_pixel[3] = DIV_ROUND_CLOSEST(in_pixel->a, 257); + out_pixel[2] = DIV_ROUND_CLOSEST(in_pixel->b, 257); + out_pixel[1] = DIV_ROUND_CLOSEST(in_pixel->g, 257); + out_pixel[0] = DIV_ROUND_CLOSEST(in_pixel->r, 257); +} + +static void argb_u16_to_ARGB16161616(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + __le16 *pixel = (__le16 *)out_pixel; + + pixel[3] = cpu_to_le16(in_pixel->a); + pixel[2] = cpu_to_le16(in_pixel->r); + pixel[1] = cpu_to_le16(in_pixel->g); + pixel[0] = cpu_to_le16(in_pixel->b); +} + +static void argb_u16_to_XRGB16161616(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + __le16 *pixel = (__le16 *)out_pixel; + + pixel[3] = cpu_to_le16(0xffff); + pixel[2] = cpu_to_le16(in_pixel->r); + pixel[1] = cpu_to_le16(in_pixel->g); + pixel[0] = cpu_to_le16(in_pixel->b); +} + +static void argb_u16_to_RGB565(u8 *out_pixel, const struct pixel_argb_u16 *in_pixel) +{ + __le16 *pixel = (__le16 *)out_pixel; + + s64 fp_rb_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(31)); + s64 fp_g_ratio = drm_fixp_div(drm_int2fixp(65535), drm_int2fixp(63)); + + s64 fp_r = drm_int2fixp(in_pixel->r); + s64 fp_g = drm_int2fixp(in_pixel->g); + s64 fp_b = drm_int2fixp(in_pixel->b); + + u16 r = drm_fixp2int(drm_fixp_div(fp_r, fp_rb_ratio)); + u16 g = drm_fixp2int(drm_fixp_div(fp_g, fp_g_ratio)); + u16 b = drm_fixp2int(drm_fixp_div(fp_b, fp_rb_ratio)); + + *pixel = cpu_to_le16(r << 11 | g << 5 | b); +} + +/** + * vkms_writeback_row() - Generic loop for all supported writeback format. It is executed just + * after the blending to write a line in the writeback buffer. + * + * @wb: Job where to insert the final image + * @src_buffer: Line to write + * @y: Row to write in the writeback buffer + */ +void vkms_writeback_row(struct vkms_writeback_job *wb, + const struct line_buffer *src_buffer, int y) +{ + struct vkms_frame_info *frame_info = &wb->wb_frame_info; + int x_dst = frame_info->dst.x1; + u8 *dst_pixels; + int rem_x, rem_y; + + packed_pixels_addr(frame_info, x_dst, y, 0, &dst_pixels, &rem_x, &rem_y); + struct pixel_argb_u16 *in_pixels = src_buffer->pixels; + int x_limit = min_t(size_t, drm_rect_width(&frame_info->dst), src_buffer->n_pixels); + + for (size_t x = 0; x < x_limit; x++, dst_pixels += frame_info->fb->format->cpp[0]) + wb->pixel_write(dst_pixels, &in_pixels[x]); +} + +/** + * get_pixel_read_line_function() - Retrieve the correct read_line function for a specific + * format. The returned pointer is NULL for unsupported pixel formats. The caller must ensure that + * the pointer is valid before using it in a vkms_plane_state. + * + * @format: DRM_FORMAT_* value for which to obtain a conversion function (see [drm_fourcc.h]) + */ +pixel_read_line_t get_pixel_read_line_function(u32 format) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + return &ARGB8888_read_line; + case DRM_FORMAT_ABGR8888: + return &ABGR8888_read_line; + case DRM_FORMAT_BGRA8888: + return &BGRA8888_read_line; + case DRM_FORMAT_RGBA8888: + return &RGBA8888_read_line; + case DRM_FORMAT_XRGB8888: + return &XRGB8888_read_line; + case DRM_FORMAT_XBGR8888: + return &XBGR8888_read_line; + case DRM_FORMAT_RGB888: + return &RGB888_read_line; + case DRM_FORMAT_BGR888: + return &BGR888_read_line; + case DRM_FORMAT_ARGB16161616: + return &ARGB16161616_read_line; + case DRM_FORMAT_ABGR16161616: + return &ABGR16161616_read_line; + case DRM_FORMAT_XRGB16161616: + return &XRGB16161616_read_line; + case DRM_FORMAT_XBGR16161616: + return &XBGR16161616_read_line; + case DRM_FORMAT_RGB565: + return &RGB565_read_line; + case DRM_FORMAT_BGR565: + return &BGR565_read_line; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + return &YUV888_semiplanar_read_line; + case DRM_FORMAT_P010: + case DRM_FORMAT_P012: + case DRM_FORMAT_P016: + return &YUV161616_semiplanar_read_line; + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YVU444: + return &planar_yuv_read_line; + case DRM_FORMAT_R1: + return &R1_read_line; + case DRM_FORMAT_R2: + return &R2_read_line; + case DRM_FORMAT_R4: + return &R4_read_line; + case DRM_FORMAT_R8: + return &R8_read_line; + default: + /* + * This is a bug in vkms_plane_atomic_check(). All the supported + * format must: + * - Be listed in vkms_formats in vkms_plane.c + * - Have a pixel_read callback defined here + */ + pr_err("Pixel format %p4cc is not supported by VKMS planes. This is a kernel bug, atomic check must forbid this configuration.\n", + &format); + BUG(); + } +} + +/* + * Those matrices were generated using the colour python framework + * + * Below are the function calls used to generate each matrix, go to + * https://colour.readthedocs.io/en/develop/generated/colour.matrix_YCbCr.html + * for more info: + * + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.601"], + * is_legal = False, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix no_operation = { + .matrix = { + { 4294967296, 0, 0, }, + { 0, 4294967296, 0, }, + { 0, 0, 4294967296, }, + }, + .y_offset = 0, +}; + +static const struct conversion_matrix yuv_bt601_full = { + .matrix = { + { 4294967296, 0, 6021544149 }, + { 4294967296, -1478054095, -3067191994 }, + { 4294967296, 7610682049, 0 }, + }, + .y_offset = 0, +}; + +/* + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.601"], + * is_legal = True, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix yuv_bt601_limited = { + .matrix = { + { 5020601039, 0, 6881764740 }, + { 5020601039, -1689204679, -3505362278 }, + { 5020601039, 8697922339, 0 }, + }, + .y_offset = 16, +}; + +/* + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.709"], + * is_legal = False, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix yuv_bt709_full = { + .matrix = { + { 4294967296, 0, 6763714498 }, + { 4294967296, -804551626, -2010578443 }, + { 4294967296, 7969741314, 0 }, + }, + .y_offset = 0, +}; + +/* + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.709"], + * is_legal = True, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix yuv_bt709_limited = { + .matrix = { + { 5020601039, 0, 7729959424 }, + { 5020601039, -919487572, -2297803934 }, + { 5020601039, 9108275786, 0 }, + }, + .y_offset = 16, +}; + +/* + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"], + * is_legal = False, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix yuv_bt2020_full = { + .matrix = { + { 4294967296, 0, 6333358775 }, + { 4294967296, -706750298, -2453942994 }, + { 4294967296, 8080551471, 0 }, + }, + .y_offset = 0, +}; + +/* + * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"], + * is_legal = True, + * bits = 8) * 2**32).astype(int) + */ +static const struct conversion_matrix yuv_bt2020_limited = { + .matrix = { + { 5020601039, 0, 7238124312 }, + { 5020601039, -807714626, -2804506279 }, + { 5020601039, 9234915964, 0 }, + }, + .y_offset = 16, +}; + +/** + * swap_uv_columns() - Swap u and v column of a given matrix + * + * @matrix: Matrix in which column are swapped + */ +static void swap_uv_columns(struct conversion_matrix *matrix) +{ + swap(matrix->matrix[0][2], matrix->matrix[0][1]); + swap(matrix->matrix[1][2], matrix->matrix[1][1]); + swap(matrix->matrix[2][2], matrix->matrix[2][1]); +} + +/** + * get_conversion_matrix_to_argb_u16() - Retrieve the correct yuv to rgb conversion matrix for a + * given encoding and range. + * + * @format: DRM_FORMAT_* value for which to obtain a conversion function (see [drm_fourcc.h]) + * @encoding: DRM_COLOR_* value for which to obtain a conversion matrix + * @range: DRM_COLOR_*_RANGE value for which to obtain a conversion matrix + * @matrix: Pointer to store the value into + */ +void get_conversion_matrix_to_argb_u16(u32 format, + enum drm_color_encoding encoding, + enum drm_color_range range, + struct conversion_matrix *matrix) +{ + const struct conversion_matrix *matrix_to_copy; + bool limited_range; + + switch (range) { + case DRM_COLOR_YCBCR_LIMITED_RANGE: + limited_range = true; + break; + case DRM_COLOR_YCBCR_FULL_RANGE: + limited_range = false; + break; + case DRM_COLOR_RANGE_MAX: + limited_range = false; + WARN_ONCE(true, "The requested range is not supported."); + break; + } + + switch (encoding) { + case DRM_COLOR_YCBCR_BT601: + matrix_to_copy = limited_range ? &yuv_bt601_limited : + &yuv_bt601_full; + break; + case DRM_COLOR_YCBCR_BT709: + matrix_to_copy = limited_range ? &yuv_bt709_limited : + &yuv_bt709_full; + break; + case DRM_COLOR_YCBCR_BT2020: + matrix_to_copy = limited_range ? &yuv_bt2020_limited : + &yuv_bt2020_full; + break; + case DRM_COLOR_ENCODING_MAX: + matrix_to_copy = &no_operation; + WARN_ONCE(true, "The requested encoding is not supported."); + break; + } + + memcpy(matrix, matrix_to_copy, sizeof(*matrix_to_copy)); + + switch (format) { + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YVU444: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + swap_uv_columns(matrix); + break; + default: + break; + } +} +EXPORT_SYMBOL(get_conversion_matrix_to_argb_u16); + +/** + * get_pixel_write_function() - Retrieve the correct write_pixel function for a specific format. + * The returned pointer is NULL for unsupported pixel formats. The caller must ensure that the + * pointer is valid before using it in a vkms_writeback_job. + * + * @format: DRM_FORMAT_* value for which to obtain a conversion function (see [drm_fourcc.h]) + */ +pixel_write_t get_pixel_write_function(u32 format) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + return &argb_u16_to_ARGB8888; + case DRM_FORMAT_XRGB8888: + return &argb_u16_to_XRGB8888; + case DRM_FORMAT_ABGR8888: + return &argb_u16_to_ABGR8888; + case DRM_FORMAT_ARGB16161616: + return &argb_u16_to_ARGB16161616; + case DRM_FORMAT_XRGB16161616: + return &argb_u16_to_XRGB16161616; + case DRM_FORMAT_RGB565: + return &argb_u16_to_RGB565; + default: + /* + * This is a bug in vkms_writeback_atomic_check. All the supported + * format must: + * - Be listed in vkms_wb_formats in vkms_writeback.c + * - Have a pixel_write callback defined here + */ + pr_err("Pixel format %p4cc is not supported by VKMS writeback. This is a kernel bug, atomic check must forbid this configuration.\n", + &format); + BUG(); + } +} diff --git a/drivers/gpu/drm/vkms/vkms_formats.h b/drivers/gpu/drm/vkms/vkms_formats.h new file mode 100644 index 000000000..eeb208cdd --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_formats.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_FORMATS_H_ +#define _VKMS_FORMATS_H_ + +#include "vkms_drv.h" + +pixel_read_line_t get_pixel_read_line_function(u32 format); + +pixel_write_t get_pixel_write_function(u32 format); + +void get_conversion_matrix_to_argb_u16(u32 format, enum drm_color_encoding encoding, + enum drm_color_range range, + struct conversion_matrix *matrix); + +#if IS_ENABLED(CONFIG_KUNIT) +struct pixel_argb_u16 argb_u16_from_yuv161616(const struct conversion_matrix *matrix, + u16 y, u16 channel_1, u16 channel_2); +#endif + +#endif /* _VKMS_FORMATS_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_luts.c b/drivers/gpu/drm/vkms/vkms_luts.c new file mode 100644 index 000000000..82cb792f1 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_luts.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include "vkms_drv.h" +#include "vkms_luts.h" + +/* + * These luts were generated with a LUT generated based on + * skia's transfer function code. The LUT generator can be + * found at + * https://gitlab.freedesktop.org/hwentland/lutgen + */ + +static struct drm_color_lut linear_array[LUT_SIZE] = { + { 0x0, 0x0, 0x0, 0 }, + { 0x101, 0x101, 0x101, 0 }, + { 0x202, 0x202, 0x202, 0 }, + { 0x303, 0x303, 0x303, 0 }, + { 0x404, 0x404, 0x404, 0 }, + { 0x505, 0x505, 0x505, 0 }, + { 0x606, 0x606, 0x606, 0 }, + { 0x707, 0x707, 0x707, 0 }, + { 0x808, 0x808, 0x808, 0 }, + { 0x909, 0x909, 0x909, 0 }, + { 0xa0a, 0xa0a, 0xa0a, 0 }, + { 0xb0b, 0xb0b, 0xb0b, 0 }, + { 0xc0c, 0xc0c, 0xc0c, 0 }, + { 0xd0d, 0xd0d, 0xd0d, 0 }, + { 0xe0e, 0xe0e, 0xe0e, 0 }, + { 0xf0f, 0xf0f, 0xf0f, 0 }, + { 0x1010, 0x1010, 0x1010, 0 }, + { 0x1111, 0x1111, 0x1111, 0 }, + { 0x1212, 0x1212, 0x1212, 0 }, + { 0x1313, 0x1313, 0x1313, 0 }, + { 0x1414, 0x1414, 0x1414, 0 }, + { 0x1515, 0x1515, 0x1515, 0 }, + { 0x1616, 0x1616, 0x1616, 0 }, + { 0x1717, 0x1717, 0x1717, 0 }, + { 0x1818, 0x1818, 0x1818, 0 }, + { 0x1919, 0x1919, 0x1919, 0 }, + { 0x1a1a, 0x1a1a, 0x1a1a, 0 }, + { 0x1b1b, 0x1b1b, 0x1b1b, 0 }, + { 0x1c1c, 0x1c1c, 0x1c1c, 0 }, + { 0x1d1d, 0x1d1d, 0x1d1d, 0 }, + { 0x1e1e, 0x1e1e, 0x1e1e, 0 }, + { 0x1f1f, 0x1f1f, 0x1f1f, 0 }, + { 0x2020, 0x2020, 0x2020, 0 }, + { 0x2121, 0x2121, 0x2121, 0 }, + { 0x2222, 0x2222, 0x2222, 0 }, + { 0x2323, 0x2323, 0x2323, 0 }, + { 0x2424, 0x2424, 0x2424, 0 }, + { 0x2525, 0x2525, 0x2525, 0 }, + { 0x2626, 0x2626, 0x2626, 0 }, + { 0x2727, 0x2727, 0x2727, 0 }, + { 0x2828, 0x2828, 0x2828, 0 }, + { 0x2929, 0x2929, 0x2929, 0 }, + { 0x2a2a, 0x2a2a, 0x2a2a, 0 }, + { 0x2b2b, 0x2b2b, 0x2b2b, 0 }, + { 0x2c2c, 0x2c2c, 0x2c2c, 0 }, + { 0x2d2d, 0x2d2d, 0x2d2d, 0 }, + { 0x2e2e, 0x2e2e, 0x2e2e, 0 }, + { 0x2f2f, 0x2f2f, 0x2f2f, 0 }, + { 0x3030, 0x3030, 0x3030, 0 }, + { 0x3131, 0x3131, 0x3131, 0 }, + { 0x3232, 0x3232, 0x3232, 0 }, + { 0x3333, 0x3333, 0x3333, 0 }, + { 0x3434, 0x3434, 0x3434, 0 }, + { 0x3535, 0x3535, 0x3535, 0 }, + { 0x3636, 0x3636, 0x3636, 0 }, + { 0x3737, 0x3737, 0x3737, 0 }, + { 0x3838, 0x3838, 0x3838, 0 }, + { 0x3939, 0x3939, 0x3939, 0 }, + { 0x3a3a, 0x3a3a, 0x3a3a, 0 }, + { 0x3b3b, 0x3b3b, 0x3b3b, 0 }, + { 0x3c3c, 0x3c3c, 0x3c3c, 0 }, + { 0x3d3d, 0x3d3d, 0x3d3d, 0 }, + { 0x3e3e, 0x3e3e, 0x3e3e, 0 }, + { 0x3f3f, 0x3f3f, 0x3f3f, 0 }, + { 0x4040, 0x4040, 0x4040, 0 }, + { 0x4141, 0x4141, 0x4141, 0 }, + { 0x4242, 0x4242, 0x4242, 0 }, + { 0x4343, 0x4343, 0x4343, 0 }, + { 0x4444, 0x4444, 0x4444, 0 }, + { 0x4545, 0x4545, 0x4545, 0 }, + { 0x4646, 0x4646, 0x4646, 0 }, + { 0x4747, 0x4747, 0x4747, 0 }, + { 0x4848, 0x4848, 0x4848, 0 }, + { 0x4949, 0x4949, 0x4949, 0 }, + { 0x4a4a, 0x4a4a, 0x4a4a, 0 }, + { 0x4b4b, 0x4b4b, 0x4b4b, 0 }, + { 0x4c4c, 0x4c4c, 0x4c4c, 0 }, + { 0x4d4d, 0x4d4d, 0x4d4d, 0 }, + { 0x4e4e, 0x4e4e, 0x4e4e, 0 }, + { 0x4f4f, 0x4f4f, 0x4f4f, 0 }, + { 0x5050, 0x5050, 0x5050, 0 }, + { 0x5151, 0x5151, 0x5151, 0 }, + { 0x5252, 0x5252, 0x5252, 0 }, + { 0x5353, 0x5353, 0x5353, 0 }, + { 0x5454, 0x5454, 0x5454, 0 }, + { 0x5555, 0x5555, 0x5555, 0 }, + { 0x5656, 0x5656, 0x5656, 0 }, + { 0x5757, 0x5757, 0x5757, 0 }, + { 0x5858, 0x5858, 0x5858, 0 }, + { 0x5959, 0x5959, 0x5959, 0 }, + { 0x5a5a, 0x5a5a, 0x5a5a, 0 }, + { 0x5b5b, 0x5b5b, 0x5b5b, 0 }, + { 0x5c5c, 0x5c5c, 0x5c5c, 0 }, + { 0x5d5d, 0x5d5d, 0x5d5d, 0 }, + { 0x5e5e, 0x5e5e, 0x5e5e, 0 }, + { 0x5f5f, 0x5f5f, 0x5f5f, 0 }, + { 0x6060, 0x6060, 0x6060, 0 }, + { 0x6161, 0x6161, 0x6161, 0 }, + { 0x6262, 0x6262, 0x6262, 0 }, + { 0x6363, 0x6363, 0x6363, 0 }, + { 0x6464, 0x6464, 0x6464, 0 }, + { 0x6565, 0x6565, 0x6565, 0 }, + { 0x6666, 0x6666, 0x6666, 0 }, + { 0x6767, 0x6767, 0x6767, 0 }, + { 0x6868, 0x6868, 0x6868, 0 }, + { 0x6969, 0x6969, 0x6969, 0 }, + { 0x6a6a, 0x6a6a, 0x6a6a, 0 }, + { 0x6b6b, 0x6b6b, 0x6b6b, 0 }, + { 0x6c6c, 0x6c6c, 0x6c6c, 0 }, + { 0x6d6d, 0x6d6d, 0x6d6d, 0 }, + { 0x6e6e, 0x6e6e, 0x6e6e, 0 }, + { 0x6f6f, 0x6f6f, 0x6f6f, 0 }, + { 0x7070, 0x7070, 0x7070, 0 }, + { 0x7171, 0x7171, 0x7171, 0 }, + { 0x7272, 0x7272, 0x7272, 0 }, + { 0x7373, 0x7373, 0x7373, 0 }, + { 0x7474, 0x7474, 0x7474, 0 }, + { 0x7575, 0x7575, 0x7575, 0 }, + { 0x7676, 0x7676, 0x7676, 0 }, + { 0x7777, 0x7777, 0x7777, 0 }, + { 0x7878, 0x7878, 0x7878, 0 }, + { 0x7979, 0x7979, 0x7979, 0 }, + { 0x7a7a, 0x7a7a, 0x7a7a, 0 }, + { 0x7b7b, 0x7b7b, 0x7b7b, 0 }, + { 0x7c7c, 0x7c7c, 0x7c7c, 0 }, + { 0x7d7d, 0x7d7d, 0x7d7d, 0 }, + { 0x7e7e, 0x7e7e, 0x7e7e, 0 }, + { 0x7f7f, 0x7f7f, 0x7f7f, 0 }, + { 0x8080, 0x8080, 0x8080, 0 }, + { 0x8181, 0x8181, 0x8181, 0 }, + { 0x8282, 0x8282, 0x8282, 0 }, + { 0x8383, 0x8383, 0x8383, 0 }, + { 0x8484, 0x8484, 0x8484, 0 }, + { 0x8585, 0x8585, 0x8585, 0 }, + { 0x8686, 0x8686, 0x8686, 0 }, + { 0x8787, 0x8787, 0x8787, 0 }, + { 0x8888, 0x8888, 0x8888, 0 }, + { 0x8989, 0x8989, 0x8989, 0 }, + { 0x8a8a, 0x8a8a, 0x8a8a, 0 }, + { 0x8b8b, 0x8b8b, 0x8b8b, 0 }, + { 0x8c8c, 0x8c8c, 0x8c8c, 0 }, + { 0x8d8d, 0x8d8d, 0x8d8d, 0 }, + { 0x8e8e, 0x8e8e, 0x8e8e, 0 }, + { 0x8f8f, 0x8f8f, 0x8f8f, 0 }, + { 0x9090, 0x9090, 0x9090, 0 }, + { 0x9191, 0x9191, 0x9191, 0 }, + { 0x9292, 0x9292, 0x9292, 0 }, + { 0x9393, 0x9393, 0x9393, 0 }, + { 0x9494, 0x9494, 0x9494, 0 }, + { 0x9595, 0x9595, 0x9595, 0 }, + { 0x9696, 0x9696, 0x9696, 0 }, + { 0x9797, 0x9797, 0x9797, 0 }, + { 0x9898, 0x9898, 0x9898, 0 }, + { 0x9999, 0x9999, 0x9999, 0 }, + { 0x9a9a, 0x9a9a, 0x9a9a, 0 }, + { 0x9b9b, 0x9b9b, 0x9b9b, 0 }, + { 0x9c9c, 0x9c9c, 0x9c9c, 0 }, + { 0x9d9d, 0x9d9d, 0x9d9d, 0 }, + { 0x9e9e, 0x9e9e, 0x9e9e, 0 }, + { 0x9f9f, 0x9f9f, 0x9f9f, 0 }, + { 0xa0a0, 0xa0a0, 0xa0a0, 0 }, + { 0xa1a1, 0xa1a1, 0xa1a1, 0 }, + { 0xa2a2, 0xa2a2, 0xa2a2, 0 }, + { 0xa3a3, 0xa3a3, 0xa3a3, 0 }, + { 0xa4a4, 0xa4a4, 0xa4a4, 0 }, + { 0xa5a5, 0xa5a5, 0xa5a5, 0 }, + { 0xa6a6, 0xa6a6, 0xa6a6, 0 }, + { 0xa7a7, 0xa7a7, 0xa7a7, 0 }, + { 0xa8a8, 0xa8a8, 0xa8a8, 0 }, + { 0xa9a9, 0xa9a9, 0xa9a9, 0 }, + { 0xaaaa, 0xaaaa, 0xaaaa, 0 }, + { 0xabab, 0xabab, 0xabab, 0 }, + { 0xacac, 0xacac, 0xacac, 0 }, + { 0xadad, 0xadad, 0xadad, 0 }, + { 0xaeae, 0xaeae, 0xaeae, 0 }, + { 0xafaf, 0xafaf, 0xafaf, 0 }, + { 0xb0b0, 0xb0b0, 0xb0b0, 0 }, + { 0xb1b1, 0xb1b1, 0xb1b1, 0 }, + { 0xb2b2, 0xb2b2, 0xb2b2, 0 }, + { 0xb3b3, 0xb3b3, 0xb3b3, 0 }, + { 0xb4b4, 0xb4b4, 0xb4b4, 0 }, + { 0xb5b5, 0xb5b5, 0xb5b5, 0 }, + { 0xb6b6, 0xb6b6, 0xb6b6, 0 }, + { 0xb7b7, 0xb7b7, 0xb7b7, 0 }, + { 0xb8b8, 0xb8b8, 0xb8b8, 0 }, + { 0xb9b9, 0xb9b9, 0xb9b9, 0 }, + { 0xbaba, 0xbaba, 0xbaba, 0 }, + { 0xbbbb, 0xbbbb, 0xbbbb, 0 }, + { 0xbcbc, 0xbcbc, 0xbcbc, 0 }, + { 0xbdbd, 0xbdbd, 0xbdbd, 0 }, + { 0xbebe, 0xbebe, 0xbebe, 0 }, + { 0xbfbf, 0xbfbf, 0xbfbf, 0 }, + { 0xc0c0, 0xc0c0, 0xc0c0, 0 }, + { 0xc1c1, 0xc1c1, 0xc1c1, 0 }, + { 0xc2c2, 0xc2c2, 0xc2c2, 0 }, + { 0xc3c3, 0xc3c3, 0xc3c3, 0 }, + { 0xc4c4, 0xc4c4, 0xc4c4, 0 }, + { 0xc5c5, 0xc5c5, 0xc5c5, 0 }, + { 0xc6c6, 0xc6c6, 0xc6c6, 0 }, + { 0xc7c7, 0xc7c7, 0xc7c7, 0 }, + { 0xc8c8, 0xc8c8, 0xc8c8, 0 }, + { 0xc9c9, 0xc9c9, 0xc9c9, 0 }, + { 0xcaca, 0xcaca, 0xcaca, 0 }, + { 0xcbcb, 0xcbcb, 0xcbcb, 0 }, + { 0xcccc, 0xcccc, 0xcccc, 0 }, + { 0xcdcd, 0xcdcd, 0xcdcd, 0 }, + { 0xcece, 0xcece, 0xcece, 0 }, + { 0xcfcf, 0xcfcf, 0xcfcf, 0 }, + { 0xd0d0, 0xd0d0, 0xd0d0, 0 }, + { 0xd1d1, 0xd1d1, 0xd1d1, 0 }, + { 0xd2d2, 0xd2d2, 0xd2d2, 0 }, + { 0xd3d3, 0xd3d3, 0xd3d3, 0 }, + { 0xd4d4, 0xd4d4, 0xd4d4, 0 }, + { 0xd5d5, 0xd5d5, 0xd5d5, 0 }, + { 0xd6d6, 0xd6d6, 0xd6d6, 0 }, + { 0xd7d7, 0xd7d7, 0xd7d7, 0 }, + { 0xd8d8, 0xd8d8, 0xd8d8, 0 }, + { 0xd9d9, 0xd9d9, 0xd9d9, 0 }, + { 0xdada, 0xdada, 0xdada, 0 }, + { 0xdbdb, 0xdbdb, 0xdbdb, 0 }, + { 0xdcdc, 0xdcdc, 0xdcdc, 0 }, + { 0xdddd, 0xdddd, 0xdddd, 0 }, + { 0xdede, 0xdede, 0xdede, 0 }, + { 0xdfdf, 0xdfdf, 0xdfdf, 0 }, + { 0xe0e0, 0xe0e0, 0xe0e0, 0 }, + { 0xe1e1, 0xe1e1, 0xe1e1, 0 }, + { 0xe2e2, 0xe2e2, 0xe2e2, 0 }, + { 0xe3e3, 0xe3e3, 0xe3e3, 0 }, + { 0xe4e4, 0xe4e4, 0xe4e4, 0 }, + { 0xe5e5, 0xe5e5, 0xe5e5, 0 }, + { 0xe6e6, 0xe6e6, 0xe6e6, 0 }, + { 0xe7e7, 0xe7e7, 0xe7e7, 0 }, + { 0xe8e8, 0xe8e8, 0xe8e8, 0 }, + { 0xe9e9, 0xe9e9, 0xe9e9, 0 }, + { 0xeaea, 0xeaea, 0xeaea, 0 }, + { 0xebeb, 0xebeb, 0xebeb, 0 }, + { 0xecec, 0xecec, 0xecec, 0 }, + { 0xeded, 0xeded, 0xeded, 0 }, + { 0xeeee, 0xeeee, 0xeeee, 0 }, + { 0xefef, 0xefef, 0xefef, 0 }, + { 0xf0f0, 0xf0f0, 0xf0f0, 0 }, + { 0xf1f1, 0xf1f1, 0xf1f1, 0 }, + { 0xf2f2, 0xf2f2, 0xf2f2, 0 }, + { 0xf3f3, 0xf3f3, 0xf3f3, 0 }, + { 0xf4f4, 0xf4f4, 0xf4f4, 0 }, + { 0xf5f5, 0xf5f5, 0xf5f5, 0 }, + { 0xf6f6, 0xf6f6, 0xf6f6, 0 }, + { 0xf7f7, 0xf7f7, 0xf7f7, 0 }, + { 0xf8f8, 0xf8f8, 0xf8f8, 0 }, + { 0xf9f9, 0xf9f9, 0xf9f9, 0 }, + { 0xfafa, 0xfafa, 0xfafa, 0 }, + { 0xfbfb, 0xfbfb, 0xfbfb, 0 }, + { 0xfcfc, 0xfcfc, 0xfcfc, 0 }, + { 0xfdfd, 0xfdfd, 0xfdfd, 0 }, + { 0xfefe, 0xfefe, 0xfefe, 0 }, + { 0xffff, 0xffff, 0xffff, 0 }, +}; + +const struct vkms_color_lut linear_eotf = { + .base = linear_array, + .lut_length = LUT_SIZE, + .channel_value2index_ratio = 0xff00ffll +}; +EXPORT_SYMBOL(linear_eotf); + +static struct drm_color_lut srgb_array[LUT_SIZE] = { + { 0x0, 0x0, 0x0, 0 }, + { 0x13, 0x13, 0x13, 0 }, + { 0x27, 0x27, 0x27, 0 }, + { 0x3b, 0x3b, 0x3b, 0 }, + { 0x4f, 0x4f, 0x4f, 0 }, + { 0x63, 0x63, 0x63, 0 }, + { 0x77, 0x77, 0x77, 0 }, + { 0x8b, 0x8b, 0x8b, 0 }, + { 0x9f, 0x9f, 0x9f, 0 }, + { 0xb3, 0xb3, 0xb3, 0 }, + { 0xc6, 0xc6, 0xc6, 0 }, + { 0xdb, 0xdb, 0xdb, 0 }, + { 0xf0, 0xf0, 0xf0, 0 }, + { 0x107, 0x107, 0x107, 0 }, + { 0x11f, 0x11f, 0x11f, 0 }, + { 0x139, 0x139, 0x139, 0 }, + { 0x153, 0x153, 0x153, 0 }, + { 0x16f, 0x16f, 0x16f, 0 }, + { 0x18c, 0x18c, 0x18c, 0 }, + { 0x1aa, 0x1aa, 0x1aa, 0 }, + { 0x1ca, 0x1ca, 0x1ca, 0 }, + { 0x1eb, 0x1eb, 0x1eb, 0 }, + { 0x20d, 0x20d, 0x20d, 0 }, + { 0x231, 0x231, 0x231, 0 }, + { 0x256, 0x256, 0x256, 0 }, + { 0x27d, 0x27d, 0x27d, 0 }, + { 0x2a4, 0x2a4, 0x2a4, 0 }, + { 0x2ce, 0x2ce, 0x2ce, 0 }, + { 0x2f9, 0x2f9, 0x2f9, 0 }, + { 0x325, 0x325, 0x325, 0 }, + { 0x352, 0x352, 0x352, 0 }, + { 0x381, 0x381, 0x381, 0 }, + { 0x3b2, 0x3b2, 0x3b2, 0 }, + { 0x3e4, 0x3e4, 0x3e4, 0 }, + { 0x418, 0x418, 0x418, 0 }, + { 0x44d, 0x44d, 0x44d, 0 }, + { 0x484, 0x484, 0x484, 0 }, + { 0x4bc, 0x4bc, 0x4bc, 0 }, + { 0x4f6, 0x4f6, 0x4f6, 0 }, + { 0x531, 0x531, 0x531, 0 }, + { 0x56e, 0x56e, 0x56e, 0 }, + { 0x5ad, 0x5ad, 0x5ad, 0 }, + { 0x5ed, 0x5ed, 0x5ed, 0 }, + { 0x62f, 0x62f, 0x62f, 0 }, + { 0x672, 0x672, 0x672, 0 }, + { 0x6b7, 0x6b7, 0x6b7, 0 }, + { 0x6fe, 0x6fe, 0x6fe, 0 }, + { 0x746, 0x746, 0x746, 0 }, + { 0x791, 0x791, 0x791, 0 }, + { 0x7dc, 0x7dc, 0x7dc, 0 }, + { 0x82a, 0x82a, 0x82a, 0 }, + { 0x879, 0x879, 0x879, 0 }, + { 0x8ca, 0x8ca, 0x8ca, 0 }, + { 0x91d, 0x91d, 0x91d, 0 }, + { 0x971, 0x971, 0x971, 0 }, + { 0x9c7, 0x9c7, 0x9c7, 0 }, + { 0xa1f, 0xa1f, 0xa1f, 0 }, + { 0xa79, 0xa79, 0xa79, 0 }, + { 0xad4, 0xad4, 0xad4, 0 }, + { 0xb32, 0xb32, 0xb32, 0 }, + { 0xb91, 0xb91, 0xb91, 0 }, + { 0xbf2, 0xbf2, 0xbf2, 0 }, + { 0xc54, 0xc54, 0xc54, 0 }, + { 0xcb9, 0xcb9, 0xcb9, 0 }, + { 0xd1f, 0xd1f, 0xd1f, 0 }, + { 0xd88, 0xd88, 0xd88, 0 }, + { 0xdf2, 0xdf2, 0xdf2, 0 }, + { 0xe5e, 0xe5e, 0xe5e, 0 }, + { 0xecc, 0xecc, 0xecc, 0 }, + { 0xf3c, 0xf3c, 0xf3c, 0 }, + { 0xfad, 0xfad, 0xfad, 0 }, + { 0x1021, 0x1021, 0x1021, 0 }, + { 0x1096, 0x1096, 0x1096, 0 }, + { 0x110e, 0x110e, 0x110e, 0 }, + { 0x1187, 0x1187, 0x1187, 0 }, + { 0x1203, 0x1203, 0x1203, 0 }, + { 0x1280, 0x1280, 0x1280, 0 }, + { 0x12ff, 0x12ff, 0x12ff, 0 }, + { 0x1380, 0x1380, 0x1380, 0 }, + { 0x1404, 0x1404, 0x1404, 0 }, + { 0x1489, 0x1489, 0x1489, 0 }, + { 0x1510, 0x1510, 0x1510, 0 }, + { 0x1599, 0x1599, 0x1599, 0 }, + { 0x1624, 0x1624, 0x1624, 0 }, + { 0x16b2, 0x16b2, 0x16b2, 0 }, + { 0x1741, 0x1741, 0x1741, 0 }, + { 0x17d2, 0x17d2, 0x17d2, 0 }, + { 0x1865, 0x1865, 0x1865, 0 }, + { 0x18fb, 0x18fb, 0x18fb, 0 }, + { 0x1992, 0x1992, 0x1992, 0 }, + { 0x1a2c, 0x1a2c, 0x1a2c, 0 }, + { 0x1ac8, 0x1ac8, 0x1ac8, 0 }, + { 0x1b65, 0x1b65, 0x1b65, 0 }, + { 0x1c05, 0x1c05, 0x1c05, 0 }, + { 0x1ca7, 0x1ca7, 0x1ca7, 0 }, + { 0x1d4b, 0x1d4b, 0x1d4b, 0 }, + { 0x1df1, 0x1df1, 0x1df1, 0 }, + { 0x1e99, 0x1e99, 0x1e99, 0 }, + { 0x1f44, 0x1f44, 0x1f44, 0 }, + { 0x1ff0, 0x1ff0, 0x1ff0, 0 }, + { 0x209f, 0x209f, 0x209f, 0 }, + { 0x2150, 0x2150, 0x2150, 0 }, + { 0x2203, 0x2203, 0x2203, 0 }, + { 0x22b8, 0x22b8, 0x22b8, 0 }, + { 0x2370, 0x2370, 0x2370, 0 }, + { 0x2429, 0x2429, 0x2429, 0 }, + { 0x24e5, 0x24e5, 0x24e5, 0 }, + { 0x25a3, 0x25a3, 0x25a3, 0 }, + { 0x2663, 0x2663, 0x2663, 0 }, + { 0x2726, 0x2726, 0x2726, 0 }, + { 0x27ea, 0x27ea, 0x27ea, 0 }, + { 0x28b1, 0x28b1, 0x28b1, 0 }, + { 0x297a, 0x297a, 0x297a, 0 }, + { 0x2a45, 0x2a45, 0x2a45, 0 }, + { 0x2b13, 0x2b13, 0x2b13, 0 }, + { 0x2be3, 0x2be3, 0x2be3, 0 }, + { 0x2cb5, 0x2cb5, 0x2cb5, 0 }, + { 0x2d89, 0x2d89, 0x2d89, 0 }, + { 0x2e60, 0x2e60, 0x2e60, 0 }, + { 0x2f39, 0x2f39, 0x2f39, 0 }, + { 0x3014, 0x3014, 0x3014, 0 }, + { 0x30f2, 0x30f2, 0x30f2, 0 }, + { 0x31d2, 0x31d2, 0x31d2, 0 }, + { 0x32b4, 0x32b4, 0x32b4, 0 }, + { 0x3398, 0x3398, 0x3398, 0 }, + { 0x347f, 0x347f, 0x347f, 0 }, + { 0x3569, 0x3569, 0x3569, 0 }, + { 0x3654, 0x3654, 0x3654, 0 }, + { 0x3742, 0x3742, 0x3742, 0 }, + { 0x3832, 0x3832, 0x3832, 0 }, + { 0x3925, 0x3925, 0x3925, 0 }, + { 0x3a1a, 0x3a1a, 0x3a1a, 0 }, + { 0x3b11, 0x3b11, 0x3b11, 0 }, + { 0x3c0b, 0x3c0b, 0x3c0b, 0 }, + { 0x3d07, 0x3d07, 0x3d07, 0 }, + { 0x3e05, 0x3e05, 0x3e05, 0 }, + { 0x3f06, 0x3f06, 0x3f06, 0 }, + { 0x400a, 0x400a, 0x400a, 0 }, + { 0x410f, 0x410f, 0x410f, 0 }, + { 0x4218, 0x4218, 0x4218, 0 }, + { 0x4322, 0x4322, 0x4322, 0 }, + { 0x442f, 0x442f, 0x442f, 0 }, + { 0x453f, 0x453f, 0x453f, 0 }, + { 0x4650, 0x4650, 0x4650, 0 }, + { 0x4765, 0x4765, 0x4765, 0 }, + { 0x487c, 0x487c, 0x487c, 0 }, + { 0x4995, 0x4995, 0x4995, 0 }, + { 0x4ab1, 0x4ab1, 0x4ab1, 0 }, + { 0x4bcf, 0x4bcf, 0x4bcf, 0 }, + { 0x4cf0, 0x4cf0, 0x4cf0, 0 }, + { 0x4e13, 0x4e13, 0x4e13, 0 }, + { 0x4f39, 0x4f39, 0x4f39, 0 }, + { 0x5061, 0x5061, 0x5061, 0 }, + { 0x518b, 0x518b, 0x518b, 0 }, + { 0x52b9, 0x52b9, 0x52b9, 0 }, + { 0x53e8, 0x53e8, 0x53e8, 0 }, + { 0x551b, 0x551b, 0x551b, 0 }, + { 0x5650, 0x5650, 0x5650, 0 }, + { 0x5787, 0x5787, 0x5787, 0 }, + { 0x58c1, 0x58c1, 0x58c1, 0 }, + { 0x59fd, 0x59fd, 0x59fd, 0 }, + { 0x5b3c, 0x5b3c, 0x5b3c, 0 }, + { 0x5c7e, 0x5c7e, 0x5c7e, 0 }, + { 0x5dc2, 0x5dc2, 0x5dc2, 0 }, + { 0x5f09, 0x5f09, 0x5f09, 0 }, + { 0x6052, 0x6052, 0x6052, 0 }, + { 0x619e, 0x619e, 0x619e, 0 }, + { 0x62ec, 0x62ec, 0x62ec, 0 }, + { 0x643d, 0x643d, 0x643d, 0 }, + { 0x6591, 0x6591, 0x6591, 0 }, + { 0x66e7, 0x66e7, 0x66e7, 0 }, + { 0x6840, 0x6840, 0x6840, 0 }, + { 0x699b, 0x699b, 0x699b, 0 }, + { 0x6afa, 0x6afa, 0x6afa, 0 }, + { 0x6c5a, 0x6c5a, 0x6c5a, 0 }, + { 0x6dbe, 0x6dbe, 0x6dbe, 0 }, + { 0x6f24, 0x6f24, 0x6f24, 0 }, + { 0x708c, 0x708c, 0x708c, 0 }, + { 0x71f8, 0x71f8, 0x71f8, 0 }, + { 0x7366, 0x7366, 0x7366, 0 }, + { 0x74d6, 0x74d6, 0x74d6, 0 }, + { 0x764a, 0x764a, 0x764a, 0 }, + { 0x77c0, 0x77c0, 0x77c0, 0 }, + { 0x7938, 0x7938, 0x7938, 0 }, + { 0x7ab4, 0x7ab4, 0x7ab4, 0 }, + { 0x7c32, 0x7c32, 0x7c32, 0 }, + { 0x7db3, 0x7db3, 0x7db3, 0 }, + { 0x7f36, 0x7f36, 0x7f36, 0 }, + { 0x80bc, 0x80bc, 0x80bc, 0 }, + { 0x8245, 0x8245, 0x8245, 0 }, + { 0x83d1, 0x83d1, 0x83d1, 0 }, + { 0x855f, 0x855f, 0x855f, 0 }, + { 0x86f0, 0x86f0, 0x86f0, 0 }, + { 0x8884, 0x8884, 0x8884, 0 }, + { 0x8a1a, 0x8a1a, 0x8a1a, 0 }, + { 0x8bb4, 0x8bb4, 0x8bb4, 0 }, + { 0x8d50, 0x8d50, 0x8d50, 0 }, + { 0x8eee, 0x8eee, 0x8eee, 0 }, + { 0x9090, 0x9090, 0x9090, 0 }, + { 0x9234, 0x9234, 0x9234, 0 }, + { 0x93db, 0x93db, 0x93db, 0 }, + { 0x9585, 0x9585, 0x9585, 0 }, + { 0x9732, 0x9732, 0x9732, 0 }, + { 0x98e1, 0x98e1, 0x98e1, 0 }, + { 0x9a93, 0x9a93, 0x9a93, 0 }, + { 0x9c48, 0x9c48, 0x9c48, 0 }, + { 0x9e00, 0x9e00, 0x9e00, 0 }, + { 0x9fbb, 0x9fbb, 0x9fbb, 0 }, + { 0xa178, 0xa178, 0xa178, 0 }, + { 0xa338, 0xa338, 0xa338, 0 }, + { 0xa4fb, 0xa4fb, 0xa4fb, 0 }, + { 0xa6c1, 0xa6c1, 0xa6c1, 0 }, + { 0xa88a, 0xa88a, 0xa88a, 0 }, + { 0xaa56, 0xaa56, 0xaa56, 0 }, + { 0xac24, 0xac24, 0xac24, 0 }, + { 0xadf5, 0xadf5, 0xadf5, 0 }, + { 0xafc9, 0xafc9, 0xafc9, 0 }, + { 0xb1a0, 0xb1a0, 0xb1a0, 0 }, + { 0xb37a, 0xb37a, 0xb37a, 0 }, + { 0xb557, 0xb557, 0xb557, 0 }, + { 0xb736, 0xb736, 0xb736, 0 }, + { 0xb919, 0xb919, 0xb919, 0 }, + { 0xbafe, 0xbafe, 0xbafe, 0 }, + { 0xbce6, 0xbce6, 0xbce6, 0 }, + { 0xbed2, 0xbed2, 0xbed2, 0 }, + { 0xc0c0, 0xc0c0, 0xc0c0, 0 }, + { 0xc2b0, 0xc2b0, 0xc2b0, 0 }, + { 0xc4a4, 0xc4a4, 0xc4a4, 0 }, + { 0xc69b, 0xc69b, 0xc69b, 0 }, + { 0xc895, 0xc895, 0xc895, 0 }, + { 0xca91, 0xca91, 0xca91, 0 }, + { 0xcc91, 0xcc91, 0xcc91, 0 }, + { 0xce93, 0xce93, 0xce93, 0 }, + { 0xd098, 0xd098, 0xd098, 0 }, + { 0xd2a1, 0xd2a1, 0xd2a1, 0 }, + { 0xd4ac, 0xd4ac, 0xd4ac, 0 }, + { 0xd6ba, 0xd6ba, 0xd6ba, 0 }, + { 0xd8cb, 0xd8cb, 0xd8cb, 0 }, + { 0xdadf, 0xdadf, 0xdadf, 0 }, + { 0xdcf7, 0xdcf7, 0xdcf7, 0 }, + { 0xdf11, 0xdf11, 0xdf11, 0 }, + { 0xe12e, 0xe12e, 0xe12e, 0 }, + { 0xe34e, 0xe34e, 0xe34e, 0 }, + { 0xe571, 0xe571, 0xe571, 0 }, + { 0xe796, 0xe796, 0xe796, 0 }, + { 0xe9bf, 0xe9bf, 0xe9bf, 0 }, + { 0xebeb, 0xebeb, 0xebeb, 0 }, + { 0xee1a, 0xee1a, 0xee1a, 0 }, + { 0xf04c, 0xf04c, 0xf04c, 0 }, + { 0xf281, 0xf281, 0xf281, 0 }, + { 0xf4b9, 0xf4b9, 0xf4b9, 0 }, + { 0xf6f4, 0xf6f4, 0xf6f4, 0 }, + { 0xf932, 0xf932, 0xf932, 0 }, + { 0xfb73, 0xfb73, 0xfb73, 0 }, + { 0xfdb7, 0xfdb7, 0xfdb7, 0 }, + { 0xffff, 0xffff, 0xffff, 0 }, +}; + +const struct vkms_color_lut srgb_eotf = { + .base = srgb_array, + .lut_length = LUT_SIZE, + .channel_value2index_ratio = 0xff00ffll +}; +EXPORT_SYMBOL(srgb_eotf); + +static struct drm_color_lut srgb_inv_array[LUT_SIZE] = { + { 0x0, 0x0, 0x0, 0 }, + { 0xcc2, 0xcc2, 0xcc2, 0 }, + { 0x15be, 0x15be, 0x15be, 0 }, + { 0x1c56, 0x1c56, 0x1c56, 0 }, + { 0x21bd, 0x21bd, 0x21bd, 0 }, + { 0x2666, 0x2666, 0x2666, 0 }, + { 0x2a8a, 0x2a8a, 0x2a8a, 0 }, + { 0x2e4c, 0x2e4c, 0x2e4c, 0 }, + { 0x31c0, 0x31c0, 0x31c0, 0 }, + { 0x34f6, 0x34f6, 0x34f6, 0 }, + { 0x37f9, 0x37f9, 0x37f9, 0 }, + { 0x3acf, 0x3acf, 0x3acf, 0 }, + { 0x3d80, 0x3d80, 0x3d80, 0 }, + { 0x4010, 0x4010, 0x4010, 0 }, + { 0x4284, 0x4284, 0x4284, 0 }, + { 0x44dd, 0x44dd, 0x44dd, 0 }, + { 0x4720, 0x4720, 0x4720, 0 }, + { 0x494e, 0x494e, 0x494e, 0 }, + { 0x4b69, 0x4b69, 0x4b69, 0 }, + { 0x4d73, 0x4d73, 0x4d73, 0 }, + { 0x4f6e, 0x4f6e, 0x4f6e, 0 }, + { 0x5159, 0x5159, 0x5159, 0 }, + { 0x5337, 0x5337, 0x5337, 0 }, + { 0x5509, 0x5509, 0x5509, 0 }, + { 0x56cf, 0x56cf, 0x56cf, 0 }, + { 0x588a, 0x588a, 0x588a, 0 }, + { 0x5a3b, 0x5a3b, 0x5a3b, 0 }, + { 0x5be2, 0x5be2, 0x5be2, 0 }, + { 0x5d80, 0x5d80, 0x5d80, 0 }, + { 0x5f16, 0x5f16, 0x5f16, 0 }, + { 0x60a4, 0x60a4, 0x60a4, 0 }, + { 0x6229, 0x6229, 0x6229, 0 }, + { 0x63a8, 0x63a8, 0x63a8, 0 }, + { 0x6520, 0x6520, 0x6520, 0 }, + { 0x6691, 0x6691, 0x6691, 0 }, + { 0x67fc, 0x67fc, 0x67fc, 0 }, + { 0x6961, 0x6961, 0x6961, 0 }, + { 0x6ac0, 0x6ac0, 0x6ac0, 0 }, + { 0x6c19, 0x6c19, 0x6c19, 0 }, + { 0x6d6e, 0x6d6e, 0x6d6e, 0 }, + { 0x6ebd, 0x6ebd, 0x6ebd, 0 }, + { 0x7008, 0x7008, 0x7008, 0 }, + { 0x714d, 0x714d, 0x714d, 0 }, + { 0x728f, 0x728f, 0x728f, 0 }, + { 0x73cc, 0x73cc, 0x73cc, 0 }, + { 0x7504, 0x7504, 0x7504, 0 }, + { 0x7639, 0x7639, 0x7639, 0 }, + { 0x776a, 0x776a, 0x776a, 0 }, + { 0x7897, 0x7897, 0x7897, 0 }, + { 0x79c1, 0x79c1, 0x79c1, 0 }, + { 0x7ae7, 0x7ae7, 0x7ae7, 0 }, + { 0x7c09, 0x7c09, 0x7c09, 0 }, + { 0x7d28, 0x7d28, 0x7d28, 0 }, + { 0x7e44, 0x7e44, 0x7e44, 0 }, + { 0x7f5d, 0x7f5d, 0x7f5d, 0 }, + { 0x8073, 0x8073, 0x8073, 0 }, + { 0x8186, 0x8186, 0x8186, 0 }, + { 0x8296, 0x8296, 0x8296, 0 }, + { 0x83a4, 0x83a4, 0x83a4, 0 }, + { 0x84ae, 0x84ae, 0x84ae, 0 }, + { 0x85b6, 0x85b6, 0x85b6, 0 }, + { 0x86bc, 0x86bc, 0x86bc, 0 }, + { 0x87bf, 0x87bf, 0x87bf, 0 }, + { 0x88bf, 0x88bf, 0x88bf, 0 }, + { 0x89be, 0x89be, 0x89be, 0 }, + { 0x8ab9, 0x8ab9, 0x8ab9, 0 }, + { 0x8bb3, 0x8bb3, 0x8bb3, 0 }, + { 0x8cab, 0x8cab, 0x8cab, 0 }, + { 0x8da0, 0x8da0, 0x8da0, 0 }, + { 0x8e93, 0x8e93, 0x8e93, 0 }, + { 0x8f84, 0x8f84, 0x8f84, 0 }, + { 0x9073, 0x9073, 0x9073, 0 }, + { 0x9161, 0x9161, 0x9161, 0 }, + { 0x924c, 0x924c, 0x924c, 0 }, + { 0x9335, 0x9335, 0x9335, 0 }, + { 0x941d, 0x941d, 0x941d, 0 }, + { 0x9503, 0x9503, 0x9503, 0 }, + { 0x95e7, 0x95e7, 0x95e7, 0 }, + { 0x96c9, 0x96c9, 0x96c9, 0 }, + { 0x97aa, 0x97aa, 0x97aa, 0 }, + { 0x9889, 0x9889, 0x9889, 0 }, + { 0x9966, 0x9966, 0x9966, 0 }, + { 0x9a42, 0x9a42, 0x9a42, 0 }, + { 0x9b1c, 0x9b1c, 0x9b1c, 0 }, + { 0x9bf5, 0x9bf5, 0x9bf5, 0 }, + { 0x9ccc, 0x9ccc, 0x9ccc, 0 }, + { 0x9da1, 0x9da1, 0x9da1, 0 }, + { 0x9e76, 0x9e76, 0x9e76, 0 }, + { 0x9f49, 0x9f49, 0x9f49, 0 }, + { 0xa01a, 0xa01a, 0xa01a, 0 }, + { 0xa0ea, 0xa0ea, 0xa0ea, 0 }, + { 0xa1b9, 0xa1b9, 0xa1b9, 0 }, + { 0xa286, 0xa286, 0xa286, 0 }, + { 0xa352, 0xa352, 0xa352, 0 }, + { 0xa41d, 0xa41d, 0xa41d, 0 }, + { 0xa4e7, 0xa4e7, 0xa4e7, 0 }, + { 0xa5af, 0xa5af, 0xa5af, 0 }, + { 0xa676, 0xa676, 0xa676, 0 }, + { 0xa73c, 0xa73c, 0xa73c, 0 }, + { 0xa801, 0xa801, 0xa801, 0 }, + { 0xa8c5, 0xa8c5, 0xa8c5, 0 }, + { 0xa987, 0xa987, 0xa987, 0 }, + { 0xaa48, 0xaa48, 0xaa48, 0 }, + { 0xab09, 0xab09, 0xab09, 0 }, + { 0xabc8, 0xabc8, 0xabc8, 0 }, + { 0xac86, 0xac86, 0xac86, 0 }, + { 0xad43, 0xad43, 0xad43, 0 }, + { 0xadff, 0xadff, 0xadff, 0 }, + { 0xaeba, 0xaeba, 0xaeba, 0 }, + { 0xaf74, 0xaf74, 0xaf74, 0 }, + { 0xb02d, 0xb02d, 0xb02d, 0 }, + { 0xb0e5, 0xb0e5, 0xb0e5, 0 }, + { 0xb19c, 0xb19c, 0xb19c, 0 }, + { 0xb252, 0xb252, 0xb252, 0 }, + { 0xb307, 0xb307, 0xb307, 0 }, + { 0xb3bb, 0xb3bb, 0xb3bb, 0 }, + { 0xb46f, 0xb46f, 0xb46f, 0 }, + { 0xb521, 0xb521, 0xb521, 0 }, + { 0xb5d3, 0xb5d3, 0xb5d3, 0 }, + { 0xb683, 0xb683, 0xb683, 0 }, + { 0xb733, 0xb733, 0xb733, 0 }, + { 0xb7e2, 0xb7e2, 0xb7e2, 0 }, + { 0xb890, 0xb890, 0xb890, 0 }, + { 0xb93d, 0xb93d, 0xb93d, 0 }, + { 0xb9ea, 0xb9ea, 0xb9ea, 0 }, + { 0xba96, 0xba96, 0xba96, 0 }, + { 0xbb40, 0xbb40, 0xbb40, 0 }, + { 0xbbea, 0xbbea, 0xbbea, 0 }, + { 0xbc94, 0xbc94, 0xbc94, 0 }, + { 0xbd3c, 0xbd3c, 0xbd3c, 0 }, + { 0xbde4, 0xbde4, 0xbde4, 0 }, + { 0xbe8b, 0xbe8b, 0xbe8b, 0 }, + { 0xbf31, 0xbf31, 0xbf31, 0 }, + { 0xbfd7, 0xbfd7, 0xbfd7, 0 }, + { 0xc07b, 0xc07b, 0xc07b, 0 }, + { 0xc120, 0xc120, 0xc120, 0 }, + { 0xc1c3, 0xc1c3, 0xc1c3, 0 }, + { 0xc266, 0xc266, 0xc266, 0 }, + { 0xc308, 0xc308, 0xc308, 0 }, + { 0xc3a9, 0xc3a9, 0xc3a9, 0 }, + { 0xc449, 0xc449, 0xc449, 0 }, + { 0xc4e9, 0xc4e9, 0xc4e9, 0 }, + { 0xc589, 0xc589, 0xc589, 0 }, + { 0xc627, 0xc627, 0xc627, 0 }, + { 0xc6c5, 0xc6c5, 0xc6c5, 0 }, + { 0xc763, 0xc763, 0xc763, 0 }, + { 0xc7ff, 0xc7ff, 0xc7ff, 0 }, + { 0xc89b, 0xc89b, 0xc89b, 0 }, + { 0xc937, 0xc937, 0xc937, 0 }, + { 0xc9d2, 0xc9d2, 0xc9d2, 0 }, + { 0xca6c, 0xca6c, 0xca6c, 0 }, + { 0xcb06, 0xcb06, 0xcb06, 0 }, + { 0xcb9f, 0xcb9f, 0xcb9f, 0 }, + { 0xcc37, 0xcc37, 0xcc37, 0 }, + { 0xcccf, 0xcccf, 0xcccf, 0 }, + { 0xcd66, 0xcd66, 0xcd66, 0 }, + { 0xcdfd, 0xcdfd, 0xcdfd, 0 }, + { 0xce93, 0xce93, 0xce93, 0 }, + { 0xcf29, 0xcf29, 0xcf29, 0 }, + { 0xcfbe, 0xcfbe, 0xcfbe, 0 }, + { 0xd053, 0xd053, 0xd053, 0 }, + { 0xd0e7, 0xd0e7, 0xd0e7, 0 }, + { 0xd17a, 0xd17a, 0xd17a, 0 }, + { 0xd20d, 0xd20d, 0xd20d, 0 }, + { 0xd2a0, 0xd2a0, 0xd2a0, 0 }, + { 0xd331, 0xd331, 0xd331, 0 }, + { 0xd3c3, 0xd3c3, 0xd3c3, 0 }, + { 0xd454, 0xd454, 0xd454, 0 }, + { 0xd4e4, 0xd4e4, 0xd4e4, 0 }, + { 0xd574, 0xd574, 0xd574, 0 }, + { 0xd603, 0xd603, 0xd603, 0 }, + { 0xd692, 0xd692, 0xd692, 0 }, + { 0xd720, 0xd720, 0xd720, 0 }, + { 0xd7ae, 0xd7ae, 0xd7ae, 0 }, + { 0xd83c, 0xd83c, 0xd83c, 0 }, + { 0xd8c9, 0xd8c9, 0xd8c9, 0 }, + { 0xd955, 0xd955, 0xd955, 0 }, + { 0xd9e1, 0xd9e1, 0xd9e1, 0 }, + { 0xda6d, 0xda6d, 0xda6d, 0 }, + { 0xdaf8, 0xdaf8, 0xdaf8, 0 }, + { 0xdb83, 0xdb83, 0xdb83, 0 }, + { 0xdc0d, 0xdc0d, 0xdc0d, 0 }, + { 0xdc97, 0xdc97, 0xdc97, 0 }, + { 0xdd20, 0xdd20, 0xdd20, 0 }, + { 0xdda9, 0xdda9, 0xdda9, 0 }, + { 0xde31, 0xde31, 0xde31, 0 }, + { 0xdeb9, 0xdeb9, 0xdeb9, 0 }, + { 0xdf41, 0xdf41, 0xdf41, 0 }, + { 0xdfc8, 0xdfc8, 0xdfc8, 0 }, + { 0xe04f, 0xe04f, 0xe04f, 0 }, + { 0xe0d5, 0xe0d5, 0xe0d5, 0 }, + { 0xe15b, 0xe15b, 0xe15b, 0 }, + { 0xe1e0, 0xe1e0, 0xe1e0, 0 }, + { 0xe266, 0xe266, 0xe266, 0 }, + { 0xe2ea, 0xe2ea, 0xe2ea, 0 }, + { 0xe36f, 0xe36f, 0xe36f, 0 }, + { 0xe3f3, 0xe3f3, 0xe3f3, 0 }, + { 0xe476, 0xe476, 0xe476, 0 }, + { 0xe4f9, 0xe4f9, 0xe4f9, 0 }, + { 0xe57c, 0xe57c, 0xe57c, 0 }, + { 0xe5fe, 0xe5fe, 0xe5fe, 0 }, + { 0xe680, 0xe680, 0xe680, 0 }, + { 0xe702, 0xe702, 0xe702, 0 }, + { 0xe783, 0xe783, 0xe783, 0 }, + { 0xe804, 0xe804, 0xe804, 0 }, + { 0xe884, 0xe884, 0xe884, 0 }, + { 0xe905, 0xe905, 0xe905, 0 }, + { 0xe984, 0xe984, 0xe984, 0 }, + { 0xea04, 0xea04, 0xea04, 0 }, + { 0xea83, 0xea83, 0xea83, 0 }, + { 0xeb02, 0xeb02, 0xeb02, 0 }, + { 0xeb80, 0xeb80, 0xeb80, 0 }, + { 0xebfe, 0xebfe, 0xebfe, 0 }, + { 0xec7b, 0xec7b, 0xec7b, 0 }, + { 0xecf9, 0xecf9, 0xecf9, 0 }, + { 0xed76, 0xed76, 0xed76, 0 }, + { 0xedf2, 0xedf2, 0xedf2, 0 }, + { 0xee6f, 0xee6f, 0xee6f, 0 }, + { 0xeeeb, 0xeeeb, 0xeeeb, 0 }, + { 0xef66, 0xef66, 0xef66, 0 }, + { 0xefe2, 0xefe2, 0xefe2, 0 }, + { 0xf05d, 0xf05d, 0xf05d, 0 }, + { 0xf0d7, 0xf0d7, 0xf0d7, 0 }, + { 0xf152, 0xf152, 0xf152, 0 }, + { 0xf1cc, 0xf1cc, 0xf1cc, 0 }, + { 0xf245, 0xf245, 0xf245, 0 }, + { 0xf2bf, 0xf2bf, 0xf2bf, 0 }, + { 0xf338, 0xf338, 0xf338, 0 }, + { 0xf3b0, 0xf3b0, 0xf3b0, 0 }, + { 0xf429, 0xf429, 0xf429, 0 }, + { 0xf4a1, 0xf4a1, 0xf4a1, 0 }, + { 0xf519, 0xf519, 0xf519, 0 }, + { 0xf590, 0xf590, 0xf590, 0 }, + { 0xf608, 0xf608, 0xf608, 0 }, + { 0xf67e, 0xf67e, 0xf67e, 0 }, + { 0xf6f5, 0xf6f5, 0xf6f5, 0 }, + { 0xf76b, 0xf76b, 0xf76b, 0 }, + { 0xf7e1, 0xf7e1, 0xf7e1, 0 }, + { 0xf857, 0xf857, 0xf857, 0 }, + { 0xf8cd, 0xf8cd, 0xf8cd, 0 }, + { 0xf942, 0xf942, 0xf942, 0 }, + { 0xf9b7, 0xf9b7, 0xf9b7, 0 }, + { 0xfa2b, 0xfa2b, 0xfa2b, 0 }, + { 0xfaa0, 0xfaa0, 0xfaa0, 0 }, + { 0xfb14, 0xfb14, 0xfb14, 0 }, + { 0xfb88, 0xfb88, 0xfb88, 0 }, + { 0xfbfb, 0xfbfb, 0xfbfb, 0 }, + { 0xfc6e, 0xfc6e, 0xfc6e, 0 }, + { 0xfce1, 0xfce1, 0xfce1, 0 }, + { 0xfd54, 0xfd54, 0xfd54, 0 }, + { 0xfdc6, 0xfdc6, 0xfdc6, 0 }, + { 0xfe39, 0xfe39, 0xfe39, 0 }, + { 0xfeaa, 0xfeaa, 0xfeaa, 0 }, + { 0xff1c, 0xff1c, 0xff1c, 0 }, + { 0xff8d, 0xff8d, 0xff8d, 0 }, + { 0xffff, 0xffff, 0xffff, 0 }, +}; + +const struct vkms_color_lut srgb_inv_eotf = { + .base = srgb_inv_array, + .lut_length = LUT_SIZE, + .channel_value2index_ratio = 0xff00ffll +}; +EXPORT_SYMBOL(srgb_inv_eotf); diff --git a/drivers/gpu/drm/vkms/vkms_luts.h b/drivers/gpu/drm/vkms/vkms_luts.h new file mode 100644 index 000000000..925a4a7b8 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_luts.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _VKMS_LUTS_H_ +#define _VKMS_LUTS_H_ + +#define LUT_SIZE 256 + +extern const struct vkms_color_lut linear_eotf; +extern const struct vkms_color_lut srgb_eotf; +extern const struct vkms_color_lut srgb_inv_eotf; + +#endif /* _VKMS_LUTS_H_ */ diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c new file mode 100644 index 000000000..86ce07a61 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_output.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include "vkms_config.h" +#include "vkms_connector.h" +#include "vkms_drv.h" +#include +#include + +int vkms_output_init(struct vkms_device *vkmsdev) +{ + struct drm_device *dev = &vkmsdev->drm; + struct vkms_config_plane *plane_cfg; + struct vkms_config_crtc *crtc_cfg; + struct vkms_config_encoder *encoder_cfg; + struct vkms_config_connector *connector_cfg; + int ret; + int writeback; + + if (!vkms_config_is_valid(vkmsdev->config)) + return -EINVAL; + + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + plane_cfg->plane = vkms_plane_init(vkmsdev, plane_cfg); + if (IS_ERR(plane_cfg->plane)) { + DRM_DEV_ERROR(dev->dev, "Failed to init vkms plane\n"); + return PTR_ERR(plane_cfg->plane); + } + } + + vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) { + struct vkms_config_plane *primary, *cursor; + + primary = vkms_config_crtc_primary_plane(vkmsdev->config, crtc_cfg); + cursor = vkms_config_crtc_cursor_plane(vkmsdev->config, crtc_cfg); + + crtc_cfg->crtc = vkms_crtc_init(dev, &primary->plane->base, + cursor ? &cursor->plane->base : NULL); + if (IS_ERR(crtc_cfg->crtc)) { + DRM_ERROR("Failed to allocate CRTC\n"); + return PTR_ERR(crtc_cfg->crtc); + } + + /* Initialize the writeback component */ + if (vkms_config_crtc_get_writeback(crtc_cfg)) { + writeback = vkms_enable_writeback_connector(vkmsdev, crtc_cfg->crtc); + if (writeback) + DRM_ERROR("Failed to init writeback connector\n"); + } + } + + vkms_config_for_each_plane(vkmsdev->config, plane_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) { + plane_cfg->plane->base.possible_crtcs |= + drm_crtc_mask(&possible_crtc->crtc->crtc); + } + } + + vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg) { + struct vkms_config_crtc *possible_crtc; + unsigned long idx = 0; + + encoder_cfg->encoder = drmm_kzalloc(dev, sizeof(*encoder_cfg->encoder), GFP_KERNEL); + if (!encoder_cfg->encoder) { + DRM_ERROR("Failed to allocate encoder\n"); + return -ENOMEM; + } + ret = drmm_encoder_init(dev, encoder_cfg->encoder, NULL, + DRM_MODE_ENCODER_VIRTUAL, NULL); + if (ret) { + DRM_ERROR("Failed to init encoder\n"); + return ret; + } + + encoder_cfg->encoder->possible_clones |= + drm_encoder_mask(encoder_cfg->encoder); + + vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) { + encoder_cfg->encoder->possible_crtcs |= + drm_crtc_mask(&possible_crtc->crtc->crtc); + + if (vkms_config_crtc_get_writeback(possible_crtc)) { + struct drm_encoder *wb_encoder = + &possible_crtc->crtc->wb_encoder; + + encoder_cfg->encoder->possible_clones |= + drm_encoder_mask(wb_encoder); + wb_encoder->possible_clones |= + drm_encoder_mask(encoder_cfg->encoder); + } + } + } + + vkms_config_for_each_connector(vkmsdev->config, connector_cfg) { + struct vkms_config_encoder *possible_encoder; + unsigned long idx = 0; + + connector_cfg->connector = vkms_connector_init(vkmsdev); + if (IS_ERR(connector_cfg->connector)) { + DRM_ERROR("Failed to init connector\n"); + return PTR_ERR(connector_cfg->connector); + } + + vkms_config_connector_for_each_possible_encoder(connector_cfg, + idx, + possible_encoder) { + ret = drm_connector_attach_encoder(&connector_cfg->connector->base, + possible_encoder->encoder); + if (ret) { + DRM_ERROR("Failed to attach connector to encoder\n"); + return ret; + } + } + } + + drm_mode_config_reset(dev); + + return 0; +} diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c new file mode 100644 index 000000000..68cb2a333 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_plane.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include "vkms_config.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vkms_drv.h" +#include "vkms_formats.h" + +static const u32 vkms_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_XRGB16161616, + DRM_FORMAT_XBGR16161616, + DRM_FORMAT_ARGB16161616, + DRM_FORMAT_ABGR16161616, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_NV21, + DRM_FORMAT_NV61, + DRM_FORMAT_NV42, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, + DRM_FORMAT_YUV444, + DRM_FORMAT_YVU420, + DRM_FORMAT_YVU422, + DRM_FORMAT_YVU444, + DRM_FORMAT_P010, + DRM_FORMAT_P012, + DRM_FORMAT_P016, + DRM_FORMAT_R1, + DRM_FORMAT_R2, + DRM_FORMAT_R4, + DRM_FORMAT_R8, +}; + +static struct drm_plane_state * +vkms_plane_duplicate_state(struct drm_plane *plane) +{ + struct vkms_plane_state *vkms_state; + struct vkms_frame_info *frame_info; + + vkms_state = kzalloc_obj(*vkms_state); + if (!vkms_state) + return NULL; + + frame_info = kzalloc_obj(*frame_info); + if (!frame_info) { + DRM_DEBUG_KMS("Couldn't allocate frame_info\n"); + kfree(vkms_state); + return NULL; + } + + vkms_state->frame_info = frame_info; + + __drm_gem_duplicate_shadow_plane_state(plane, &vkms_state->base); + + return &vkms_state->base.base; +} + +static void vkms_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct vkms_plane_state *vkms_state = to_vkms_plane_state(old_state); + struct drm_crtc *crtc = vkms_state->base.base.crtc; + + if (crtc && vkms_state->frame_info->fb) { + /* dropping the reference we acquired in + * vkms_primary_plane_update() + */ + if (drm_framebuffer_read_refcount(vkms_state->frame_info->fb)) + drm_framebuffer_put(vkms_state->frame_info->fb); + } + + kfree(vkms_state->frame_info); + vkms_state->frame_info = NULL; + + __drm_gem_destroy_shadow_plane_state(&vkms_state->base); + kfree(vkms_state); +} + +static void vkms_plane_reset(struct drm_plane *plane) +{ + struct vkms_plane_state *vkms_state; + + if (plane->state) { + vkms_plane_destroy_state(plane, plane->state); + plane->state = NULL; /* must be set to NULL here */ + } + + vkms_state = kzalloc_obj(*vkms_state); + if (!vkms_state) { + DRM_ERROR("Cannot allocate vkms_plane_state\n"); + return; + } + + __drm_gem_reset_shadow_plane(plane, &vkms_state->base); +} + +static const struct drm_plane_funcs vkms_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = vkms_plane_reset, + .atomic_duplicate_state = vkms_plane_duplicate_state, + .atomic_destroy_state = vkms_plane_destroy_state, +}; + +static void vkms_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct vkms_plane_state *vkms_plane_state; + struct drm_shadow_plane_state *shadow_plane_state; + struct drm_framebuffer *fb = new_state->fb; + struct vkms_frame_info *frame_info; + u32 fmt; + + if (!new_state->crtc || !fb) + return; + + fmt = fb->format->format; + vkms_plane_state = to_vkms_plane_state(new_state); + shadow_plane_state = &vkms_plane_state->base; + + frame_info = vkms_plane_state->frame_info; + memcpy(&frame_info->src, &new_state->src, sizeof(struct drm_rect)); + memcpy(&frame_info->dst, &new_state->dst, sizeof(struct drm_rect)); + frame_info->fb = fb; + memcpy(&frame_info->map, &shadow_plane_state->data, sizeof(frame_info->map)); + drm_framebuffer_get(frame_info->fb); + frame_info->rotation = new_state->rotation; + + vkms_plane_state->pixel_read_line = get_pixel_read_line_function(fmt); + get_conversion_matrix_to_argb_u16(fmt, new_state->color_encoding, new_state->color_range, + &vkms_plane_state->conversion_matrix); +} + +static int vkms_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc_state *crtc_state; + int ret; + + if (!new_plane_state->fb || WARN_ON(!new_plane_state->crtc)) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, + new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); + if (ret != 0) + return ret; + + return 0; +} + +static int vkms_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_shadow_plane_state *shadow_plane_state; + struct drm_framebuffer *fb = state->fb; + int ret; + + if (!fb) + return 0; + + shadow_plane_state = to_drm_shadow_plane_state(state); + + ret = drm_gem_plane_helper_prepare_fb(plane, state); + if (ret) + return ret; + + return drm_gem_fb_vmap(fb, shadow_plane_state->map, shadow_plane_state->data); +} + +static void vkms_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_shadow_plane_state *shadow_plane_state; + struct drm_framebuffer *fb = state->fb; + + if (!fb) + return; + + shadow_plane_state = to_drm_shadow_plane_state(state); + + drm_gem_fb_vunmap(fb, shadow_plane_state->map); +} + +static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = { + .atomic_update = vkms_plane_atomic_update, + .atomic_check = vkms_plane_atomic_check, + .prepare_fb = vkms_prepare_fb, + .cleanup_fb = vkms_cleanup_fb, +}; + +struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev, + struct vkms_config_plane *plane_cfg) +{ + struct drm_device *dev = &vkmsdev->drm; + struct vkms_plane *plane; + + plane = drmm_universal_plane_alloc(dev, struct vkms_plane, base, 0, + &vkms_plane_funcs, + vkms_formats, ARRAY_SIZE(vkms_formats), + NULL, vkms_config_plane_get_type(plane_cfg), + NULL); + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(&plane->base, &vkms_plane_helper_funcs); + + drm_plane_create_rotation_property(&plane->base, DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK); + + drm_plane_create_color_properties(&plane->base, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + DRM_COLOR_YCBCR_BT601, + DRM_COLOR_YCBCR_FULL_RANGE); + + if (vkms_config_plane_get_default_pipeline(plane_cfg)) + vkms_initialize_colorops(&plane->base); + + return plane; +} diff --git a/drivers/gpu/drm/vkms/vkms_writeback.c b/drivers/gpu/drm/vkms/vkms_writeback.c new file mode 100644 index 000000000..ecf29a2c0 --- /dev/null +++ b/drivers/gpu/drm/vkms/vkms_writeback.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vkms_drv.h" +#include "vkms_formats.h" + +static const u32 vkms_wb_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XRGB16161616, + DRM_FORMAT_ARGB16161616, + DRM_FORMAT_RGB565 +}; + +static const struct drm_connector_funcs vkms_wb_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int vkms_wb_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) +{ + struct drm_connector_state *conn_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_crtc_state *crtc_state; + struct drm_framebuffer *fb; + const struct drm_display_mode *mode; + int ret; + + if (!conn_state->writeback_job || !conn_state->writeback_job->fb) + return 0; + + if (!conn_state->crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + mode = &crtc_state->mode; + + fb = conn_state->writeback_job->fb; + if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) { + DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n", + fb->width, fb->height); + return -EINVAL; + } + + ret = drm_atomic_helper_check_wb_connector_state(connector, state); + if (ret < 0) + return ret; + + return 0; +} + +static int vkms_wb_connector_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + return drm_add_modes_noedid(connector, dev->mode_config.max_width, + dev->mode_config.max_height); +} + +static int vkms_wb_prepare_job(struct drm_writeback_connector *wb_connector, + struct drm_writeback_job *job) +{ + struct vkms_writeback_job *vkmsjob; + int ret; + + if (!job->fb) + return 0; + + vkmsjob = kzalloc_obj(*vkmsjob); + if (!vkmsjob) + return -ENOMEM; + + ret = drm_gem_fb_vmap(job->fb, vkmsjob->wb_frame_info.map, vkmsjob->data); + if (ret) { + DRM_ERROR("vmap failed: %d\n", ret); + goto err_kfree; + } + + vkmsjob->wb_frame_info.fb = job->fb; + drm_framebuffer_get(vkmsjob->wb_frame_info.fb); + + job->priv = vkmsjob; + + return 0; + +err_kfree: + kfree(vkmsjob); + return ret; +} + +static void vkms_wb_cleanup_job(struct drm_writeback_connector *connector, + struct drm_writeback_job *job) +{ + struct vkms_writeback_job *vkmsjob = job->priv; + struct vkms_output *vkms_output = container_of(connector, + struct vkms_output, + wb_connector); + + if (!job->fb) + return; + + drm_gem_fb_vunmap(job->fb, vkmsjob->wb_frame_info.map); + + drm_framebuffer_put(vkmsjob->wb_frame_info.fb); + + vkms_set_composer(vkms_output, false); + kfree(vkmsjob); +} + +static void vkms_wb_atomic_commit(struct drm_connector *conn, + struct drm_atomic_commit *state) +{ + struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state, + conn); + struct vkms_output *output = drm_crtc_to_vkms_output(connector_state->crtc); + struct drm_writeback_connector *wb_conn = &output->wb_connector; + struct drm_connector_state *conn_state = wb_conn->base.state; + struct vkms_crtc_state *crtc_state = output->composer_state; + struct drm_framebuffer *fb = connector_state->writeback_job->fb; + u16 crtc_height = crtc_state->base.mode.vdisplay; + u16 crtc_width = crtc_state->base.mode.hdisplay; + struct vkms_writeback_job *active_wb; + struct vkms_frame_info *wb_frame_info; + u32 wb_format = fb->format->format; + + if (!conn_state) + return; + + vkms_set_composer(output, true); + + active_wb = conn_state->writeback_job->priv; + wb_frame_info = &active_wb->wb_frame_info; + + spin_lock_irq(&output->composer_lock); + crtc_state->active_writeback = active_wb; + crtc_state->wb_pending = true; + spin_unlock_irq(&output->composer_lock); + drm_writeback_queue_job(wb_conn, connector_state); + active_wb->pixel_write = get_pixel_write_function(wb_format); + drm_rect_init(&wb_frame_info->src, 0, 0, crtc_width, crtc_height); + drm_rect_init(&wb_frame_info->dst, 0, 0, crtc_width, crtc_height); +} + +static const struct drm_connector_helper_funcs vkms_wb_conn_helper_funcs = { + .get_modes = vkms_wb_connector_get_modes, + .prepare_writeback_job = vkms_wb_prepare_job, + .cleanup_writeback_job = vkms_wb_cleanup_job, + .atomic_commit = vkms_wb_atomic_commit, + .atomic_check = vkms_wb_atomic_check, +}; + +int vkms_enable_writeback_connector(struct vkms_device *vkmsdev, + struct vkms_output *vkms_output) +{ + struct drm_writeback_connector *wb = &vkms_output->wb_connector; + int ret; + + ret = drmm_encoder_init(&vkmsdev->drm, &vkms_output->wb_encoder, + NULL, DRM_MODE_ENCODER_VIRTUAL, NULL); + if (ret) + return ret; + vkms_output->wb_encoder.possible_crtcs |= drm_crtc_mask(&vkms_output->crtc); + vkms_output->wb_encoder.possible_clones |= + drm_encoder_mask(&vkms_output->wb_encoder); + + drm_connector_helper_add(&wb->base, &vkms_wb_conn_helper_funcs); + + return drmm_writeback_connector_init(&vkmsdev->drm, wb, + &vkms_wb_connector_funcs, + &vkms_output->wb_encoder, + vkms_wb_formats, + ARRAY_SIZE(vkms_wb_formats)); +} -- cgit v1.3.1