diff --git a/drivers/gpu/drm/tests/drm_framebuffer_test.c b/drivers/gpu/drm/tests/drm_framebuffer_test.c
index 149e1985e53f..70b14e05dc83 100644
--- a/drivers/gpu/drm/tests/drm_framebuffer_test.c
+++ b/drivers/gpu/drm/tests/drm_framebuffer_test.c
@@ -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),
