From: Alex Hung <[email protected]> [WHAT] Add KUnit tests for the DMUB HPD notification callbacks (dmub_hpd_callback, dmub_hpd_sense_callback) and for amdgpu_dm_register_hpd_handlers().
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 | 7 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 4 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 418 ++++++++++++++++++ 3 files changed, 427 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 c169f891470a..e34bd3bbc2b8 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 @@ -1585,7 +1585,7 @@ EXPORT_IF_KUNIT(handle_hpd_rx_irq); * Dmub Hpd interrupt processing callback. Gets displayindex through the * ink index and calls helper to do the processing. */ -static void dmub_hpd_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { struct amdgpu_dm_connector *aconnector; @@ -1651,6 +1651,7 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, } } } +EXPORT_IF_KUNIT(dmub_hpd_callback); /** * dmub_hpd_sense_callback - DMUB HPD sense processing callback. @@ -1660,11 +1661,12 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, * HPD sense changes can occur during low power states and need to be * notified from firmware to driver. */ -static void dmub_hpd_sense_callback(struct amdgpu_device *adev, +STATIC_IFN_KUNIT void dmub_hpd_sense_callback(struct amdgpu_device *adev, struct dmub_notification *notify) { drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); } +EXPORT_IF_KUNIT(dmub_hpd_sense_callback); int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) { @@ -1742,6 +1744,7 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) } return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_register_hpd_handlers); /* ========== IRQ handlers ========== */ struct amdgpu_crtc * 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 e01b0856f91f..7200d3754905 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 @@ -169,6 +169,10 @@ void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, union hpd_irq_data hpd_irq_data); void handle_hpd_rx_irq(void *param); +void dmub_hpd_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dmub_hpd_sense_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); #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 fa161d0ecfd9..eb74883cb6d9 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 @@ -2812,6 +2812,411 @@ static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test) ->dm.hpd_rx_offload_wq->is_handling_link_loss); } +/* Tests for dmub_hpd_callback()/dmub_hpd_sense_callback() */ + +/** + * dm_test_dmub_hpd_callback_null_inputs - Test DMUB callback null inputs + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_null_inputs(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + dmub_hpd_callback(NULL, NULL); + dmub_hpd_callback(adev, NULL); +} + +/** + * dm_test_dmub_hpd_callback_invalid_index - Test DMUB callback index check + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_invalid_index(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + notify.link_index = 1; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_empty_connectors - Test DMUB callback without match + * @test: The KUnit test context + */ +static void dm_test_dmub_hpd_callback_empty_connectors(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); + dmub_hpd_sense_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_unknown_type_match - Test DMUB callback matched + * connector, unknown type + * @test: The KUnit test context + * + * A writeback connector must be skipped (continue) and a non-writeback + * connector whose dc_link matches the notification link must be selected. + * An unrecognized notification type takes the "unknown" warn branch, and the + * post-loop dispatch is a no-op (neither HPD nor HPD_IRQ), so no real DC/MST + * handler runs. + */ +static void dm_test_dmub_hpd_callback_unknown_type_match(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct drm_connector *wbconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + dc->links[0] = link; + + /* A writeback connector must be skipped by the loop. */ + wbconn = kunit_kzalloc(test, sizeof(*wbconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wbconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wbconn, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_connector_cleanup, wbconn), 0); + + /* A DisplayPort connector whose dc_link matches drives the match branch. */ + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_SENSE_NOTIFY; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_type - Test DMUB callback HPD type dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD logs the + * HPD callback and dispatches to handle_hpd_irq_helper(). Detection is stubbed + * to report no connection and link detect to fail, so the connected branches + * are skipped and no real DC/DRM hotplug path runs. + */ +static void dm_test_dmub_hpd_callback_hpd_type(struct kunit *test) +{ + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + 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); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->detect_connection_type = dm_test_detect_connection_none; + link_srv->detect_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD; + + dmub_hpd_callback(adev, ¬ify); +} + +/** + * dm_test_dmub_hpd_callback_hpd_irq_type - Test DMUB callback HPD_IRQ dispatch + * @test: The KUnit test context + * + * A matched connector with notification type DMUB_NOTIFICATION_HPD_IRQ logs + * the HPD RX callback and dispatches to handle_hpd_rx_irq(). The HPD RX handler + * stub reports no left work, so the function returns through the early goto + * without scheduling offload work; an MST-branch link skips the trailing + * drm_dp_cec_irq(). + */ +static void dm_test_dmub_hpd_callback_hpd_irq_type(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct dmub_notification notify = { 0 }; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + spin_lock_init(&offload_wq->offload_lock); + adev->dm.hpd_rx_offload_wq = offload_wq; + + 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); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->link_index = 0; + link->type = dc_connection_mst_branch; + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + dc->link_count = 1; + dc->links[0] = link; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + aconn->dc_link = link; + + notify.link_index = 0; + notify.type = DMUB_NOTIFICATION_HPD_IRQ; + + dmub_hpd_callback(adev, ¬ify); +} + +/* Tests for amdgpu_dm_register_hpd_handlers() */ + +/** + * dm_test_register_hpd_handlers_empty - Test HPD registration with no connectors + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_empty(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); +} + +/** + * dm_test_register_hpd_handlers_valid - Test HPD/HPDRX registration succeeds + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_valid(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1RX; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1RX])); + + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd - Test invalid HPD source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_HPD1RX; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_INVALID; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_invalid_hpd_rx - Test invalid HPDRX source fails + * @test: The KUnit test context + */ +static void dm_test_register_hpd_handlers_invalid_hpd_rx(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + + aconn = kunit_kzalloc(test, sizeof(*aconn), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconn->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, dm_test_connector_cleanup, &aconn->base), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->irq_source_hpd = DC_IRQ_SOURCE_INVALID; + link->irq_source_hpd_rx = DC_IRQ_SOURCE_HPD1; + aconn->dc_link = link; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), -EINVAL); + amdgpu_dm_irq_fini(adev); +} + +/** + * dm_test_register_hpd_handlers_dmub_outbox - Test DMUB outbox callbacks register + * @test: The KUnit test context + * + * Enables DMUB outbox support so dc_is_dmub_outbox_supported() returns true, + * exercising the dm_register_dmub_notify_callback() block. Asserts that the + * HPD, HPD_IRQ and HPD_SENSE_NOTIFY callbacks are registered. + */ +static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + dc = dm_test_alloc_dc_with_ctx(test); + adev->dm.dc = dc; + + /* Make dc_is_dmub_outbox_supported() return true. */ + dc->caps.dmcub_support = true; + dc->ctx->asic_id.chip_family = AMDGPU_FAMILY_GC_11_0_1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_register_hpd_handlers(adev), 0); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_IRQ], + (dmub_notify_interrupt_callback_t)dmub_hpd_callback); + KUNIT_EXPECT_PTR_EQ(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD_SENSE_NOTIFY], + (dmub_notify_interrupt_callback_t)dmub_hpd_sense_callback); + + amdgpu_dm_irq_fini(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), @@ -2934,6 +3339,19 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_handle_hpd_rx_irq_msg_rdy), KUNIT_CASE(dm_test_handle_hpd_rx_irq_link_loss), KUNIT_CASE(dm_test_schedule_hpd_rx_offload_work), + /* dmub_hpd_callback/dmub_hpd_sense_callback */ + KUNIT_CASE(dm_test_dmub_hpd_callback_null_inputs), + KUNIT_CASE(dm_test_dmub_hpd_callback_invalid_index), + KUNIT_CASE(dm_test_dmub_hpd_callback_empty_connectors), + KUNIT_CASE(dm_test_dmub_hpd_callback_unknown_type_match), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_type), + KUNIT_CASE(dm_test_dmub_hpd_callback_hpd_irq_type), + /* amdgpu_dm_register_hpd_handlers */ + KUNIT_CASE(dm_test_register_hpd_handlers_empty), + KUNIT_CASE(dm_test_register_hpd_handlers_valid), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd), + KUNIT_CASE(dm_test_register_hpd_handlers_invalid_hpd_rx), + KUNIT_CASE(dm_test_register_hpd_handlers_dmub_outbox), {} }; -- 2.55.0
