drm/amd/display: Test irq state setters and work

[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 <bhawanpreet.lakha@amd.com>
Signed-off-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: George Zhang <george.zhang@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
Alex Hung 2026-06-22 23:50:51 -06:00 committed by Alex Deucher
parent 80e4717b46
commit a9011c014c
3 changed files with 636 additions and 9 deletions

View File

@ -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.

View File

@ -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__ */

View File

@ -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),
{}
};