root/drivers/gpu/drm/tests/drm_bridge_test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Kunit test for drm_bridge functions
 */
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_bridge_helper.h>
#include <drm/drm_kunit_helpers.h>

#include <kunit/device.h>
#include <kunit/test.h>

/*
 * Mimick the typical "private" struct defined by a bridge driver, which
 * embeds a bridge plus other fields.
 *
 * Having at least one member before @bridge ensures we test non-zero
 * @bridge offset.
 */
struct drm_bridge_priv {
        unsigned int enable_count;
        unsigned int disable_count;
        struct drm_bridge bridge;
        void *data;
};

struct drm_bridge_init_priv {
        struct drm_device drm;
        /** @dev: device, only for tests not needing a whole drm_device */
        struct device *dev;
        struct drm_plane *plane;
        struct drm_crtc *crtc;
        struct drm_encoder encoder;
        struct drm_bridge_priv *test_bridge;
        struct drm_connector *connector;
        bool destroyed;
};

static struct drm_bridge_priv *bridge_to_priv(struct drm_bridge *bridge)
{
        return container_of(bridge, struct drm_bridge_priv, bridge);
}

static void drm_test_bridge_priv_destroy(struct drm_bridge *bridge)
{
        struct drm_bridge_priv *bridge_priv = bridge_to_priv(bridge);
        struct drm_bridge_init_priv *priv = (struct drm_bridge_init_priv *)bridge_priv->data;

        priv->destroyed = true;
}

static void drm_test_bridge_enable(struct drm_bridge *bridge)
{
        struct drm_bridge_priv *priv = bridge_to_priv(bridge);

        priv->enable_count++;
}

static void drm_test_bridge_disable(struct drm_bridge *bridge)
{
        struct drm_bridge_priv *priv = bridge_to_priv(bridge);

        priv->disable_count++;
}

static const struct drm_bridge_funcs drm_test_bridge_legacy_funcs = {
        .destroy                = drm_test_bridge_priv_destroy,
        .enable                 = drm_test_bridge_enable,
        .disable                = drm_test_bridge_disable,
};

static void drm_test_bridge_atomic_enable(struct drm_bridge *bridge,
                                          struct drm_atomic_commit *state)
{
        struct drm_bridge_priv *priv = bridge_to_priv(bridge);

        priv->enable_count++;
}

static void drm_test_bridge_atomic_disable(struct drm_bridge *bridge,
                                           struct drm_atomic_commit *state)
{
        struct drm_bridge_priv *priv = bridge_to_priv(bridge);

        priv->disable_count++;
}

static const struct drm_bridge_funcs drm_test_bridge_atomic_funcs = {
        .destroy                = drm_test_bridge_priv_destroy,
        .atomic_enable          = drm_test_bridge_atomic_enable,
        .atomic_disable         = drm_test_bridge_atomic_disable,
        .atomic_destroy_state   = drm_atomic_helper_bridge_destroy_state,
        .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
        .atomic_reset           = drm_atomic_helper_bridge_reset,
};

KUNIT_DEFINE_ACTION_WRAPPER(drm_bridge_remove_wrapper,
                            drm_bridge_remove,
                            struct drm_bridge *);

static int drm_kunit_bridge_add(struct kunit *test,
                                struct drm_bridge *bridge)
{
        drm_bridge_add(bridge);

        return kunit_add_action_or_reset(test,
                                         drm_bridge_remove_wrapper,
                                         bridge);
}

