From: Alex Hung <[email protected]> [WHAT] Add KUnit coverage for the two remaining uncovered functions in amdgpu_dm_irq.c: amdgpu_dm_irq_handler() and dm_handle_vmin_vmax_update().
amdgpu_dm_irq_handler() is driven with a fake dc whose irq_service maps the hardware IRQ entry to a DC source and whose per-source info table lets dc_interrupt_ack() succeed; high- and low-context counting handlers verify the immediate and scheduled dispatch paths. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha <[email protected]> Signed-off-by: Alex Hung <[email protected]> Signed-off-by: George Zhang <[email protected]> --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 6 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 125 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index dab970e064ff..32caba06927a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -679,7 +679,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); * Calls all registered high irq work immediately, and schedules work for low * irq. The DM IRQ table is used to find the corresponding handlers. */ -static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, struct amdgpu_irq_src *source, struct amdgpu_iv_entry *entry) { @@ -699,6 +699,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_handler); STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { @@ -1874,7 +1875,7 @@ STATIC_IFN_KUNIT void dm_pflip_high_irq(void *interrupt_params) } EXPORT_IF_KUNIT(dm_pflip_high_irq); -static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) +STATIC_IFN_KUNIT void dm_handle_vmin_vmax_update(struct work_struct *offload_work) { struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); struct amdgpu_device *adev = work->adev; @@ -1889,6 +1890,7 @@ static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) kfree(work->adjust); kfree(work); } +EXPORT_IF_KUNIT(dm_handle_vmin_vmax_update); static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, struct dc_stream_state *stream, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index da698f224d85..4c200a9614a7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -124,6 +124,7 @@ int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) struct amdgpu_irq_src; +struct amdgpu_iv_entry; enum amdgpu_interrupt_state; enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); @@ -178,6 +179,10 @@ void dm_vupdate_high_irq(void *interrupt_params); void dm_crtc_high_irq(void *interrupt_params); void dm_handle_hpd_work(struct work_struct *work); void dm_dmub_outbox1_low_irq(void *interrupt_params); +int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); +void dm_handle_vmin_vmax_update(struct work_struct *offload_work); #endif #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index c356c2c836b4..5a4fecdf3135 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -3802,6 +3802,127 @@ static void dm_test_register_outbox_irq_handlers_with_dmub(struct kunit *test) amdgpu_dm_irq_fini(adev); } +/* Tests for amdgpu_dm_irq_handler() */ + +/** + * dm_test_irq_handler_dispatches_work - Test the top-level IRQ handler + * @test: The KUnit test context + * + * amdgpu_dm_irq_handler() translates the hardware IRQ entry to a DC IRQ + * source, acknowledges it, then dispatches to the high-context (immediate) + * and low-context (scheduled) handler lists. A fake dc with a stubbed + * irq_service maps the source id to DC_IRQ_SOURCE_VBLANK1 and lets the ack + * succeed without touching hardware registers. + */ +static void dm_test_irq_handler_dispatches_work(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_iv_entry entry = { 0 }; + struct amdgpu_device *adev; + int high_count = 0; + int low_count = 0; + void *handler; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + dc = dm_test_alloc_dc_with_irq_service(test, + &dm_test_irq_service_funcs_dce110); + adev->dm.dc = dc; + + /* High-context (immediate) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_VBLANK1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &high_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Low-context (scheduled) handler on VBLANK1. */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, + &low_count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* src_id maps to DC_IRQ_SOURCE_VBLANK1 via the dce110 stub. */ + entry.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0; + entry.src_data[0] = 0; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_handler(adev, NULL, &entry), 0); + + /* High-context handler runs synchronously in-place. */ + KUNIT_EXPECT_EQ(test, high_count, 1); + + /* + * Low-context work runs asynchronously; amdgpu_dm_irq_fini() flushes + * each pending work item before freeing, so it has run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, low_count, 1); +} + +/* Tests for dm_handle_vmin_vmax_update() */ + +/** + * dm_test_handle_vmin_vmax_update - Test the deferred vmin/vmax worker + * @test: The KUnit test context + * + * The worker applies the cached timing adjust to the stream via + * dc_stream_adjust_vmin_vmax(), drops the stream reference taken when the + * work was scheduled, and frees the work and its adjust copy. A fake dc with + * a current_state lets the adjust walk an empty pipe list without touching + * hardware. The work and adjust are kmalloc'd because the worker frees them. + */ +static void dm_test_handle_vmin_vmax_update(struct kunit *test) +{ + struct dc_crtc_timing_adjust *adjust; + struct vupdate_offload_work *work; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + mutex_init(&adev->dm.dc_lock); + + dc = dm_test_alloc_dc_with_ctx(test); + dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state); + adev->dm.dc = dc; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + /* + * Start at two references: the worker's dc_stream_release() drops one, + * leaving the kunit-managed allocation intact (no kfree). + */ + kref_init(&stream->refcount); + kref_get(&stream->refcount); + + /* The worker kfree()s both, so they must come from the slab. */ + work = kzalloc(sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); + adjust = kzalloc(sizeof(*adjust), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adjust); + + work->adev = adev; + work->stream = stream; + work->adjust = adjust; + adjust->v_total_min = 1000; + adjust->v_total_max = 1100; + + dm_handle_vmin_vmax_update(&work->work); + + /* The adjust was applied and one stream reference was dropped. */ + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_min, 1000); + KUNIT_EXPECT_EQ(test, stream->adjust.v_total_max, 1100); + KUNIT_EXPECT_EQ(test, kref_read(&stream->refcount), 1); +} + static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_to_dal_irq_source */ KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), @@ -3958,6 +4079,10 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_dcn10_register_irq_handlers_one_crtc), KUNIT_CASE(dm_test_register_outbox_irq_handlers_without_dmub), KUNIT_CASE(dm_test_register_outbox_irq_handlers_with_dmub), + /* amdgpu_dm_irq_handler */ + KUNIT_CASE(dm_test_irq_handler_dispatches_work), + /* dm_handle_vmin_vmax_update */ + KUNIT_CASE(dm_test_handle_vmin_vmax_update), {} }; -- 2.55.0
