@@ -7,6 +7,7 @@
#include <kunit/test.h>
+#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_mode.h>
@@ -750,7 +751,148 @@ static void drm_test_framebuffer_addfb2(struct kunit *test)
KUNIT_EXPECT_PTR_EQ(test, fb->filp_head.next, &file_priv->fbs);
}
+static void drm_framebuffer_fb_release_remove_mock(struct kref *kref)
+{
+ struct drm_framebuffer *fb = container_of(kref, typeof(*fb), base.refcount);
+ struct drm_framebuffer_test_priv *priv = container_of(fb->dev, typeof(*priv), dev);
+ bool *obj_released = priv->private;
+
+ obj_released[fb->base.id - 1] = true;
+}
+
+static int crtc_set_config_fb_release_mock(struct drm_mode_set *set,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct drm_crtc *crtc = set->crtc;
+ struct drm_framebuffer_test_priv *priv = container_of(crtc->dev, typeof(*priv), dev);
+ bool *obj_released = priv->private;
+
+ obj_released[crtc->base.id - 1] = true;
+ obj_released[crtc->primary->base.id - 1] = true;
+ return 0;
+}
+
+static int disable_plane_fb_release_mock(struct drm_plane *plane,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct drm_framebuffer_test_priv *priv = container_of(plane->dev, typeof(*priv), dev);
+ bool *obj_released = priv->private;
+
+ obj_released[plane->base.id - 1] = true;
+ return 0;
+}
+
+#define NUM_OBJS 5
+
+/*
+ * The drm_fb_release function implicitly calls at some point the
+ * drm_framebuffer_remove, which actually removes framebuffers
+ * based on the driver supporting or not the atomic API. To simplify
+ * this test, let it rely on legacy removing and leave the atomic remove
+ * to be tested in another test case. By doing that, we can also test
+ * the legacy_remove_fb function entirely.
+ */
+static void drm_test_fb_release(struct kunit *test)
+{
+ struct drm_framebuffer_test_priv *priv = test->priv;
+ struct drm_device *dev = &priv->dev;
+ struct drm_file *file_priv = &priv->file_priv;
+ struct drm_plane_funcs plane_funcs = {
+ .disable_plane = disable_plane_fb_release_mock
+ };
+ struct drm_crtc_funcs crtc_funcs = {
+ .set_config = crtc_set_config_fb_release_mock
+ };
+ struct drm_framebuffer *fb1, *fb2;
+ struct drm_plane *plane1, *plane2;
+ struct drm_crtc *crtc;
+ bool *obj_released;
+
+ /*
+ * obj_released[i] where "i" is the obj.base.id - 1. Note that the
+ * "released" word means different things for each kind of obj, which
+ * in case of a framebuffer, means that it was freed, while for the
+ * crtc and plane, means that it was deactivated.
+ */
+ obj_released = kunit_kzalloc(test, NUM_OBJS * sizeof(bool), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, obj_released);
+ priv->private = obj_released;
+
+ fb1 = kunit_kzalloc(test, sizeof(*fb1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb1);
+ list_add(&fb1->filp_head, &file_priv->fbs);
+ kref_init(&fb1->base.refcount);
+ fb1->dev = dev;
+ fb1->base.free_cb = drm_framebuffer_fb_release_remove_mock;
+ fb1->base.id = 1;
+
+ fb2 = kunit_kzalloc(test, sizeof(*fb2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb2);
+ list_add(&fb2->filp_head, &file_priv->fbs);
+ kref_init(&fb2->base.refcount);
+ fb2->dev = dev;
+ fb2->base.free_cb = drm_framebuffer_fb_release_remove_mock;
+ fb2->base.id = 2;
+
+ plane1 = kunit_kzalloc(test, sizeof(*plane1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane1);
+ list_add(&plane1->head, &dev->mode_config.plane_list);
+ drm_modeset_lock_init(&plane1->mutex);
+ plane1->dev = dev;
+ plane1->funcs = &plane_funcs;
+ plane1->base.id = 3;
+
+ plane2 = kunit_kzalloc(test, sizeof(*plane2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane2);
+ list_add(&plane2->head, &dev->mode_config.plane_list);
+ drm_modeset_lock_init(&plane2->mutex);
+ plane2->dev = dev;
+ plane2->funcs = &plane_funcs;
+ plane2->base.id = 4;
+
+ crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+ list_add(&crtc->head, &dev->mode_config.crtc_list);
+ drm_modeset_lock_init(&crtc->mutex);
+ crtc->dev = dev;
+ crtc->funcs = &crtc_funcs;
+ crtc->base.id = 5;
+
+ /*
+ * Attach fb2 to some planes to stress the case where we have more than
+ * one reference to the fb. plane1 is attached to crtc as primary plane
+ * and plane2 will represent any non-primary plane, allowing to cover
+ * all codepaths on legacy_remove_fb
+ */
+ crtc->primary = plane1;
+ plane1->crtc = crtc;
+ plane1->fb = fb2;
+ plane2->fb = fb2;
+ /* Each plane holds one reference to fb */
+ drm_framebuffer_get(fb2);
+ drm_framebuffer_get(fb2);
+
+ drm_fb_release(file_priv);
+
+ KUNIT_EXPECT_TRUE(test, list_empty(&file_priv->fbs));
+
+ /* Every object from this test should be released */
+ for (int i = 0; i < 5; i++)
+ KUNIT_EXPECT_EQ(test, obj_released[i], 1);
+
+ KUNIT_EXPECT_FALSE(test, kref_read(&fb1->base.refcount));
+ KUNIT_EXPECT_FALSE(test, kref_read(&fb2->base.refcount));
+
+ KUNIT_EXPECT_PTR_EQ(test, plane1->crtc, NULL);
+ KUNIT_EXPECT_PTR_EQ(test, plane1->fb, NULL);
+ KUNIT_EXPECT_PTR_EQ(test, plane1->old_fb, NULL);
+ KUNIT_EXPECT_PTR_EQ(test, plane2->crtc, NULL);
+ KUNIT_EXPECT_PTR_EQ(test, plane2->fb, NULL);
+ KUNIT_EXPECT_PTR_EQ(test, plane2->old_fb, NULL);
+}
+
static struct kunit_case drm_framebuffer_tests[] = {
+ KUNIT_CASE(drm_test_fb_release),
KUNIT_CASE(drm_test_framebuffer_addfb2),
KUNIT_CASE_PARAM(drm_test_framebuffer_check_src_coords, check_src_coords_gen_params),
KUNIT_CASE(drm_test_framebuffer_cleanup),
Add a single KUnit test case for the drm_fb_release function, which also implicitly test the static legacy_remove_fb function. Signed-off-by: Carlos Eduardo Gallo Filho <gcarlos@disroot.org> --- v2: - Rely on drm_kunit_helper_alloc_device() for mock initialization. --- drivers/gpu/drm/tests/drm_framebuffer_test.c | 142 +++++++++++++++++++ 1 file changed, 142 insertions(+)