static struct drm_bridge_init_priv *
drm_test_bridge_init(struct kunit *test, const struct drm_bridge_funcs *funcs)
{
        struct drm_bridge_init_priv *priv;
        struct drm_encoder *enc;
        struct drm_bridge *bridge;
        struct drm_device *drm;
        struct device *dev;
        int ret;

        dev = drm_kunit_helper_alloc_device(test);
        if (IS_ERR(dev))
                return ERR_CAST(dev);

        priv = drm_kunit_helper_alloc_drm_device(test, dev,
                                                 struct drm_bridge_init_priv, drm,
                                                 DRIVER_MODESET | DRIVER_ATOMIC);
        if (IS_ERR(priv))
                return ERR_CAST(priv);

        priv->test_bridge = devm_drm_bridge_alloc(dev, struct drm_bridge_priv, bridge, funcs);
        if (IS_ERR(priv->test_bridge))
                return ERR_CAST(priv->test_bridge);

        priv->test_bridge->data = priv;

        drm = &priv->drm;
        priv->plane = drm_kunit_helper_create_primary_plane(test, drm,
                                                            NULL,
                                                            NULL,
                                                            NULL, 0,
                                                            NULL);
        if (IS_ERR(priv->plane))
                return ERR_CAST(priv->plane);

        priv->crtc = drm_kunit_helper_create_crtc(test, drm,
                                                  priv->plane, NULL,
                                                  NULL,
                                                  NULL);
        if (IS_ERR(priv->crtc))
                return ERR_CAST(priv->crtc);

        enc = &priv->encoder;
        ret = drmm_encoder_init(drm, enc, NULL, DRM_MODE_ENCODER_TMDS, NULL);
        if (ret)
                return ERR_PTR(ret);

        enc->possible_crtcs = drm_crtc_mask(priv->crtc);

        bridge = &priv->test_bridge->bridge;
        bridge->type = DRM_MODE_CONNECTOR_VIRTUAL;

        ret = drm_kunit_bridge_add(test, bridge);
        if (ret)
                return ERR_PTR(ret);

        ret = drm_bridge_attach(enc, bridge, NULL, 0);
        if (ret)
                return ERR_PTR(ret);

        priv->connector = drm_bridge_connector_init(drm, enc);
        if (IS_ERR(priv->connector))
                return ERR_CAST(priv->connector);

        drm_mode_config_reset(drm);

        return priv;
}

/*
 * Test that drm_bridge_get_current_state() returns the last committed
 * state for an atomic bridge.
 */
static void drm_test_drm_bridge_get_current_state_atomic(struct kunit *test)
{
        struct drm_modeset_acquire_ctx ctx;
        struct drm_bridge_init_priv *priv;
        struct drm_bridge_state *curr_bridge_state;
        struct drm_bridge_state *bridge_state;
        struct drm_atomic_commit *state;
        struct drm_bridge *bridge;
        struct drm_device *drm;
        int ret;

        priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        drm_modeset_acquire_init(&ctx, 0);

        drm = &priv->drm;
        state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);

retry_commit:
        bridge = &priv->test_bridge->bridge;
        bridge_state = drm_atomic_get_bridge_state(state, bridge);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bridge_state);

        ret = drm_atomic_commit(state);
        if (ret == -EDEADLK) {
                drm_atomic_commit_clear(state);
                drm_modeset_backoff(&ctx);
                goto retry_commit;
        }
        KUNIT_ASSERT_EQ(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        drm_modeset_acquire_init(&ctx, 0);

retry_state:
        ret = drm_modeset_lock(&bridge->base.lock, &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_state;
        }

        curr_bridge_state = drm_bridge_get_current_state(bridge);
        KUNIT_EXPECT_PTR_EQ(test, curr_bridge_state, bridge_state);

        drm_modeset_unlock(&bridge->base.lock);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);
}

/*
 * Test that drm_bridge_get_current_state() returns NULL for a
 * non-atomic bridge.
 */
