[RFC PATCH 3/3] drm/vkms: Add KUnit tests for stereo modes

From: Daniel Campos Ramos

Date: Fri Oct 09 2026 - 03:02:49 EST


Build a device from the default configuration with vkms_output_init(),
its connector reading an EDID that declares 3D in its HDMI
Vendor-Specific Data Block, and check that:

- the connector lists the stereo modes the EDID declares, frame packing,
top-and-bottom and side by side (half), and no other;
- the writeback connector accepts a buffer of the frame that holds both
eyes and refuses other sizes, for each of these layouts and for a 2D
mode.

Export vkms_output_init() for KUnit and select the DRM KUnit helpers.

Assisted-by: LLM
Signed-off-by: Daniel Campos Ramos <Capitain_Jack@xxxxxxxxx>
---
drivers/gpu/drm/vkms/Kconfig | 1 +
drivers/gpu/drm/vkms/tests/Makefile | 1 +
drivers/gpu/drm/vkms/tests/vkms_stereo_test.c | 476 ++++++++++++++++++
drivers/gpu/drm/vkms/vkms_output.c | 2 +
4 files changed, 480 insertions(+)
create mode 100644 drivers/gpu/drm/vkms/tests/vkms_stereo_test.c

diff --git a/drivers/gpu/drm/vkms/Kconfig b/drivers/gpu/drm/vkms/Kconfig
index 3977bbb99f..d0b7f772cc 100644
--- a/drivers/gpu/drm/vkms/Kconfig
+++ b/drivers/gpu/drm/vkms/Kconfig
@@ -19,6 +19,7 @@ config DRM_VKMS
config DRM_VKMS_KUNIT_TEST
tristate "KUnit tests for VKMS" if !KUNIT_ALL_TESTS
depends on DRM_VKMS && KUNIT
+ select DRM_KUNIT_TEST_HELPERS
default KUNIT_ALL_TESTS
help
This builds unit tests for VKMS. This option is not useful for
diff --git a/drivers/gpu/drm/vkms/tests/Makefile b/drivers/gpu/drm/vkms/tests/Makefile
index 92cfa7262b..cad869aa61 100644
--- a/drivers/gpu/drm/vkms/tests/Makefile
+++ b/drivers/gpu/drm/vkms/tests/Makefile
@@ -5,5 +5,6 @@ vkms-kunit-tests-y := \
vkms_format_test.o \
vkms_color_test.o \
vkms_configfs_test.o \
+ vkms_stereo_test.o \

obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += vkms-kunit-tests.o
diff --git a/drivers/gpu/drm/vkms/tests/vkms_stereo_test.c b/drivers/gpu/drm/vkms/tests/vkms_stereo_test.c
new file mode 100644
index 0000000000..9229d5ae17
--- /dev/null
+++ b/drivers/gpu/drm/vkms/tests/vkms_stereo_test.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <kunit/test.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_kunit_helpers.h>
+#include <drm/drm_modeset_lock.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_writeback.h>
+
+#include "../vkms_config.h"
+#include "../vkms_connector.h"
+#include "../vkms_drv.h"
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
+/*
+ * edid-decode (hex):
+ *
+ * 00 ff ff ff ff ff ff 00 31 d8 3d 00 00 00 00 00
+ * 00 21 01 03 81 a0 5a 78 0a 00 00 00 00 00 00 00
+ * 00 00 00 20 00 00 01 01 01 01 01 01 01 01 01 01
+ * 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c
+ * 45 00 40 84 63 00 00 1e 00 00 00 fc 00 53 74 65
+ * 72 65 6f 20 45 44 49 44 0a 20 00 00 00 fd 00 17
+ * 4c 0f 8c 1e 00 0a 20 20 20 20 20 20 00 00 00 10
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 b4
+ *
+ * 02 03 1d 81 46 10 04 20 13 1f 22 6e 03 0c 00 10
+ * 00 00 3c 20 a0 04 01 00 06 50 e2 00 4a 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7f
+ *
+ * ----------------
+ *
+ * Block 0, Base EDID:
+ * EDID Structure Version & Revision: 1.3
+ * Vendor & Product Identification:
+ * Manufacturer: LNX
+ * Model: 61
+ * Made in: 2023
+ * Basic Display Parameters & Features:
+ * Digital display
+ * DFP 1.x compatible TMDS
+ * Maximum image size: 160 cm x 90 cm
+ * Gamma: 2.20
+ * RGB color display
+ * First detailed timing is the preferred timing
+ * Color Characteristics:
+ * Red : 0.0000, 0.0000
+ * Green: 0.0000, 0.0000
+ * Blue : 0.0000, 0.0000
+ * White: 0.0000, 0.0000
+ * Established Timings I & II:
+ * DMT 0x04: 640x480 59.940476 Hz 4:3 31.469 kHz 25.175000 MHz
+ * Standard Timings: none
+ * Detailed Timing Descriptors:
+ * DTD 1: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (1600 mm x 900 mm)
+ * Hfront 88 Hsync 44 Hback 148 Hpol P
+ * Vfront 4 Vsync 5 Vback 36 Vpol P
+ * Display Product Name: 'Stereo EDID'
+ * Display Range Limits:
+ * Monitor ranges (GTF): 23-76 Hz V, 15-140 kHz H, max dotclock 300 MHz
+ * Dummy Descriptor:
+ * Extension blocks: 1
+ * Checksum: 0xb4
+ *
+ * ----------------
+ *
+ * Block 1, CTA-861 Extension Block:
+ * Revision: 3
+ * Underscans IT Video Formats by default
+ * Native detailed modes: 1
+ * Video Data Block:
+ * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz
+ * VIC 4: 1280x720 60.000000 Hz 16:9 45.000 kHz 74.250000 MHz
+ * VIC 32: 1920x1080 24.000000 Hz 16:9 27.000 kHz 74.250000 MHz
+ * VIC 19: 1280x720 50.000000 Hz 16:9 37.500 kHz 74.250000 MHz
+ * VIC 31: 1920x1080 50.000000 Hz 16:9 56.250 kHz 148.500000 MHz
+ * VIC 34: 1920x1080 30.000000 Hz 16:9 33.750 kHz 74.250000 MHz
+ * Vendor-Specific Data Block (HDMI), OUI 00-0C-03:
+ * Source physical address: 1.0.0.0
+ * Maximum TMDS clock: 300 MHz
+ * Extended HDMI video details:
+ * 3D present
+ * All advertised VICs are 3D-capable
+ * 3D: Side-by-side (half, horizontal)
+ * 3D VIC indices with specific capabilities:
+ * VIC 16: 1920x1080 60.000000 Hz 16:9 67.500 kHz 148.500000 MHz (top-and-bottom)
+ * VIC 34: 1920x1080 30.000000 Hz 16:9 33.750 kHz 74.250000 MHz (frame packing)
+ * Video Capability Data Block:
+ * YCbCr quantization: No Data
+ * RGB quantization: Selectable (via AVI Q)
+ * PT scan behavior: No Data
+ * IT scan behavior: Always Underscanned
+ * CE scan behavior: Always Underscanned
+ * Checksum: 0x7f
+ */
+static const u8 test_edid_hdmi_3d[] = {
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x31, 0xd8, 0x3d, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x01, 0x03, 0x81, 0xa0, 0x5a, 0x78,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38,
+ 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x40, 0x84, 0x63, 0x00, 0x00, 0x1e,
+ 0x00, 0x00, 0x00, 0xfc, 0x00, 0x53, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x20,
+ 0x45, 0x44, 0x49, 0x44, 0x0a, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x17,
+ 0x4c, 0x0f, 0x8c, 0x1e, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb4, 0x02, 0x03, 0x1d, 0x81,
+ 0x46, 0x10, 0x04, 0x20, 0x13, 0x1f, 0x22, 0x6e, 0x03, 0x0c, 0x00, 0x10,
+ 0x00, 0x00, 0x3c, 0x20, 0xa0, 0x04, 0x01, 0x00, 0x06, 0x50, 0xe2, 0x00,
+ 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x7f,
+};
+
+struct vkms_stereo_test_priv {
+ struct vkms_device *vkmsdev;
+ struct drm_connector *connector;
+ struct drm_writeback_connector *wb_connector;
+ struct drm_crtc *crtc;
+ struct drm_plane *primary;
+};
+
+struct vkms_stereo_test_mode {
+ int hdisplay;
+ int vdisplay;
+ int vrefresh;
+ u32 layout;
+};
+
+/* The stereo modes the EDID above declares, without the interlaced ones */
+static const struct vkms_stereo_test_mode test_edid_stereo_modes[] = {
+ /* 3D_present: the mandatory formats of the 2D formats listed */
+ { 1920, 1080, 24, DRM_MODE_FLAG_3D_FRAME_PACKING },
+ { 1920, 1080, 24, DRM_MODE_FLAG_3D_TOP_AND_BOTTOM },
+ { 1280, 720, 60, DRM_MODE_FLAG_3D_FRAME_PACKING },
+ { 1280, 720, 60, DRM_MODE_FLAG_3D_TOP_AND_BOTTOM },
+ { 1280, 720, 50, DRM_MODE_FLAG_3D_FRAME_PACKING },
+ { 1280, 720, 50, DRM_MODE_FLAG_3D_TOP_AND_BOTTOM },
+ /* 3D_Structure_ALL: side by side (half) for every VIC */
+ { 1920, 1080, 60, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ { 1280, 720, 60, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ { 1920, 1080, 24, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ { 1280, 720, 50, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ { 1920, 1080, 50, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ { 1920, 1080, 30, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ /* 3D_Structure_X */
+ { 1920, 1080, 60, DRM_MODE_FLAG_3D_TOP_AND_BOTTOM },
+ { 1920, 1080, 30, DRM_MODE_FLAG_3D_FRAME_PACKING },
+};
+
+static void vkms_stereo_test_config_destroy(void *config)
+{
+ vkms_config_destroy(config);
+}
+
+static void vkms_stereo_test_fb_destroy(struct drm_framebuffer *fb)
+{
+ drm_framebuffer_cleanup(fb);
+}
+
+static const struct drm_framebuffer_funcs vkms_stereo_test_fb_funcs = {
+ .destroy = vkms_stereo_test_fb_destroy,
+};
+
+static void vkms_stereo_test_fb_put(void *fb)
+{
+ drm_framebuffer_put(fb);
+}
+
+static struct drm_framebuffer *vkms_stereo_test_create_fb(struct kunit *test,
+ int width, int height)
+{
+ struct vkms_stereo_test_priv *priv = test->priv;
+ struct drm_framebuffer *fb;
+ int ret;
+
+ fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, fb);
+
+ fb->dev = &priv->vkmsdev->drm;
+ fb->format = drm_format_info(DRM_FORMAT_XRGB8888);
+ fb->width = width;
+ fb->height = height;
+ fb->pitches[0] = width * 4;
+
+ ret = drm_framebuffer_init(fb->dev, fb, &vkms_stereo_test_fb_funcs);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = kunit_add_action_or_reset(test, vkms_stereo_test_fb_put, fb);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ return fb;
+}
+
+static int vkms_stereo_test_init(struct kunit *test)
+{
+ struct vkms_stereo_test_priv *priv;
+ struct vkms_config_connector *connector_cfg;
+ struct vkms_config_plane *plane_cfg;
+ struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config *config;
+ struct device *dev;
+ int ret;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, priv);
+ test->priv = priv;
+
+ config = vkms_config_default_create(false, true, false, false);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+ ret = kunit_add_action_or_reset(test, vkms_stereo_test_config_destroy, config);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ vkms_config_for_each_connector(config, connector_cfg) {
+ vkms_config_connector_set_edid_enabled(connector_cfg, true);
+ vkms_config_connector_set_edid(connector_cfg, test_edid_hdmi_3d,
+ sizeof(test_edid_hdmi_3d));
+ }
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ priv->vkmsdev = drm_kunit_helper_alloc_drm_device(test, dev, struct vkms_device, drm,
+ DRIVER_MODESET | DRIVER_ATOMIC);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->vkmsdev);
+
+ priv->vkmsdev->config = config;
+ config->dev = priv->vkmsdev;
+
+ ret = vkms_output_init(priv->vkmsdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ vkms_config_for_each_crtc(config, crtc_cfg) {
+ priv->crtc = &crtc_cfg->crtc->crtc;
+ priv->wb_connector = &crtc_cfg->crtc->wb_connector;
+ plane_cfg = vkms_config_crtc_primary_plane(config, crtc_cfg);
+ priv->primary = &plane_cfg->plane->base;
+ }
+
+ vkms_config_for_each_connector(config, connector_cfg)
+ priv->connector = &connector_cfg->connector->base;
+
+ return 0;
+}
+
+static void vkms_stereo_test_probe(struct kunit *test)
+{
+ struct vkms_stereo_test_priv *priv = test->priv;
+ struct drm_device *drm = &priv->vkmsdev->drm;
+ int count;
+
+ mutex_lock(&drm->mode_config.mutex);
+ count = drm_helper_probe_single_connector_modes(priv->connector, XRES_MAX, YRES_MAX);
+ mutex_unlock(&drm->mode_config.mutex);
+
+ KUNIT_ASSERT_GT(test, count, 0);
+}
+
+static bool vkms_stereo_test_mode_matches(const struct drm_display_mode *mode,
+ const struct vkms_stereo_test_mode *expected)
+{
+ return mode->hdisplay == expected->hdisplay &&
+ mode->vdisplay == expected->vdisplay &&
+ drm_mode_vrefresh(mode) == expected->vrefresh &&
+ (mode->flags & DRM_MODE_FLAG_3D_MASK) == expected->layout;
+}
+
+static const struct drm_display_mode *
+vkms_stereo_test_find_mode(struct kunit *test, const struct vkms_stereo_test_mode *expected)
+{
+ struct vkms_stereo_test_priv *priv = test->priv;
+ const struct drm_display_mode *mode;
+
+ list_for_each_entry(mode, &priv->connector->modes, head) {
+ if (vkms_stereo_test_mode_matches(mode, expected))
+ return mode;
+ }
+
+ return NULL;
+}
+
+/*
+ * The connector lists the stereo modes of its EDID next to the 2D ones, and
+ * no other stereo mode. Formats at 24, 30 and 60 Hz are listed twice, at
+ * the integer rate and at 1000/1001 of it, both covered by one entry.
+ */
+static void vkms_stereo_test_edid_modes(struct kunit *test)
+{
+ static const struct vkms_stereo_test_mode mode_2d = { 1920, 1080, 60, 0 };
+ struct vkms_stereo_test_priv *priv = test->priv;
+ const struct drm_display_mode *mode;
+ int i;
+
+ vkms_stereo_test_probe(test);
+
+ for (i = 0; i < ARRAY_SIZE(test_edid_stereo_modes); i++) {
+ const struct vkms_stereo_test_mode *expected = &test_edid_stereo_modes[i];
+
+ KUNIT_EXPECT_NOT_NULL_MSG(test, vkms_stereo_test_find_mode(test, expected),
+ "%dx%d@%d layout %u not listed",
+ expected->hdisplay, expected->vdisplay,
+ expected->vrefresh, expected->layout >> 14);
+ }
+
+ list_for_each_entry(mode, &priv->connector->modes, head) {
+ bool expected = false;
+
+ if (!(mode->flags & DRM_MODE_FLAG_3D_MASK))
+ continue;
+
+ for (i = 0; i < ARRAY_SIZE(test_edid_stereo_modes); i++)
+ expected |= vkms_stereo_test_mode_matches(mode, &test_edid_stereo_modes[i]);
+
+ KUNIT_EXPECT_TRUE_MSG(test, expected, "unexpected " DRM_MODE_FMT,
+ DRM_MODE_ARG(mode));
+ }
+
+ KUNIT_EXPECT_NOT_NULL(test, vkms_stereo_test_find_mode(test, &mode_2d));
+}
+
+struct vkms_stereo_test_frame_case {
+ const char *name;
+ struct vkms_stereo_test_mode mode;
+ int width;
+ int height;
+ int bad_width;
+ int bad_height;
+};
+
+/*
+ * The frame of a mode is the buffer that holds both eyes: frame packing
+ * adds vtotal lines (1080 + 1125 for these modes), the other layouts keep
+ * the size of the mode. Each case also names a writeback buffer size that
+ * must be refused.
+ */
+static const struct vkms_stereo_test_frame_case vkms_stereo_test_frame_cases[] = {
+ { "2d", { 1920, 1080, 60, 0 }, 1920, 1080, 1920, 2205 },
+ { "frame-packing", { 1920, 1080, 30, DRM_MODE_FLAG_3D_FRAME_PACKING },
+ 1920, 2205, 1920, 1080 },
+ { "top-and-bottom", { 1920, 1080, 60, DRM_MODE_FLAG_3D_TOP_AND_BOTTOM },
+ 1920, 1080, 1920, 2205 },
+ { "side-by-side-half", { 1920, 1080, 60, DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF },
+ 1920, 1080, 3840, 1080 },
+};
+
+static void vkms_stereo_test_frame_case_desc(const struct vkms_stereo_test_frame_case *t,
+ char *desc)
+{
+ strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(vkms_stereo_test_frame, vkms_stereo_test_frame_cases,
+ vkms_stereo_test_frame_case_desc);
+
+/*
+ * Checks an update showing a primary plane of the frame size and writing
+ * the composed frame back to a buffer of wb_width x wb_height.
+ */
+static int vkms_stereo_test_check_update(struct kunit *test,
+ const struct drm_display_mode *mode,
+ int width, int height,
+ int wb_width, int wb_height)
+{
+ struct vkms_stereo_test_priv *priv = test->priv;
+ struct drm_device *drm = &priv->vkmsdev->drm;
+ struct drm_framebuffer *fb, *wb_fb;
+ struct drm_connector_state *conn_state, *wb_state;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_plane_state *plane_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_atomic_commit *state;
+ int ret;
+
+ fb = vkms_stereo_test_create_fb(test, width, height);
+ wb_fb = vkms_stereo_test_create_fb(test, wb_width, wb_height);
+
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
+
+ crtc_state = drm_atomic_get_crtc_state(state, priv->crtc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state);
+ ret = drm_atomic_set_mode_for_crtc(crtc_state, mode);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ crtc_state->enable = true;
+ crtc_state->active = true;
+
+ conn_state = drm_atomic_get_connector_state(state, priv->connector);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state);
+ ret = drm_atomic_set_crtc_for_connector(conn_state, priv->crtc);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ plane_state = drm_atomic_get_plane_state(state, priv->primary);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state);
+ ret = drm_atomic_set_crtc_for_plane(plane_state, priv->crtc);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ drm_atomic_set_fb_for_plane(plane_state, fb);
+ plane_state->crtc_w = width;
+ plane_state->crtc_h = height;
+ plane_state->src_w = width << 16;
+ plane_state->src_h = height << 16;
+
+ wb_state = drm_atomic_get_connector_state(state, &priv->wb_connector->base);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb_state);
+ ret = drm_atomic_set_crtc_for_connector(wb_state, priv->crtc);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = drm_writeback_set_fb(wb_state, wb_fb);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = drm_atomic_check_only(state);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+
+ return ret;
+}
+
+/*
+ * The writeback connector takes a buffer of the frame size, so it receives
+ * both eyes, and refuses any other size.
+ */
+static void vkms_stereo_test_writeback_frame(struct kunit *test)
+{
+ const struct vkms_stereo_test_frame_case *t = test->param_value;
+ const struct drm_display_mode *mode;
+ int ret;
+
+ vkms_stereo_test_probe(test);
+
+ mode = vkms_stereo_test_find_mode(test, &t->mode);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ ret = vkms_stereo_test_check_update(test, mode, t->width, t->height,
+ t->width, t->height);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ ret = vkms_stereo_test_check_update(test, mode, t->width, t->height,
+ t->bad_width, t->bad_height);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static struct kunit_case vkms_stereo_test_cases[] = {
+ KUNIT_CASE(vkms_stereo_test_edid_modes),
+ KUNIT_CASE_PARAM(vkms_stereo_test_writeback_frame,
+ vkms_stereo_test_frame_gen_params),
+ {}
+};
+
+static struct kunit_suite vkms_stereo_test_suite = {
+ .name = "vkms-stereo",
+ .init = vkms_stereo_test_init,
+ .test_cases = vkms_stereo_test_cases,
+};
+
+kunit_test_suite(vkms_stereo_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Kunit test for vkms stereo modes");
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index ad901340ba..b484255c43 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -5,6 +5,7 @@
#include "vkms_drv.h"
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
+#include <kunit/visibility.h>

int vkms_output_init(struct vkms_device *vkmsdev)
{
@@ -130,3 +131,4 @@ int vkms_output_init(struct vkms_device *vkmsdev)

return 0;
}
+EXPORT_SYMBOL_IF_KUNIT(vkms_output_init);