From: Alex Hung <[email protected]> [WHAT] Add KUnit tests for the per-source IRQ state callbacks (HPD, pflip, CRTC, vline0, vupdate, dmub outbox/trace), the immediate and scheduled IRQ work paths, and amdgpu_dm_outbox_init.
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 | 28 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 35 ++ .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 582 ++++++++++++++++++ 3 files changed, 636 insertions(+), 9 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 ee74b212aebf..5af7f0bebdb8 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 @@ -594,7 +594,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_irq_resume_late); * amdgpu_dm_irq_schedule_work - schedule all work items registered for the * "irq_source". */ -static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; @@ -643,12 +643,13 @@ static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, irq_source); } } +EXPORT_IF_KUNIT(amdgpu_dm_irq_schedule_work); /* * amdgpu_dm_irq_immediate_work * Callback high irq work immediately, don't send to work queue */ -static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, enum dc_irq_source irq_source) { struct amdgpu_dm_irq_handler_data *handler_data; @@ -667,6 +668,7 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); /** * amdgpu_dm_irq_handler - Generic DM IRQ handler @@ -719,7 +721,7 @@ STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int } EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source); -static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -730,6 +732,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, src, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); static inline int dm_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, @@ -765,7 +768,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, return 0; } -static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -778,8 +781,9 @@ static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, IRQ_TYPE_PFLIP, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_pflip_irq_state); -static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -792,8 +796,9 @@ static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VBLANK, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_crtc_irq_state); -static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -806,8 +811,9 @@ static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VLINE0, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vline0_irq_state); -static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -818,8 +824,9 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); -static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int crtc_id, enum amdgpu_interrupt_state state) @@ -832,8 +839,9 @@ static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, IRQ_TYPE_VUPDATE, __func__); } +EXPORT_IF_KUNIT(amdgpu_dm_set_vupdate_irq_state); -static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, +STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, unsigned int type, enum amdgpu_interrupt_state state) @@ -844,6 +852,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, dc_interrupt_set(adev->dm.dc, irq_source, st); return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { .set = amdgpu_dm_set_crtc_irq_state, @@ -911,6 +920,7 @@ void amdgpu_dm_outbox_init(struct amdgpu_device *adev) DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } +EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); /** * amdgpu_dm_hpd_init - hpd setup callback. 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 bccb5d354a9f..15e024947970 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 @@ -123,9 +123,44 @@ int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct amdgpu_irq_src; +enum amdgpu_interrupt_state; + enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2); const char *dmub_notification_type_str(enum dmub_notification_type e); +int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state); +void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); +void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source); #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 7ea6f93ca998..7537d30cd983 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 @@ -33,6 +33,104 @@ static void dm_test_irq_handler_alt(void *arg) { } +static void dm_test_irq_handler_count(void *arg) +{ + int *count = arg; + + if (count) + (*count)++; +} + +static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) +{ + struct dc_context *ctx; + struct dc *dc; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc->ctx = ctx; + ctx->dc = dc; + + return dc; +} + +static enum dc_irq_source dm_test_to_dal_irq_source_dcn10( + struct irq_service *irq_service, + uint32_t src_id, + uint32_t ext_id) +{ + switch (src_id) { + case DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP: + return DC_IRQ_SOURCE_VBLANK1; + case DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT: + return DC_IRQ_SOURCE_VUPDATE1; + case DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT: + return DC_IRQ_SOURCE_PFLIP1; + case DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT: + return DC_IRQ_SOURCE_DMCUB_OUTBOX; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static const struct irq_service_funcs dm_test_irq_service_funcs_dcn10 = { + .to_dal_irq_source = dm_test_to_dal_irq_source_dcn10 +}; + +static bool dm_test_irq_src_set(struct irq_service *irq_service, + const struct irq_source_info *info, bool enable) +{ + return true; +} + +static bool dm_test_irq_src_ack(struct irq_service *irq_service, + const struct irq_source_info *info) +{ + return true; +} + +/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +static struct irq_source_info_funcs dm_test_irq_src_funcs = { + .set = dm_test_irq_src_set, + .ack = dm_test_irq_src_ack, +}; + +static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, + const struct irq_service_funcs *funcs) +{ + struct irq_source_info *info; + struct resource_pool *res_pool; + struct irq_service *irqs; + struct dc *dc; + int i; + + dc = dm_test_alloc_dc_with_ctx(test); + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); + + /* + * Populate the per-source info table so dc_interrupt_set()/_ack() + * succeed without touching hardware registers. + */ + info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); + for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) + info[i].funcs = &dm_test_irq_src_funcs; + + irqs->funcs = funcs; + irqs->info = info; + res_pool->irqs = irqs; + dc->res_pool = res_pool; + + return dc; +} + static void dm_test_crtc_list_del(void *data) { struct amdgpu_crtc *acrtc = data; @@ -1179,6 +1277,462 @@ static void dm_test_hpd_rx_irq_work_suspend_flushes(struct kunit *test) amdgpu_dm_hpd_rx_irq_work_suspend(&adev->dm); } +/* Tests for CRTC-based irq state callbacks (no-CRTC early return) */ + +/** + * dm_test_set_crtc_irq_state_no_crtc - Test crtc irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* mode_info.crtcs[0] is NULL -> returns 0 without dereferencing DC. */ + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_no_crtc - Test pflip irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_no_crtc - Test vline0 irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_no_crtc - Test vupdate irq state with missing CRTC + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_no_crtc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for CRTC-based irq state callbacks (dm_irq_state happy path) */ + +/** + * dm_test_set_crtc_irq_state_otg_disabled - Test crtc irq state with disabled OTG + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_otg_disabled(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* otg_inst == -1 short-circuits before computing the irq source. */ + acrtc->otg_inst = -1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_enable - Test crtc irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_crtc_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* + * otg_inst >= 0 computes the irq source and reaches the NULL-safe + * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + */ + acrtc->otg_inst = 3; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_pflip_irq_state_disable - Test pflip irq state reaches DC (disable) + * @test: The KUnit test context + */ +static void dm_test_set_pflip_irq_state_disable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + /* The disable state exercises the st == false path. */ + acrtc->otg_inst = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_pflip_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/** + * dm_test_set_vline0_irq_state_enable - Test vline0 irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vline0_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vline0_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_vupdate_irq_state_enable - Test vupdate irq state reaches DC (enable) + * @test: The KUnit test context + */ +static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->otg_inst = 2; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_vupdate_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/** + * dm_test_set_crtc_irq_state_allows_idle - Test the idle-optimization branch + * @test: The KUnit test context + * + * With a non-NULL DC that advertises IPS support and currently allows idle + * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before + * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let dc_interrupt_set() succeed. + */ +static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dal_logger *logger; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + dc = dm_test_alloc_dc_with_irq_service(test, &dm_test_irq_service_funcs_dcn10); + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + dc->caps.ips_support = true; + dc->idle_optimizations_allowed = true; + /* Keep dc_allow_idle_optimizations() a safe early return. */ + dc->debug.disable_idle_power_optimizations = true; + adev->dm.dc = dc; + + acrtc->otg_inst = 0; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_crtc_irq_state(adev, NULL, 0, AMDGPU_IRQ_STATE_ENABLE), 0); +} + +/* Tests for amdgpu_dm_irq_immediate_work() */ + +/** + * dm_test_irq_immediate_work_empty - Test immediate work on empty high table + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + 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); + + /* No registered high-context handlers: must be a safe no-op. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_immediate_work_invokes_handler - Test immediate work calls handler + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + 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); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* High-context handlers are invoked synchronously, in-place. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD1); + KUNIT_EXPECT_EQ(test, count, 1); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, dm_test_irq_handler_count); +} + +/** + * dm_test_irq_immediate_work_invokes_all - Test immediate work calls all handlers + * @test: The KUnit test context + */ +static void dm_test_irq_immediate_work_invokes_all(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + 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); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD2; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Both registered high-context handlers must run. */ + amdgpu_dm_irq_immediate_work(adev, DC_IRQ_SOURCE_HPD2); + KUNIT_EXPECT_EQ(test, count, 2); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, dm_test_irq_handler_count); +} + +/* Tests for amdgpu_dm_irq_schedule_work() */ + +/** + * dm_test_irq_schedule_work_empty - Test schedule work on empty low table + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + + 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); + + /* Empty handler list: schedule_work returns immediately. */ + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_irq_schedule_work_queues_handler - Test schedule work runs handler + * @test: The KUnit test context + */ +static void dm_test_irq_schedule_work_queues_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + int count = 0; + void *handler; + + 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); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + /* + * Low-context work runs asynchronously on system_highpri_wq. + * amdgpu_dm_irq_fini() flushes each pending work item before freeing + * the handlers, so the handler is guaranteed to have run afterwards. + */ + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 1); +} + +/** + * dm_test_irq_schedule_work_requeue_fallback - Test the re-queue fallback path + * @test: The KUnit test context + * + * The first schedule queues the handler's work item. Issuing a second + * schedule before the work has run makes queue_work() fail for the + * still-pending item, forcing amdgpu_dm_irq_schedule_work() into the fallback + * that allocates and queues a fresh handler copy. Both work items run when + * amdgpu_dm_irq_fini() flushes the queue, so the handler fires twice. + */ +static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test) +{ + struct dc_interrupt_params int_params = { 0 }; + struct amdgpu_device *adev; + int count = 0; + void *handler; + + 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); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_count, &count); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); + + amdgpu_dm_irq_fini(adev); + KUNIT_EXPECT_EQ(test, count, 2); +} + +/* Tests for amdgpu_dm_set_hpd_irq_state() */ + +/** + * dm_test_set_hpd_irq_state_null_dc - Test HPD irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* dc_interrupt_set() is a no-op when dc is NULL, so both states + * return 0 without dereferencing the (absent) DC. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_outbox_irq_state() */ + +/** + * dm_test_set_dmub_outbox_irq_state_null_dc - Test outbox irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_outbox_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_outbox_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_set_dmub_trace_irq_state() */ + +/** + * dm_test_set_dmub_trace_irq_state_null_dc - Test trace irq state with no DC + * @test: The KUnit test context + */ +static void dm_test_set_dmub_trace_irq_state_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_ENABLE), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_set_dmub_trace_irq_state(adev, NULL, 0, + AMDGPU_IRQ_STATE_DISABLE), 0); +} + +/* Tests for amdgpu_dm_outbox_init() */ + +/** + * dm_test_outbox_init_null_dc - Test outbox init is a safe no-op with no DC + * @test: The KUnit test context + */ +static void dm_test_outbox_init_null_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + amdgpu_dm_outbox_init(adev); +} + 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), @@ -1247,6 +1801,34 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { /* amdgpu_dm_hpd_rx_irq_work_suspend */ KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_null), KUNIT_CASE(dm_test_hpd_rx_irq_work_suspend_flushes), + /* CRTC-based irq state callbacks (no-CRTC early return) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_pflip_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vline0_irq_state_no_crtc), + KUNIT_CASE(dm_test_set_vupdate_irq_state_no_crtc), + /* CRTC-based irq state callbacks (dm_irq_state happy path) */ + KUNIT_CASE(dm_test_set_crtc_irq_state_otg_disabled), + KUNIT_CASE(dm_test_set_crtc_irq_state_enable), + KUNIT_CASE(dm_test_set_pflip_irq_state_disable), + KUNIT_CASE(dm_test_set_vline0_irq_state_enable), + KUNIT_CASE(dm_test_set_vupdate_irq_state_enable), + KUNIT_CASE(dm_test_set_crtc_irq_state_allows_idle), + /* amdgpu_dm_irq_immediate_work */ + KUNIT_CASE(dm_test_irq_immediate_work_empty), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_handler), + KUNIT_CASE(dm_test_irq_immediate_work_invokes_all), + /* amdgpu_dm_irq_schedule_work */ + KUNIT_CASE(dm_test_irq_schedule_work_empty), + KUNIT_CASE(dm_test_irq_schedule_work_queues_handler), + KUNIT_CASE(dm_test_irq_schedule_work_requeue_fallback), + /* amdgpu_dm_set_hpd_irq_state */ + KUNIT_CASE(dm_test_set_hpd_irq_state_null_dc), + /* amdgpu_dm_set_dmub_outbox_irq_state */ + KUNIT_CASE(dm_test_set_dmub_outbox_irq_state_null_dc), + /* amdgpu_dm_set_dmub_trace_irq_state */ + KUNIT_CASE(dm_test_set_dmub_trace_irq_state_null_dc), + /* amdgpu_dm_outbox_init */ + KUNIT_CASE(dm_test_outbox_init_null_dc), {} }; -- 2.55.0