static void drm_test_drm_bridge_get_current_state_legacy(struct kunit *test)
{
        struct drm_bridge_init_priv *priv;
        struct drm_bridge *bridge;

        priv = drm_test_bridge_init(test, &drm_test_bridge_legacy_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        /*
         * NOTE: Strictly speaking, we should take the bridge->base.lock
         * before calling that function. However, bridge->base is only
         * initialized if the bridge is atomic, while we explicitly
         * initialize one that isn't there.
         *
         * In order to avoid unnecessary warnings, let's skip the
         * locking. The function would return NULL in all cases anyway,
         * so we don't really have any concurrency to worry about.
         */
        bridge = &priv->test_bridge->bridge;
        KUNIT_EXPECT_NULL(test, drm_bridge_get_current_state(bridge));
}

static struct kunit_case drm_bridge_get_current_state_tests[] = {
        KUNIT_CASE(drm_test_drm_bridge_get_current_state_atomic),
        KUNIT_CASE(drm_test_drm_bridge_get_current_state_legacy),
        { }
};


static struct kunit_suite drm_bridge_get_current_state_test_suite = {
        .name = "drm_test_bridge_get_current_state",
        .test_cases = drm_bridge_get_current_state_tests,
};

/*
 * Test that an atomic bridge is properly power-cycled when calling
 * drm_bridge_helper_reset_crtc().
 */
static void drm_test_drm_bridge_helper_reset_crtc_atomic(struct kunit *test)
{
        struct drm_modeset_acquire_ctx ctx;
        struct drm_bridge_init_priv *priv;
        struct drm_display_mode *mode;
        struct drm_bridge_priv *bridge_priv;
        int ret;

        priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);

        drm_modeset_acquire_init(&ctx, 0);

retry_commit:
        ret = drm_kunit_helper_enable_crtc_connector(test,
                                                     &priv->drm, priv->crtc,
                                                     priv->connector,
                                                     mode,
                                                     &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_commit;
        }
        KUNIT_ASSERT_EQ(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        bridge_priv = priv->test_bridge;
        KUNIT_ASSERT_EQ(test, bridge_priv->enable_count, 1);
        KUNIT_ASSERT_EQ(test, bridge_priv->disable_count, 0);

        drm_modeset_acquire_init(&ctx, 0);

retry_reset:
        ret = drm_bridge_helper_reset_crtc(&bridge_priv->bridge, &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_reset;
        }
        KUNIT_ASSERT_EQ(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        KUNIT_EXPECT_EQ(test, bridge_priv->enable_count, 2);
        KUNIT_EXPECT_EQ(test, bridge_priv->disable_count, 1);
}

/*
 * Test that calling drm_bridge_helper_reset_crtc() on a disabled atomic
 * bridge will fail and not call the enable / disable callbacks
 */
static void drm_test_drm_bridge_helper_reset_crtc_atomic_disabled(struct kunit *test)
{
        struct drm_modeset_acquire_ctx ctx;
        struct drm_bridge_init_priv *priv;
        struct drm_display_mode *mode;
        struct drm_bridge_priv *bridge_priv;
        int ret;

        priv = drm_test_bridge_init(test, &drm_test_bridge_atomic_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);

        bridge_priv = priv->test_bridge;
        KUNIT_ASSERT_EQ(test, bridge_priv->enable_count, 0);
        KUNIT_ASSERT_EQ(test, bridge_priv->disable_count, 0);

        drm_modeset_acquire_init(&ctx, 0);

retry_reset:
        ret = drm_bridge_helper_reset_crtc(&bridge_priv->bridge, &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_reset;
        }
        KUNIT_EXPECT_LT(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        KUNIT_EXPECT_EQ(test, bridge_priv->enable_count, 0);
        KUNIT_EXPECT_EQ(test, bridge_priv->disable_count, 0);
}

/*
 * Test that a non-atomic bridge is properly power-cycled when calling
 * drm_bridge_helper_reset_crtc().
 */
static void drm_test_drm_bridge_helper_reset_crtc_legacy(struct kunit *test)
{
        struct drm_modeset_acquire_ctx ctx;
        struct drm_bridge_init_priv *priv;
        struct drm_display_mode *mode;
        struct drm_bridge_priv *bridge_priv;
        int ret;

        priv = drm_test_bridge_init(test, &drm_test_bridge_legacy_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        mode = drm_kunit_display_mode_from_cea_vic(test, &priv->drm, 16);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mode);

        drm_modeset_acquire_init(&ctx, 0);

retry_commit:
        ret = drm_kunit_helper_enable_crtc_connector(test,
                                                     &priv->drm, priv->crtc,
                                                     priv->connector,
                                                     mode,
                                                     &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_commit;
        }
        KUNIT_ASSERT_EQ(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        bridge_priv = priv->test_bridge;
        KUNIT_ASSERT_EQ(test, bridge_priv->enable_count, 1);
        KUNIT_ASSERT_EQ(test, bridge_priv->disable_count, 0);

        drm_modeset_acquire_init(&ctx, 0);

retry_reset:
        ret = drm_bridge_helper_reset_crtc(&bridge_priv->bridge, &ctx);
        if (ret == -EDEADLK) {
                drm_modeset_backoff(&ctx);
                goto retry_reset;
        }
        KUNIT_ASSERT_EQ(test, ret, 0);

        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);

        KUNIT_EXPECT_EQ(test, bridge_priv->enable_count, 2);
        KUNIT_EXPECT_EQ(test, bridge_priv->disable_count, 1);
}

static struct kunit_case drm_bridge_helper_reset_crtc_tests[] = {
        KUNIT_CASE(drm_test_drm_bridge_helper_reset_crtc_atomic),
        KUNIT_CASE(drm_test_drm_bridge_helper_reset_crtc_atomic_disabled),
        KUNIT_CASE(drm_test_drm_bridge_helper_reset_crtc_legacy),
        { }
};

static struct kunit_suite drm_bridge_helper_reset_crtc_test_suite = {
        .name = "drm_test_bridge_helper_reset_crtc",
        .test_cases = drm_bridge_helper_reset_crtc_tests,
};

static int drm_test_bridge_alloc_init(struct kunit *test)
{
        struct drm_bridge_init_priv *priv;

        priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);

        priv->dev = kunit_device_register(test, "drm-bridge-dev");
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev);

        test->priv = priv;

        priv->test_bridge = devm_drm_bridge_alloc(priv->dev, struct drm_bridge_priv, bridge,
                                                  &drm_test_bridge_atomic_funcs);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->test_bridge);

        priv->test_bridge->data = priv;

        KUNIT_ASSERT_FALSE(test, priv->destroyed);

        return 0;
}

