summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorBhawanpreet Lakha <bhawanpreet.lakha@amd.com>2026-07-15 12:20:30 -0400
committerAlex Deucher <alexander.deucher@amd.com>2026-08-06 09:58:51 -0400
commitbed804fe0b49d67bae577b1833831c49ff7e490d (patch)
treee55a150c2bc82de793f6f06dfc0fa22d955355a3 /drivers
parentd1db1a99fceaf79e520977323f8b2238b566eeff (diff)
downloadlinux-bed804fe0b49d67bae577b1833831c49ff7e490d.tar.gz
linux-bed804fe0b49d67bae577b1833831c49ff7e490d.zip
drm/amd/display: Add vblank handling tests for crtc
Add KUnit coverage for the CRTC vblank paths: - amdgpu_dm_crtc_handle_vblank: no-event completion and the AMDGPU_FLIP_SUBMITTED guard that keeps a pending event pending. - amdgpu_dm_crtc_vblank_control_worker: enable increments, disable decrements, and disable clamps the active vblank IRQ count at zero. - amdgpu_dm_crtc_disable_vblank: disable path returns cleanly when the IRQ subsystem is not installed. Expose amdgpu_dm_crtc_vblank_control_worker for KUnit via STATIC_IFN_KUNIT/EXPORT_IF_KUNIT and declare it in the header. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung <alex.hung@amd.com> Signed-off-by: Bhawanpreet Lakha <bhawanpreet.lakha@amd.com> Signed-off-by: Roman Li <roman.li@amd.com> Tested-by: Dan Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c5
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h1
-rw-r--r--drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c213
3 files changed, 218 insertions, 1 deletions
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
index ce42102109bb..2a4e241350f0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
@@ -59,6 +59,7 @@ void amdgpu_dm_crtc_handle_vblank(struct amdgpu_crtc *acrtc)
spin_unlock_irqrestore(&dev->event_lock, flags);
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_handle_vblank);
bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state,
struct dc_stream_state *new_stream,
@@ -216,7 +217,7 @@ struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev)
}
EXPORT_IF_KUNIT(idle_create_workqueue);
-static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work)
+STATIC_IFN_KUNIT void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work)
{
struct vblank_control_work *vblank_work =
container_of(work, struct vblank_control_work, work);
@@ -241,6 +242,7 @@ static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work)
kfree(vblank_work);
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_vblank_control_worker);
static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
{
@@ -400,6 +402,7 @@ void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc)
{
amdgpu_dm_crtc_set_vblank(crtc, false);
}
+EXPORT_IF_KUNIT(amdgpu_dm_crtc_disable_vblank);
STATIC_IFN_KUNIT void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
index ed6e8021d69e..c68b7b2e1916 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h
@@ -52,6 +52,7 @@ struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc);
void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc);
void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
struct drm_crtc_state *new_crtc_state);
+void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work);
#endif
bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
index ce20692959d0..4d4b4061c789 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c
@@ -9,6 +9,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_kunit_helpers.h>
+#include <drm/drm_vblank.h>
#include "dc.h"
#include "amdgpu.h"
@@ -621,6 +622,209 @@ static void dm_test_crtc_destroy_state_no_stream(struct kunit *test)
amdgpu_dm_crtc_destroy_state(NULL, &dm_state->base);
}
+/**
+ * dm_test_crtc_handle_vblank_no_event - Test vblank handling with no pending event
+ * @test: The KUnit test context
+ *
+ * With no flip event pending, handling a vblank must complete without sending a
+ * vblank event and must leave acrtc->event untouched (NULL).
+ */
+static void dm_test_crtc_handle_vblank_no_event(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct amdgpu_crtc *acrtc;
+
+ adev = dm_kunit_alloc_adev(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ /* Initialise vblank so drm_crtc_handle_vblank() runs cleanly. */
+ KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+ acrtc->base.dev = &adev->ddev;
+ acrtc->event = NULL;
+
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ KUNIT_EXPECT_NULL(test, acrtc->event);
+}
+
+/**
+ * dm_test_crtc_handle_vblank_skips_when_flip_submitted - Test event kept on submit
+ * @test: The KUnit test context
+ *
+ * A pending event whose flip is still AMDGPU_FLIP_SUBMITTED must not be signalled
+ * on vblank; acrtc->event must remain set for later completion.
+ */
+static void dm_test_crtc_handle_vblank_skips_when_flip_submitted(struct kunit *test)
+{
+ struct drm_pending_vblank_event *event;
+ struct amdgpu_device *adev;
+ struct amdgpu_crtc *acrtc;
+
+ adev = dm_kunit_alloc_adev(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ KUNIT_ASSERT_EQ(test, drm_vblank_init(&adev->ddev, 1), 0);
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+ event = kunit_kzalloc(test, sizeof(*event), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event);
+
+ acrtc->base.dev = &adev->ddev;
+ acrtc->event = event;
+ acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED;
+
+ amdgpu_dm_crtc_handle_vblank(acrtc);
+
+ /* Flip still in-flight: event must be preserved, not signalled. */
+ KUNIT_EXPECT_PTR_EQ(test, acrtc->event, event);
+}
+
+/**
+ * dm_test_vblank_control_worker_setup - Build a vblank_control_work for the worker
+ * @test: The KUnit test context
+ * @enable: Value for vblank_work->enable
+ * @count: Initial dm->active_vblank_irq_count
+ *
+ * Returns a work item wired to a freshly allocated adev/crtc/stream. The CRTC is
+ * left without an atomic state so amdgpu_dm_ism_commit_event() short-circuits and
+ * only the vblank IRQ accounting in the worker runs.
+ */
+static struct vblank_control_work *
+dm_test_vblank_control_worker_setup(struct kunit *test, bool enable,
+ uint32_t count)
+{
+ struct dc_stream_state *stream;
+ struct vblank_control_work *work;
+ struct amdgpu_device *adev;
+ struct amdgpu_crtc *acrtc;
+
+ adev = dm_kunit_alloc_adev(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ mutex_init(&adev->dm.dc_lock);
+ adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc);
+ adev->dm.active_vblank_irq_count = count;
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+ acrtc->base.dev = &adev->ddev;
+ acrtc->base.state = NULL;
+
+ stream = dm_kunit_alloc_stream(test, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+ /* Worker releases the stream; keep an extra ref so kunit owns the free. */
+ kref_get(&stream->refcount);
+
+ /* Worker kfree()s the work item, so it must be a plain allocation. */
+ work = kzalloc_obj(*work, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work);
+ work->dm = &adev->dm;
+ work->acrtc = acrtc;
+ work->stream = stream;
+ work->enable = enable;
+
+ return work;
+}
+
+/**
+ * dm_test_vblank_control_worker_enable_increments - Test enable bumps IRQ count
+ * @test: The KUnit test context
+ *
+ * Running the worker with enable set must increment the active vblank IRQ count.
+ */
+static void dm_test_vblank_control_worker_enable_increments(struct kunit *test)
+{
+ struct vblank_control_work *work;
+ struct amdgpu_display_manager *dm;
+
+ work = dm_test_vblank_control_worker_setup(test, true, 0);
+ dm = work->dm;
+
+ amdgpu_dm_crtc_vblank_control_worker(&work->work);
+
+ KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 1);
+}
+
+/**
+ * dm_test_vblank_control_worker_disable_decrements - Test disable drops IRQ count
+ * @test: The KUnit test context
+ *
+ * Running the worker with enable clear must decrement a non-zero active vblank
+ * IRQ count.
+ */
+static void dm_test_vblank_control_worker_disable_decrements(struct kunit *test)
+{
+ struct vblank_control_work *work;
+ struct amdgpu_display_manager *dm;
+
+ work = dm_test_vblank_control_worker_setup(test, false, 2);
+ dm = work->dm;
+
+ amdgpu_dm_crtc_vblank_control_worker(&work->work);
+
+ KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 1);
+}
+
+/**
+ * dm_test_vblank_control_worker_disable_clamps_zero - Test disable clamps at zero
+ * @test: The KUnit test context
+ *
+ * Disabling when the active vblank IRQ count is already zero must not underflow.
+ */
+static void dm_test_vblank_control_worker_disable_clamps_zero(struct kunit *test)
+{
+ struct vblank_control_work *work;
+ struct amdgpu_display_manager *dm;
+
+ work = dm_test_vblank_control_worker_setup(test, false, 0);
+ dm = work->dm;
+
+ amdgpu_dm_crtc_vblank_control_worker(&work->work);
+
+ KUNIT_EXPECT_EQ(test, dm->active_vblank_irq_count, 0);
+}
+
+/**
+ * dm_test_crtc_disable_vblank_no_irq_installed - Test disable with IRQ uninstalled
+ * @test: The KUnit test context
+ *
+ * Disabling vblank walks amdgpu_dm_crtc_set_vblank()'s disable path. With the
+ * IRQ subsystem not installed, amdgpu_irq_put() returns early so the routine
+ * completes without touching the vblank workqueue or the active IRQ count.
+ */
+static void dm_test_crtc_disable_vblank_no_irq_installed(struct kunit *test)
+{
+ struct amdgpu_device *adev;
+ struct amdgpu_crtc *acrtc;
+
+ adev = dm_kunit_alloc_adev(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev);
+
+ adev->dm.dc = dm_kunit_alloc_dc_with_ctx(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->dm.dc);
+ /* DCE_VERSION_6_0 has no VRR, so the vupdate-irq branch is skipped. */
+ adev->dm.dc->ctx->dce_version = DCE_VERSION_6_0;
+ adev->dm.active_vblank_irq_count = 0;
+
+ /* No CRTCs registered and IRQs not installed -> irq_put returns early. */
+ adev->mode_info.num_crtc = 0;
+ adev->irq.installed = false;
+
+ acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc);
+ acrtc->base.dev = &adev->ddev;
+ acrtc->crtc_id = 0;
+
+ amdgpu_dm_crtc_disable_vblank(&acrtc->base);
+
+ KUNIT_EXPECT_EQ(test, adev->dm.active_vblank_irq_count, 0);
+}
+
static struct kunit_case amdgpu_dm_crtc_tests[] = {
/* amdgpu_dm_crtc_modeset_required */
KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed),
@@ -665,6 +869,15 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = {
KUNIT_CASE(dm_test_crtc_reset_state_allocates_state),
/* amdgpu_dm_crtc_destroy_state */
KUNIT_CASE(dm_test_crtc_destroy_state_no_stream),
+ /* amdgpu_dm_crtc_handle_vblank */
+ KUNIT_CASE(dm_test_crtc_handle_vblank_no_event),
+ KUNIT_CASE(dm_test_crtc_handle_vblank_skips_when_flip_submitted),
+ /* amdgpu_dm_crtc_vblank_control_worker */
+ KUNIT_CASE(dm_test_vblank_control_worker_enable_increments),
+ KUNIT_CASE(dm_test_vblank_control_worker_disable_decrements),
+ KUNIT_CASE(dm_test_vblank_control_worker_disable_clamps_zero),
+ /* amdgpu_dm_crtc_disable_vblank */
+ KUNIT_CASE(dm_test_crtc_disable_vblank_no_irq_installed),
{}
};