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 <[email protected]>
---
 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);

Reply via email to