/*
 * Test that a bridge is freed when the device is destroyed in lack of
 * other drm_bridge_get/put() operations.
 */
static void drm_test_drm_bridge_alloc_basic(struct kunit *test)
{
        struct drm_bridge_init_priv *priv = test->priv;

        KUNIT_ASSERT_FALSE(test, priv->destroyed);

        kunit_device_unregister(test, priv->dev);
        KUNIT_EXPECT_TRUE(test, priv->destroyed);
}

/*
 * Test that a bridge is not freed when the device is destroyed when there
 * is still a reference to it, and freed when that reference is put.
 */
static void drm_test_drm_bridge_alloc_get_put(struct kunit *test)
{
        struct drm_bridge_init_priv *priv = test->priv;

        KUNIT_ASSERT_FALSE(test, priv->destroyed);

        drm_bridge_get(&priv->test_bridge->bridge);
        KUNIT_EXPECT_FALSE(test, priv->destroyed);

        kunit_device_unregister(test, priv->dev);
        KUNIT_EXPECT_FALSE(test, priv->destroyed);

        drm_bridge_put(&priv->test_bridge->bridge);
        KUNIT_EXPECT_TRUE(test, priv->destroyed);
}

static struct kunit_case drm_bridge_alloc_tests[] = {
        KUNIT_CASE(drm_test_drm_bridge_alloc_basic),
        KUNIT_CASE(drm_test_drm_bridge_alloc_get_put),
        { }
};

static struct kunit_suite drm_bridge_alloc_test_suite = {
        .name = "drm_bridge_alloc",
        .init = drm_test_bridge_alloc_init,
        .test_cases = drm_bridge_alloc_tests,
};

kunit_test_suites(
        &drm_bridge_get_current_state_test_suite,
        &drm_bridge_helper_reset_crtc_test_suite,
        &drm_bridge_alloc_test_suite,
);

MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>");

MODULE_DESCRIPTION("Kunit test for drm_bridge functions");
MODULE_LICENSE("GPL");