The atomic state readout framework allows a driver to read the state of its hardware into atomic states, either to create the initial state of a device whose outputs have already been enabled, or to compare a commit against the current hardware state.
Add a KUnit test suite covering: - the drm_atomic_sro_state container API: allocation, per-object get and set, and installation of the read-out states into a drm_device; - drm_atomic_helper_sro_readout_state(), including the cases where the readout isn't possible (-ENOTSUPP) because the mode configuration or helper hooks are missing, and error propagation from the readout hooks; - drm_atomic_sro_readout_and_compare() and the per-object state comparison helpers for planes, CRTCs, connectors and bridges, including the mismatch reporting through a drm_printer; - drm_mode_config_create_initial_state(), including its error paths and the fallback to the regular initialization path when the readout isn't available. The tests are built on a mock DRM device whose per-object function tables either implement or omit the readout hooks, with dedicated variants to inject errors from them. Signed-off-by: Maxime Ripard <[email protected]> --- drivers/gpu/drm/tests/Makefile | 1 + .../gpu/drm/tests/drm_atomic_state_readout_test.c | 1501 ++++++++++++++++++++ 2 files changed, 1502 insertions(+) diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile index 42207df1d1c8..b11a07094b32 100644 --- a/drivers/gpu/drm/tests/Makefile +++ b/drivers/gpu/drm/tests/Makefile @@ -7,4 +7,5 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \ drm_atomic_test.o \ drm_atomic_commit_test.o \ + drm_atomic_state_readout_test.o \ drm_bridge_test.o \ drm_cmdline_parser_test.o \ diff --git a/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c new file mode 100644 index 000000000000..ada512f7b32b --- /dev/null +++ b/drivers/gpu/drm/tests/drm_atomic_state_readout_test.c @@ -0,0 +1,1501 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for the DRM atomic state readout infrastructure + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_atomic_state_readout.h> +#include <drm/drm_atomic_state_readout_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_encoder.h> +#include <drm/drm_kunit_helpers.h> +#include <drm/drm_mode_config.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> + +#include <kunit/test.h> + +#include <linux/media-bus-format.h> + +/* + * Those tests assume that the atomic_readout module parameter of + * drm_atomic_state_readout is left to its default value (enabled). + */ + +struct sro_test_priv { + struct drm_device drm; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_encoder encoder; + struct drm_connector connector; + struct drm_private_obj private_obj; + struct drm_bridge bridge; + + unsigned int connector_destroy_count; + unsigned int private_obj_destroy_count; + bool connector_readout_called; + bool hw_reset_called; + int readout_ret; + + /* KUnit context, for the hooks that can't get it as an argument */ + struct kunit *test; +}; + +/* + * The tests run with the mock hardware state described by those + * constants. + */ +#define SRO_TEST_CRTC_X 42 +#define SRO_TEST_CRTC_Y 24 +#define SRO_TEST_MODE_VIC 16 /* 1920x1080@60Hz */ +#define SRO_TEST_OTHER_MODE_VIC 4 /* 640x480@60Hz */ + +static int mock_plane_sro_readout_state(struct drm_plane *plane, + struct drm_atomic_sro_state *state, + struct drm_plane_state *plane_state) +{ + plane_state->visible = true; + plane_state->crtc_x = SRO_TEST_CRTC_X; + plane_state->crtc_y = SRO_TEST_CRTC_Y; + + return 0; +} + +static const struct drm_plane_funcs mock_plane_with_sro_funcs = { + .atomic_create_state = drm_atomic_helper_plane_create_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_sro_readout_state = mock_plane_sro_readout_state, + .atomic_sro_compare_state = drm_atomic_helper_plane_compare_state, +}; + +static int mock_crtc_sro_readout_state(struct drm_crtc *crtc, + struct drm_atomic_sro_state *state, + struct drm_crtc_state *crtc_state) +{ + struct sro_test_priv *priv = + container_of(crtc->dev, struct sro_test_priv, drm); + struct drm_display_mode *mode; + int ret; + + mode = drm_kunit_display_mode_from_cea_vic(priv->test, crtc->dev, + SRO_TEST_MODE_VIC); + if (!mode) + return -EINVAL; + + /* + * drm_atomic_set_mode_for_crtc() also sets the mode blob an + * enabled CRTC needs to pass the atomic checks. + */ + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + return ret; + + crtc_state->active = true; + + /* The connector read out is routed to that CRTC */ + crtc_state->connector_mask = drm_connector_mask(&priv->connector); + + return 0; +} + +static const struct drm_crtc_funcs mock_crtc_with_sro_funcs = { + .atomic_create_state = drm_atomic_helper_crtc_create_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_sro_readout_state = mock_crtc_sro_readout_state, + .atomic_sro_compare_state = drm_atomic_helper_crtc_compare_state, +}; + +static int mock_connector_sro_readout_state(struct drm_connector *conn, + struct drm_atomic_sro_state *state, + struct drm_connector_state *conn_state) +{ + struct sro_test_priv *priv = + container_of(conn, struct sro_test_priv, connector); + struct drm_crtc *crtc; + + priv->connector_readout_called = true; + + /* + * The CRTC states must be read out before the connectors so + * that the connector readout hooks can find them. + */ + drm_for_each_crtc(crtc, conn->dev) { + if (!drm_atomic_sro_get_crtc_state(state, crtc)) + continue; + + /* + * drm_atomic_set_crtc_for_connector() takes a connector + * reference when setting the crtc pointer, which is + * later released by + * __drm_atomic_helper_connector_destroy_state(). The + * readout bypasses that path, so take the reference + * explicitly. + */ + drm_connector_get(conn); + conn_state->crtc = crtc; + conn_state->best_encoder = &priv->encoder; + } + + if (!conn_state->crtc) + return -EINVAL; + + return 0; +} + +static void mock_connector_destroy_state(struct drm_connector *conn, + struct drm_connector_state *conn_state) +{ + struct sro_test_priv *priv = + container_of(conn, struct sro_test_priv, connector); + + priv->connector_destroy_count++; + + drm_atomic_helper_connector_destroy_state(conn, conn_state); +} + +static const struct drm_connector_funcs mock_connector_with_sro_funcs = { + .atomic_create_state = drm_atomic_helper_connector_create_state, + .atomic_destroy_state = mock_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_sro_readout_state = mock_connector_sro_readout_state, + .atomic_sro_compare_state = drm_atomic_helper_connector_compare_state, +}; + +static int mock_connector_sro_readout_err(struct drm_connector *conn, + struct drm_atomic_sro_state *state, + struct drm_connector_state *conn_state) +{ + return -EINVAL; +} + +static const struct drm_connector_funcs mock_connector_with_err_funcs = { + .atomic_create_state = drm_atomic_helper_connector_create_state, + .atomic_destroy_state = mock_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_sro_readout_state = mock_connector_sro_readout_err, + .atomic_sro_compare_state = drm_atomic_helper_connector_compare_state, +}; + +static const struct drm_connector_helper_funcs mock_connector_helper_funcs = { +}; + +/* + * The private object state is subclassed so that the tests can check + * that it went through the readout hook. + */ +struct mock_privobj_state { + struct drm_private_state base; + bool readout; +}; + +static inline struct mock_privobj_state * +to_mock_privobj_state(struct drm_private_state *state) +{ + return container_of(state, struct mock_privobj_state, base); +} + +static struct drm_private_state * +mock_private_obj_create_state(struct drm_private_obj *obj) +{ + struct mock_privobj_state *privobj_state; + + privobj_state = kzalloc_obj(*privobj_state); + if (!privobj_state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, + &privobj_state->base); + + return &privobj_state->base; +} + +static void mock_private_obj_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *obj_state) +{ + struct sro_test_priv *priv = + container_of(obj, struct sro_test_priv, private_obj); + + priv->private_obj_destroy_count++; + + kfree(obj_state); +} + +static int mock_private_obj_sro_readout_state(struct drm_private_obj *obj, + struct drm_atomic_sro_state *state, + struct drm_private_state *obj_state) +{ + to_mock_privobj_state(obj_state)->readout = true; + + return 0; +} + +static bool mock_private_obj_compare_state(struct drm_private_obj *obj, + struct drm_printer *p, + struct drm_private_state *expected, + struct drm_private_state *actual) +{ + return to_mock_privobj_state(expected)->readout == + to_mock_privobj_state(actual)->readout; +} + +static const struct drm_private_state_funcs mock_private_obj_with_sro_funcs = { + .atomic_create_state = mock_private_obj_create_state, + .atomic_destroy_state = mock_private_obj_destroy_state, + .atomic_sro_readout_state = mock_private_obj_sro_readout_state, + .atomic_sro_compare_state = mock_private_obj_compare_state, +}; + +static const struct drm_bridge_funcs mock_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, +}; + +static void mock_private_obj_fini(void *ptr) +{ + drm_atomic_private_obj_fini(ptr); +} + +static void mock_hw_reset(struct drm_device *drm) +{ + struct sro_test_priv *priv = + container_of(drm, struct sro_test_priv, drm); + + priv->hw_reset_called = true; +} + +static int mock_modeset_commit(struct drm_device *drm, + struct drm_atomic_commit *commit, + bool nonblock) +{ + struct sro_test_priv *priv = + container_of(drm, struct sro_test_priv, drm); + + KUNIT_FAIL(priv->test, "unexpected modeset"); + + return 0; +} + +/* + * The atomic_commit hook is a tripwire: reading out the hardware + * state and installing it is a state read, not a modeset, so nothing + * must ever go through the atomic commit machinery. atomic_check is + * only there so that a commit doesn't crash before reaching the + * tripwire. + */ +static const struct drm_mode_config_funcs mock_mode_config_with_sro_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = mock_modeset_commit, + .atomic_sro_readout_state = drm_atomic_helper_sro_readout_state, + .hw_reset = mock_hw_reset, +}; + +static int mock_device_readout(struct drm_device *drm) +{ + struct sro_test_priv *priv = + container_of(drm, struct sro_test_priv, drm); + struct drm_atomic_sro_state *sro_state; + struct drm_crtc_state *crtc_state; + + if (priv->readout_ret) + return priv->readout_ret; + + sro_state = drm_atomic_sro_state_alloc(drm); + if (IS_ERR(sro_state)) + return PTR_ERR(sro_state); + + crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc); + if (IS_ERR(crtc_state)) { + drm_atomic_sro_state_free(sro_state); + return PTR_ERR(crtc_state); + } + + crtc_state->enable = true; + crtc_state->active = true; + + drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state); + drm_atomic_sro_install_state(sro_state); + drm_atomic_sro_state_free(sro_state); + + return 0; +} + +static const struct drm_mode_config_funcs mock_mode_config_with_sro_hw_reset_funcs = { + .atomic_sro_readout_state = mock_device_readout, + .hw_reset = mock_hw_reset, +}; + +static const struct drm_mode_config_funcs mock_mode_config_without_sro_with_hw_reset_funcs = { + .hw_reset = mock_hw_reset, +}; + +static const struct drm_mode_config_helper_funcs mock_mode_config_helper_without_sro_funcs = { +}; + +static const struct drm_mode_config_helper_funcs mock_mode_config_helper_with_sro_funcs = { + .atomic_sro_build_state = drm_atomic_helper_sro_build_state, +}; + +static struct drm_atomic_sro_state * +mock_build_state_error(struct drm_device *dev, + struct drm_atomic_commit *commit) +{ + return ERR_PTR(-EINVAL); +} + +static const struct drm_mode_config_helper_funcs mock_mode_config_helper_with_err_funcs = { + .atomic_sro_build_state = mock_build_state_error, +}; + +/* + * Passing NULL for any of the object funcs will make the KUnit + * helpers use their defaults, which don't implement the state + * readout hooks. + */ +static struct sro_test_priv * +mock_device_create(struct kunit *test, + const struct drm_mode_config_funcs *mode_config_funcs, + const struct drm_mode_config_helper_funcs *helper_funcs, + const struct drm_plane_funcs *plane_funcs, + const struct drm_crtc_funcs *crtc_funcs, + const struct drm_connector_funcs *connector_funcs) +{ + struct sro_test_priv *priv; + struct drm_device *drm; + struct device *dev; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, + struct sro_test_priv, drm, + DRIVER_MODESET | + DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + drm = &priv->drm; + + priv->test = test; + + drm->mode_config.funcs = mode_config_funcs; + drm->mode_config.helper_private = helper_funcs; + + priv->plane = drm_kunit_helper_create_primary_plane(test, drm, + plane_funcs, + NULL, + NULL, 0, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->plane); + + priv->crtc = drm_kunit_helper_create_crtc(test, drm, + priv->plane, NULL, + crtc_funcs, + NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->crtc); + + ret = drmm_encoder_init(drm, &priv->encoder, NULL, + DRM_MODE_ENCODER_NONE, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + priv->encoder.possible_crtcs = drm_crtc_mask(priv->crtc); + + ret = drmm_connector_init(drm, &priv->connector, + connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_connector_helper_add(&priv->connector, + &mock_connector_helper_funcs); + + drm_connector_attach_encoder(&priv->connector, &priv->encoder); + + drm_atomic_private_obj_init(drm, &priv->private_obj, + "test private obj", + &mock_private_obj_with_sro_funcs); + kunit_add_action_or_reset(test, mock_private_obj_fini, + &priv->private_obj); + + return priv; +} + +static struct sro_test_priv * +mock_device_with_sro_create(struct kunit *test) +{ + return mock_device_create(test, + &mock_mode_config_with_sro_funcs, + &mock_mode_config_helper_with_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); +} + +/* + * State readout container tests + */ + +/* + * Test that a state container can be allocated and freed for a device, + * and that it reports the device it was created for. + */ +static void drm_test_sro_state_alloc_free(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_atomic_sro_state *sro_state; + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + KUNIT_EXPECT_PTR_EQ(test, drm_atomic_sro_state_get_device(sro_state), + &priv->drm); + + drm_atomic_sro_state_free(sro_state); +} + +/* + * Test that objects with no state stored in the container report no + * state. + */ +static void drm_test_sro_state_get_unset(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_private_obj *obj; + struct sro_test_priv *priv; + struct drm_atomic_sro_state *sro_state; + struct drm_plane_state *plane_state; + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_private_state *obj_state; + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + obj = &priv->private_obj; + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + /* No state has been stored yet */ + plane_state = drm_atomic_sro_get_plane_state(sro_state, priv->plane); + KUNIT_EXPECT_NULL(test, plane_state); + + crtc_state = drm_atomic_sro_get_crtc_state(sro_state, priv->crtc); + KUNIT_EXPECT_NULL(test, crtc_state); + + conn_state = drm_atomic_sro_get_connector_state(sro_state, connector); + KUNIT_EXPECT_NULL(test, conn_state); + + obj_state = drm_atomic_sro_get_private_obj_state(sro_state, obj); + KUNIT_EXPECT_NULL(test, obj_state); + + drm_atomic_sro_state_free(sro_state); +} + +/* + * Test that states stored in the container can be retrieved from it, + * and that the container destroys the states that haven't been + * installed when it is freed. + */ +static void drm_test_sro_state_set_get(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_private_obj *obj; + struct sro_test_priv *priv; + struct drm_atomic_sro_state *sro_state; + struct drm_plane_state *plane_state, *plane_readback; + struct drm_crtc_state *crtc_state, *crtc_readback; + struct drm_connector_state *conn_state, *conn_readback; + struct drm_private_state *obj_state, *obj_readback; + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + obj = &priv->private_obj; + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + plane_state = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state); + + crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + conn_state = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + obj_state = mock_private_obj_create_state(obj); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, obj_state); + + drm_atomic_sro_set_plane_state(sro_state, priv->plane, plane_state); + drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state); + drm_atomic_sro_set_connector_state(sro_state, connector, conn_state); + drm_atomic_sro_set_private_obj_state(sro_state, obj, obj_state); + + plane_readback = drm_atomic_sro_get_plane_state(sro_state, priv->plane); + KUNIT_EXPECT_PTR_EQ(test, plane_readback, plane_state); + + crtc_readback = drm_atomic_sro_get_crtc_state(sro_state, priv->crtc); + KUNIT_EXPECT_PTR_EQ(test, crtc_readback, crtc_state); + + conn_readback = drm_atomic_sro_get_connector_state(sro_state, + connector); + KUNIT_EXPECT_PTR_EQ(test, conn_readback, conn_state); + + obj_readback = drm_atomic_sro_get_private_obj_state(sro_state, obj); + KUNIT_EXPECT_PTR_EQ(test, obj_readback, obj_state); + + drm_atomic_sro_state_free(sro_state); + + /* The uninstalled states have been destroyed by the container */ + KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 1); + KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 1); +} + +/* + * Test that drm_atomic_sro_install_state() makes the read out states + * the current states of the objects and transfers their ownership: + * freeing the container afterwards doesn't destroy them. + */ +static void drm_test_sro_state_install(struct kunit *test) +{ + struct drm_connector *connector; + struct drm_private_obj *obj; + struct sro_test_priv *priv; + struct drm_atomic_sro_state *sro_state; + struct drm_plane_state *plane_state; + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_private_state *obj_state; + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + obj = &priv->private_obj; + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + plane_state = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state); + + crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + crtc_state->enable = true; + crtc_state->active = true; + + conn_state = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + + obj_state = mock_private_obj_create_state(obj); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, obj_state); + + drm_atomic_sro_set_plane_state(sro_state, priv->plane, plane_state); + drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state); + drm_atomic_sro_set_connector_state(sro_state, connector, conn_state); + drm_atomic_sro_set_private_obj_state(sro_state, obj, obj_state); + + drm_atomic_sro_install_state(sro_state); + + /* The read out states are now owned by the objects */ + KUNIT_EXPECT_PTR_EQ(test, priv->plane->state, plane_state); + KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state); + KUNIT_EXPECT_PTR_EQ(test, connector->state, conn_state); + KUNIT_EXPECT_PTR_EQ(test, obj->state, obj_state); + KUNIT_EXPECT_TRUE(test, priv->crtc->state->active); + + /* + * Freeing the container after the installation must not destroy + * the installed states. + */ + drm_atomic_sro_state_free(sro_state); + + KUNIT_EXPECT_PTR_EQ(test, priv->plane->state, plane_state); + KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state); + KUNIT_EXPECT_PTR_EQ(test, connector->state, conn_state); + KUNIT_EXPECT_PTR_EQ(test, obj->state, obj_state); + + /* + * The connector had no state before the installation, while the + * state the private object was initialized with by + * drm_atomic_private_obj_init() has been destroyed when + * replaced by the read out one. + */ + KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 0); + KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 1); +} + +/* + * Test that installing a state on an object that already has one + * destroys the state that was installed before. + */ +static void drm_test_sro_state_install_replaces_state(struct kunit *test) +{ + struct drm_connector *connector; + struct sro_test_priv *priv; + struct drm_atomic_sro_state *sro_state; + struct drm_connector_state *old_state, *new_state; + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + + old_state = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + connector->state = old_state; + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + new_state = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + drm_atomic_sro_set_connector_state(sro_state, connector, new_state); + + drm_atomic_sro_install_state(sro_state); + drm_atomic_sro_state_free(sro_state); + + KUNIT_EXPECT_PTR_EQ(test, connector->state, new_state); + + /* The state that was installed before has been destroyed */ + KUNIT_EXPECT_EQ(test, priv->connector_destroy_count, 1); +} + +/* + * Test that objects with no state in the container are left untouched + * by the installation. + */ +static void drm_test_sro_state_install_partial(struct kunit *test) +{ + struct drm_private_state *initial_obj_state; + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_atomic_sro_state *sro_state; + struct drm_crtc_state *crtc_state; + + /* The private object is initialized with a state of its own */ + initial_obj_state = priv->private_obj.state; + KUNIT_ASSERT_NOT_NULL(test, initial_obj_state); + + sro_state = drm_atomic_sro_state_alloc(&priv->drm); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sro_state); + + crtc_state = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + drm_atomic_sro_set_crtc_state(sro_state, priv->crtc, crtc_state); + + drm_atomic_sro_install_state(sro_state); + drm_atomic_sro_state_free(sro_state); + + KUNIT_EXPECT_PTR_EQ(test, priv->crtc->state, crtc_state); + KUNIT_EXPECT_NULL(test, priv->plane->state); + KUNIT_EXPECT_NULL(test, priv->connector.state); + + /* The private object has been left untouched */ + KUNIT_EXPECT_PTR_EQ(test, priv->private_obj.state, initial_obj_state); + KUNIT_EXPECT_EQ(test, priv->private_obj_destroy_count, 0); +} + +static struct kunit_case drm_atomic_sro_state_tests[] = { + KUNIT_CASE(drm_test_sro_state_alloc_free), + KUNIT_CASE(drm_test_sro_state_get_unset), + KUNIT_CASE(drm_test_sro_state_set_get), + KUNIT_CASE(drm_test_sro_state_install), + KUNIT_CASE(drm_test_sro_state_install_replaces_state), + KUNIT_CASE(drm_test_sro_state_install_partial), + { } +}; + +static struct kunit_suite drm_atomic_sro_state_test_suite = { + .name = "drm_atomic_sro_state", + .test_cases = drm_atomic_sro_state_tests, +}; + +/* + * Readout tests + */ + +/* + * Test that a successful readout calls the readout hook of every + * object, including the private objects, and installs the states it + * built as the current states of the objects. The connectors are read + * out after the CRTCs so that their hooks can look the CRTC states up. + */ +static void drm_test_sro_readout_state(struct kunit *test) +{ + struct mock_privobj_state *privobj_state; + struct sro_test_priv *priv = mock_device_with_sro_create(test); + int ret; + + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* The states read out have been installed on the objects */ + KUNIT_EXPECT_TRUE(test, priv->crtc->state->enable); + KUNIT_EXPECT_TRUE(test, priv->crtc->state->active); + KUNIT_EXPECT_TRUE(test, priv->plane->state->visible); + KUNIT_EXPECT_EQ(test, priv->plane->state->crtc_x, SRO_TEST_CRTC_X); + KUNIT_EXPECT_EQ(test, priv->plane->state->crtc_y, SRO_TEST_CRTC_Y); + KUNIT_EXPECT_PTR_EQ(test, priv->connector.state->crtc, priv->crtc); + + KUNIT_ASSERT_NOT_NULL(test, priv->private_obj.state); + privobj_state = to_mock_privobj_state(priv->private_obj.state); + KUNIT_EXPECT_TRUE(test, privobj_state->readout); +} + +/* + * Test that a device without an atomic_sro_build_state helper can't be + * read out, and that nothing is installed. + */ +static void drm_test_sro_readout_state_no_helper(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + /* + * Everything is in place for the readout except the + * atomic_sro_build_state helper. + */ + priv = mock_device_create(test, + &mock_mode_config_with_sro_funcs, + &mock_mode_config_helper_without_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP); + + /* Nothing has been installed */ + KUNIT_EXPECT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_NULL(test, priv->plane->state); +} + +/* + * Test that a device whose mode configuration doesn't implement the + * atomic_sro_readout_state hook can't be read out either. + */ +static void drm_test_sro_readout_state_no_hook(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + /* + * Everything is in place for the readout except the + * atomic_sro_readout_state mode configuration hook. + */ + priv = mock_device_create(test, + &mock_mode_config_without_sro_with_hw_reset_funcs, + &mock_mode_config_helper_with_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP); + + /* Nothing has been installed */ + KUNIT_EXPECT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_NULL(test, priv->plane->state); +} + +/* + * Test that a device where an object doesn't implement the readout + * hooks can't be read out either. + */ +static void drm_test_sro_readout_state_missing_hooks(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + /* + * The default KUnit helpers object implementations don't have + * any readout hook. + */ + priv = mock_device_create(test, + &mock_mode_config_with_sro_funcs, + &mock_mode_config_helper_with_sro_funcs, + NULL, NULL, + &mock_connector_with_sro_funcs); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, -ENOTSUPP); + + KUNIT_EXPECT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_NULL(test, priv->plane->state); +} + +/* + * Test that an error from atomic_sro_build_state() is reported to the + * caller as-is, and that no state has been installed. + */ +static void drm_test_sro_readout_state_error(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + priv = mock_device_create(test, + &mock_mode_config_with_sro_funcs, + &mock_mode_config_helper_with_err_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + + /* Nothing has been installed */ + KUNIT_EXPECT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_NULL(test, priv->plane->state); +} + +/* + * Test that drm_atomic_sro_readout_and_compare() builds a fresh state + * from the hardware and compares it to the commit: a commit state + * duplicated from the state read out of the hardware is identical, + * while a modified one is reported as a mismatch. + */ +static void drm_test_sro_readout_and_compare(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *commit; + struct sro_test_priv *priv; + struct drm_crtc_state *crtc_state; + bool identical; + int ret; + + priv = mock_device_with_sro_create(test); + + /* Install the mock hardware state */ + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_modeset_acquire_init(&ctx, 0); + + commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit); + + crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + /* + * The commit state has been duplicated from the state read out + * of the mock hardware, so the two are identical. + */ + identical = drm_atomic_sro_readout_and_compare(commit); + KUNIT_EXPECT_TRUE(test, identical); + + crtc_state->active = false; + identical = drm_atomic_sro_readout_and_compare(commit); + KUNIT_EXPECT_FALSE(test, identical); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that the readout and compare of a commit is scoped to the + * objects of the commit: the objects that the commit doesn't contain + * are neither read out nor compared. + */ +static void drm_test_sro_readout_and_compare_scope(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *commit; + struct sro_test_priv *priv; + struct drm_crtc_state *crtc_state; + bool identical; + int ret; + + priv = mock_device_with_sro_create(test); + + /* Install the mock hardware state */ + ret = drm_atomic_helper_sro_readout_state(&priv->drm); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* + * The connector readout hook sets connector_readout_called + * when it runs. Reset it so that we can check whether it runs + * again. + */ + priv->connector_readout_called = false; + + drm_modeset_acquire_init(&ctx, 0); + + commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit); + + /* + * The commit only contains a state for the CRTC, duplicated + * from the state read out of the mock hardware, so the two + * are identical. + */ + crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + identical = drm_atomic_sro_readout_and_compare(commit); + KUNIT_EXPECT_TRUE(test, identical); + + /* + * The connector isn't part of the commit, so its readout hook + * must not have run. + */ + KUNIT_EXPECT_FALSE(test, priv->connector_readout_called); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +/* + * Test that the state installed by the readout lets the first commit + * tell whether a modeset is needed: a commit asking for the + * configuration already programmed in the hardware doesn't need one, + * and a commit changing the configuration does. + */ +static void drm_test_sro_readout_no_modeset(struct kunit *test) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *commit; + struct sro_test_priv *priv; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *mode; + int ret; + + priv = mock_device_with_sro_create(test); + + ret = drm_mode_config_create_initial_state(&priv->drm); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* + * A commit with the same configuration as the hardware + * doesn't need a modeset. + */ + drm_modeset_acquire_init(&ctx, 0); + + commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit); + + crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + ret = drm_atomic_check_only(commit); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + /* A commit with a different configuration needs a modeset. */ + drm_modeset_acquire_init(&ctx, 0); + +retry: + commit = drm_kunit_helper_atomic_state_alloc(test, &priv->drm, &ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, commit); + + crtc_state = drm_atomic_get_crtc_state(commit, priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + + mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, + SRO_TEST_OTHER_MODE_VIC); + KUNIT_ASSERT_NOT_NULL(test, mode); + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + KUNIT_ASSERT_EQ(test, ret, 0); + + commit->allow_modeset = true; + + ret = drm_atomic_check_only(commit); + if (ret == -EDEADLK) + goto backoff; + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, crtc_state->mode_changed); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return; + +backoff: + drm_atomic_commit_clear(commit); + drm_modeset_backoff(&ctx); + goto retry; +} + +static struct kunit_case drm_atomic_sro_readout_tests[] = { + KUNIT_CASE(drm_test_sro_readout_state), + KUNIT_CASE(drm_test_sro_readout_state_no_helper), + KUNIT_CASE(drm_test_sro_readout_state_no_hook), + KUNIT_CASE(drm_test_sro_readout_state_missing_hooks), + KUNIT_CASE(drm_test_sro_readout_state_error), + KUNIT_CASE(drm_test_sro_readout_and_compare), + KUNIT_CASE(drm_test_sro_readout_and_compare_scope), + KUNIT_CASE(drm_test_sro_readout_no_modeset), + { } +}; + +static struct kunit_suite drm_atomic_sro_readout_test_suite = { + .name = "drm_atomic_sro_readout", + .test_cases = drm_atomic_sro_readout_tests, +}; + +/* + * Compare helper tests + */ + +/* + * Test that two identical plane states compare as equal, without + * reporting any mismatch. + */ +static void drm_test_sro_compare_plane_identical(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_plane_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + state1 = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + identical = drm_atomic_helper_plane_compare_state(priv->plane, p, + state1, state2); + KUNIT_EXPECT_TRUE(test, identical); +} + +/* + * Test that plane states differing by a single field compare as + * different, and that the mismatching field is reported through the + * printer. + */ +static void drm_test_sro_compare_plane_mismatch(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_plane_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + state1 = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = priv->plane->funcs->atomic_create_state(priv->plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + state2->crtc = priv->crtc; + + identical = drm_atomic_helper_plane_compare_state(priv->plane, p, + state1, state2); + KUNIT_EXPECT_FALSE(test, identical); +} + +/* + * Test that two identical active CRTC states compare as equal. + */ +static void drm_test_sro_compare_crtc_identical(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_crtc_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + state1 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + state1->enable = true; + state1->active = true; + state2->enable = true; + state2->active = true; + + identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p, + state1, state2); + KUNIT_EXPECT_TRUE(test, identical); +} + +/* + * Test that two inactive CRTCs compare as equal even though their + * modes differ: the modes are meaningless while inactive and must not + * be compared. + */ +static void drm_test_sro_compare_crtc_inactive(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_display_mode *mode1, *mode2; + struct drm_crtc_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + state1 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + /* Two different but valid modes */ + mode1 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode1); + + mode2 = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 4); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode2); + + /* + * Both CRTCs are inactive: the modes are meaningless and must + * not be compared. + */ + drm_mode_copy(&state1->mode, mode1); + drm_mode_copy(&state2->mode, mode2); + + identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p, + state1, state2); + KUNIT_EXPECT_TRUE(test, identical); +} + +/* + * Test that an active CRTC and an inactive one compare as different. + */ +static void drm_test_sro_compare_crtc_active_mismatch(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_crtc_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + state1 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = priv->crtc->funcs->atomic_create_state(priv->crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + state1->enable = true; + state1->active = true; + + identical = drm_atomic_helper_crtc_compare_state(priv->crtc, p, + state1, state2); + KUNIT_EXPECT_FALSE(test, identical); +} + +/* + * Test that two identical connector states compare as equal. + */ +static void drm_test_sro_compare_connector_identical(struct kunit *test) +{ + struct drm_connector *connector; + struct sro_test_priv *priv; + struct drm_connector_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + + state1 = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + identical = + drm_atomic_helper_connector_compare_state(connector, p, + state1, state2); + KUNIT_EXPECT_TRUE(test, identical); +} + +/* + * Test that connector states whose CRTCs differ compare as different. + */ +static void drm_test_sro_compare_connector_mismatch(struct kunit *test) +{ + struct drm_connector *connector; + struct sro_test_priv *priv; + struct drm_connector_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + priv = mock_device_with_sro_create(test); + connector = &priv->connector; + + state1 = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = connector->funcs->atomic_create_state(connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + state2->crtc = priv->crtc; + + identical = + drm_atomic_helper_connector_compare_state(connector, p, + state1, state2); + KUNIT_EXPECT_FALSE(test, identical); +} + +/* + * Test that two identical bridge states compare as equal. + */ +static void drm_test_sro_compare_bridge_identical(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_bridge_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + priv->bridge.funcs = &mock_bridge_funcs; + priv->bridge.name = "test bridge"; + + state1 = drm_atomic_helper_bridge_create_state(&priv->bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = drm_atomic_helper_bridge_create_state(&priv->bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + identical = drm_atomic_helper_bridge_compare_state(&priv->bridge, p, + state1, state2); + KUNIT_EXPECT_TRUE(test, identical); + + priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state1); + priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state2); +} + +/* + * Test that bridge states whose input bus formats differ compare as + * different. + */ +static void drm_test_sro_compare_bridge_mismatch(struct kunit *test) +{ + struct sro_test_priv *priv = mock_device_with_sro_create(test); + struct drm_bridge_state *state1, *state2; + struct drm_printer *p; + bool identical; + + p = drm_kunit_helper_printer(test, LOGLEVEL_ERR); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + + priv->bridge.funcs = &mock_bridge_funcs; + priv->bridge.name = "test bridge"; + + state1 = drm_atomic_helper_bridge_create_state(&priv->bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state1); + + state2 = drm_atomic_helper_bridge_create_state(&priv->bridge); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state2); + + state2->input_bus_cfg.format = MEDIA_BUS_FMT_RGB888_1X24; + + identical = drm_atomic_helper_bridge_compare_state(&priv->bridge, p, + state1, state2); + KUNIT_EXPECT_FALSE(test, identical); + + priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state1); + priv->bridge.funcs->atomic_destroy_state(&priv->bridge, state2); +} + +static struct kunit_case drm_atomic_sro_compare_tests[] = { + KUNIT_CASE(drm_test_sro_compare_plane_identical), + KUNIT_CASE(drm_test_sro_compare_plane_mismatch), + KUNIT_CASE(drm_test_sro_compare_crtc_identical), + KUNIT_CASE(drm_test_sro_compare_crtc_inactive), + KUNIT_CASE(drm_test_sro_compare_crtc_active_mismatch), + KUNIT_CASE(drm_test_sro_compare_connector_identical), + KUNIT_CASE(drm_test_sro_compare_connector_mismatch), + KUNIT_CASE(drm_test_sro_compare_bridge_identical), + KUNIT_CASE(drm_test_sro_compare_bridge_mismatch), + { } +}; + +static struct kunit_suite drm_atomic_sro_compare_test_suite = { + .name = "drm_atomic_sro_compare", + .test_cases = drm_atomic_sro_compare_tests, +}; + +/* + * drm_mode_config_create_initial_state() tests + */ + +/* + * Test that when the readout succeeds, + * drm_mode_config_create_initial_state() installs the states it read + * out and doesn't reset the hardware. + */ +static void drm_test_mode_config_initial_state_readout(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + priv = mock_device_create(test, + &mock_mode_config_with_sro_hw_reset_funcs, + &mock_mode_config_helper_without_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + ret = drm_mode_config_create_initial_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* The read out state has been installed */ + KUNIT_EXPECT_TRUE(test, priv->crtc->state->enable); + KUNIT_EXPECT_TRUE(test, priv->crtc->state->active); + + /* No fallback to a hardware reset has been needed */ + KUNIT_EXPECT_FALSE(test, priv->hw_reset_called); +} + +/* + * Test that when the driver reports the hardware configuration as + * unsupported, drm_mode_config_create_initial_state() falls back to + * the regular initialization path: the hardware is reset and pristine + * states are created. + */ +static void drm_test_mode_config_initial_state_readout_error(struct kunit *test) +{ + struct mock_privobj_state *privobj_state; + struct sro_test_priv *priv; + int ret; + + priv = mock_device_create(test, + &mock_mode_config_with_sro_hw_reset_funcs, + &mock_mode_config_helper_without_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + /* The driver reports the hardware configuration as unsupported */ + priv->readout_ret = -EINVAL; + + ret = drm_mode_config_create_initial_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* The regular initialization path has been taken */ + KUNIT_EXPECT_TRUE(test, priv->hw_reset_called); + + /* A pristine state has been created for all the objects */ + KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->active); + KUNIT_ASSERT_NOT_NULL(test, priv->plane->state); + KUNIT_ASSERT_NOT_NULL(test, priv->connector.state); + + KUNIT_ASSERT_NOT_NULL(test, priv->private_obj.state); + privobj_state = to_mock_privobj_state(priv->private_obj.state); + KUNIT_EXPECT_FALSE(test, privobj_state->readout); +} + +/* + * Test that when an object readout hook reports the hardware + * configuration as unsupported, drm_mode_config_create_initial_state() + * falls back to the regular initialization path. + */ +static void +drm_test_mode_config_initial_state_object_readout_error(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + priv = mock_device_create(test, + &mock_mode_config_with_sro_funcs, + &mock_mode_config_helper_with_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_err_funcs); + + + /* + * The object readout hooks report their errors through a + * WARN_ON, which is expected here. + */ + kunit_warning_suppress(test) { + ret = drm_mode_config_create_initial_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + /* The regular initialization path has been taken */ + KUNIT_EXPECT_TRUE(test, priv->hw_reset_called); + + /* A pristine state has been created for all the objects */ + KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->active); + KUNIT_ASSERT_NOT_NULL(test, priv->plane->state); + KUNIT_ASSERT_NOT_NULL(test, priv->connector.state); +} + +/* + * Test that a driver without readout support takes the regular + * initialization path as well. + */ +static void drm_test_mode_config_initial_state_no_readout(struct kunit *test) +{ + struct sro_test_priv *priv; + int ret; + + priv = mock_device_create(test, + &mock_mode_config_without_sro_with_hw_reset_funcs, + &mock_mode_config_helper_without_sro_funcs, + &mock_plane_with_sro_funcs, + &mock_crtc_with_sro_funcs, + &mock_connector_with_sro_funcs); + + ret = drm_mode_config_create_initial_state(&priv->drm); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* The regular initialization path has been taken */ + KUNIT_EXPECT_TRUE(test, priv->hw_reset_called); + + KUNIT_ASSERT_NOT_NULL(test, priv->crtc->state); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->enable); + KUNIT_EXPECT_FALSE(test, priv->crtc->state->active); + KUNIT_ASSERT_NOT_NULL(test, priv->plane->state); + KUNIT_ASSERT_NOT_NULL(test, priv->connector.state); +} + +static struct kunit_case drm_mode_config_initial_state_tests[] = { + KUNIT_CASE(drm_test_mode_config_initial_state_readout), + KUNIT_CASE(drm_test_mode_config_initial_state_readout_error), + KUNIT_CASE(drm_test_mode_config_initial_state_object_readout_error), + KUNIT_CASE(drm_test_mode_config_initial_state_no_readout), + { } +}; + +static struct kunit_suite drm_mode_config_initial_state_test_suite = { + .name = "drm_mode_config_initial_state", + .test_cases = drm_mode_config_initial_state_tests, +}; + +kunit_test_suites( + &drm_atomic_sro_compare_test_suite, + &drm_atomic_sro_readout_test_suite, + &drm_atomic_sro_state_test_suite, + &drm_mode_config_initial_state_test_suite, +); + +MODULE_AUTHOR("Maxime Ripard <[email protected]>"); +MODULE_DESCRIPTION("Kunit test for the DRM atomic state readout"); +MODULE_LICENSE("GPL"); -- 2.55.0
