From: Alex Hung <[email protected]> Add KUnit coverage for the HPD interrupt-handling helpers: the HPD-RX offload worker, the HDMI HPD debounce worker, handle_hpd_irq_helper(), handle_hpd_irq(), schedule_hpd_rx_offload_work() and handle_hpd_rx_irq(). Expose these statics for KUnit and add the stub link-service callbacks, sink helpers and fixtures the tests rely on.
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 | 16 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 8 + .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 930 ++++++++++++++++++ 3 files changed, 949 insertions(+), 5 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 8e18ed9f9501..c169f891470a 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 @@ -1075,7 +1075,7 @@ static void force_connector_state( mutex_unlock(&aconnector->hpd_lock); } -static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +STATIC_IFN_KUNIT void dm_handle_hpd_rx_offload_work(struct work_struct *work) { struct hpd_rx_irq_offload_work *offload_work; struct amdgpu_dm_connector *aconnector; @@ -1168,6 +1168,7 @@ static void dm_handle_hpd_rx_offload_work(struct work_struct *work) kfree(offload_work); } +EXPORT_IF_KUNIT(dm_handle_hpd_rx_offload_work); struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) { @@ -1309,8 +1310,9 @@ void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work) dc_allow_idle_optimizations(dc, true); } } +EXPORT_IF_KUNIT(amdgpu_dm_hdmi_hpd_debounce_work); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason) { struct drm_connector *connector = &aconnector->base; @@ -1408,16 +1410,18 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, } } } +EXPORT_IF_KUNIT(handle_hpd_irq_helper); -static void handle_hpd_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); } +EXPORT_IF_KUNIT(handle_hpd_irq); -static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, +STATIC_IFN_KUNIT 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) { struct hpd_rx_irq_offload_work *offload_work = @@ -1436,8 +1440,9 @@ static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_ queue_work(offload_wq->wq, &offload_work->work); drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); } +EXPORT_IF_KUNIT(schedule_hpd_rx_offload_work); -static void handle_hpd_rx_irq(void *param) +STATIC_IFN_KUNIT void handle_hpd_rx_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; struct drm_connector *connector = &aconnector->base; @@ -1570,6 +1575,7 @@ static void handle_hpd_rx_irq(void *param) mutex_unlock(&aconnector->hpd_lock); } +EXPORT_IF_KUNIT(handle_hpd_rx_irq); /** * dmub_hpd_callback - DMUB HPD interrupt processing 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 15e024947970..e01b0856f91f 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 @@ -161,6 +161,14 @@ 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); +void dm_handle_hpd_rx_offload_work(struct work_struct *work); +void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason); +void handle_hpd_irq(void *param); +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); #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 8c58523ef1bb..fa161d0ecfd9 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 @@ -24,6 +24,7 @@ #include "link_service.h" #include "dmub/dmub_srv.h" #include "dal_asic_id.h" +#include "../../amdgpu/amdgpu_reset.h" static void dm_test_irq_handler(void *arg) { @@ -41,6 +42,148 @@ static void dm_test_irq_handler_count(void *arg) (*count)++; } +static bool dm_test_detect_connection_none(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_none; + + return true; +} + +static bool dm_test_detect_link_false(struct dc_link *link, + enum dc_detect_reason reason) +{ + return false; +} + +static bool dm_test_detect_connection_single(struct dc_link *link, + enum dc_connection_type *type) +{ + *type = dc_connection_single; + + return true; +} + +/* Recording stubs for the dm_handle_hpd_rx_offload_work() DP-IRQ branches. */ +static int dm_test_automated_test_count; +static int dm_test_handle_link_loss_count; + +static void dm_test_dp_handle_automated_test(struct dc_link *link) +{ + dm_test_automated_test_count++; +} + +static void dm_test_dp_handle_link_loss(struct dc_link *link) +{ + dm_test_handle_link_loss_count++; +} + +static bool dm_test_dp_parse_link_loss_true(struct dc_link *link, + union hpd_irq_data *hpd_irq_dpcd_data) +{ + return true; +} + +static bool dm_test_dp_should_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + +static enum dc_status dm_test_dp_read_hpd_rx_irq_data_ok(struct dc_link *link, + union hpd_irq_data *irq_data) +{ + return DC_OK; +} + +/* + * Allocate a refcounted dc_sink for tests without pulling in the DC-core + * dc_sink_create()/dc_sink_release() symbols. Production code under test still + * uses dc_sink_retain()/dc_sink_release() (resolved inside the amdgpu module). + * Use kzalloc (not kunit_kzalloc) so the final kref_put frees it exactly once. + */ +static struct dc_sink *dm_test_sink_create(struct dc_link *link) +{ + struct dc_sink *sink = kzalloc(sizeof(*sink), GFP_KERNEL); + + if (!sink) + return NULL; + + sink->link = link; + sink->ctx = link->ctx; + kref_init(&sink->refcount); + + return sink; +} + +static void dm_test_sink_free(struct kref *kref) +{ + struct dc_sink *sink = container_of(kref, struct dc_sink, refcount); + + kfree(sink->dc_container_id); + kfree(sink); +} + +static void dm_test_sink_release(struct dc_sink *sink) +{ + kref_put(&sink->refcount, dm_test_sink_free); +} + +static bool dm_test_handle_hpd_rx_no_work(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = false; + + return false; +} + +static bool dm_test_handle_hpd_rx_automated(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_msg_rdy(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = false; + *has_left_work = true; + hpd_irq_data->bytes.device_service_irq.bits.UP_REQ_MSG_RDY = 1; + + return false; +} + +static bool dm_test_handle_hpd_rx_link_loss(struct dc_link *link, + union hpd_irq_data *hpd_irq_data, + bool *link_loss, + bool defer_handling, + bool *has_left_work) +{ + *link_loss = true; + *has_left_work = true; + + return false; +} + +static bool dm_test_allow_hpd_rx_irq_true(const struct dc_link *link) +{ + return true; +} + + static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) { struct dc_context *ctx; @@ -1902,6 +2045,773 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) amdgpu_dm_hpd_fini(adev); } +/* Tests for dm_handle_hpd_rx_offload_work() */ + +/** + * dm_test_hpd_rx_offload_work_no_connector - Test missing connector early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connector(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_no_connection - Test no connection early exit + * @test: The KUnit test context + */ +static void dm_test_hpd_rx_offload_work_no_connection(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct amdgpu_device *adev; + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv); + link_srv->detect_connection_type = dm_test_detect_connection_none; + dc->link_srv = link_srv; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + link->dc = dc; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); +} + +/** + * dm_test_hpd_rx_offload_work_automated_test - Test AUTOMATED_TEST branch + * @test: The KUnit test context + * + * With a present connection and the AUTOMATED_TEST service-IRQ bit set, the + * worker runs dc_link_dp_handle_automated_test() (stubbed via link_srv) and + * writes the test response with core_link_write_dpcd(). timing_changed is left + * false so force_connector_state() (which needs a registered DRM device) is + * skipped, and aux_access_disabled makes the DPCD write a safe no-op. + */ +static void dm_test_hpd_rx_offload_work_automated_test(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + 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; + + dm_test_automated_test_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->timing_changed = false; + offload_wq->aconnector = aconn; + + 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_single; + link_srv->dp_handle_automated_test = dm_test_dp_handle_automated_test; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->aux_access_disabled = true; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST = 1; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_automated_test_count, 1); +} + +/** + * dm_test_hpd_rx_offload_work_link_loss - Test link-loss branch + * @test: The KUnit test context + * + * With a present non-eDP connection and no service-IRQ bits set, the worker + * takes the else-if link-loss path: dc_link_check_link_loss_status() and + * dc_link_dp_allow_hpd_rx_irq() gate entry, then dc_link_dp_read_hpd_rx_irq_data() + * returns DC_OK and a second link-loss check triggers dc_link_dp_handle_link_loss(). + * All four are stubbed via link_srv. + */ +static void dm_test_hpd_rx_offload_work_link_loss(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct hpd_rx_irq_offload_work *offload_work; + 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; + + dm_test_handle_link_loss_count = 0; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + adev->reset_domain = kunit_kzalloc(test, sizeof(*adev->reset_domain), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->reset_domain); + + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + offload_wq->aconnector = aconn; + + 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_single; + link_srv->dp_parse_link_loss_status = dm_test_dp_parse_link_loss_true; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_dp_should_allow_hpd_rx_irq_true; + link_srv->dp_read_hpd_rx_irq_data = dm_test_dp_read_hpd_rx_irq_data_ok; + link_srv->dp_handle_link_loss = dm_test_dp_handle_link_loss; + dc->link_srv = link_srv; + dc->ctx = ctx; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + aconn->dc_link = link; + + offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + + dm_handle_hpd_rx_offload_work(&offload_work->work); + + KUNIT_EXPECT_EQ(test, dm_test_handle_link_loss_count, 1); +} + +/* Tests for amdgpu_dm_hdmi_hpd_debounce_work() */ + +/** + * dm_test_hdmi_hpd_debounce_detect_false - Test debounce false detect path + * @test: The KUnit test context + */ +static void dm_test_hdmi_hpd_debounce_detect_false(struct kunit *test) +{ + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + 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_link = dm_test_detect_link_false; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/** + * dm_test_hdmi_hpd_debounce_reallow_idle - Test debounce idle/sink-release tail + * @test: The KUnit test context + * + * With detection stubbed to return false, the if (ret) block is skipped, but + * the function tail is still exercised: IPS support plus an idle-allowed dmub + * makes reallow_idle true so dc_allow_idle_optimizations() runs on both entry + * and exit (disable_idle_power_optimizations keeps those safe early returns), + * and a cached hdmi_prev_sink is released and cleared. + */ +static void dm_test_hdmi_hpd_debounce_reallow_idle(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + struct dc_dmub_srv *dmub_srv; + struct amdgpu_device *adev; + struct dal_logger *logger; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + mutex_init(&adev->dm.dc_lock); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + 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); + logger = kunit_kzalloc(test, sizeof(*logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, logger); + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + 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; + aconn->dc_link = link; + + /* DC_LOG_* dereferences ctx->logger->dev, so wire a real drm device. */ + logger->dev = &adev->ddev; + dc->ctx->logger = logger; + + /* Make reallow_idle true and keep dc_allow_idle*() a safe early return. */ + dmub_srv->idle_allowed = true; + dc->ctx->dmub_srv = dmub_srv; + dc->caps.ips_support = true; + dc->debug.disable_idle_power_optimizations = true; + + /* A cached previous sink must be released and cleared. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + + amdgpu_dm_hdmi_hpd_debounce_work(&aconn->hdmi_hpd_debounce_work.work); + KUNIT_EXPECT_NULL(test, aconn->hdmi_prev_sink); +} + +/* Tests for handle_hpd_irq()/handle_hpd_irq_helper() */ + +/** + * dm_test_handle_hpd_irq_disabled - Test HPD helper returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_irq_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + + adev = dm_kunit_alloc_adev(test); + adev->dm.disable_hpd_irq = true; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + 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 = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + dc->ctx = ctx; + ctx->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + aconn->fake_enable = true; + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); + + handle_hpd_irq(aconn); + KUNIT_EXPECT_TRUE(test, aconn->fake_enable); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_schedule - Test HDMI debounce branch + * @test: The KUnit test context + * + * An HDMI link reporting a disconnect (connection type none) with a non-zero + * debounce delay and a cached local_sink must take the debounce branch: it + * caches local_sink in hdmi_prev_sink and schedules the delayed debounce work + * instead of detecting immediately. + */ +static void dm_test_handle_hpd_irq_helper_debounce_schedule(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + 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; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* local_sink is cached for later comparison by the debounce work. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_debounce_release_prev - Test stale prev_sink + * @test: The KUnit test context + * + * When the debounce branch is taken and a stale hdmi_prev_sink is already + * cached from a previous HPD, it must be released before caching the current + * local_sink. This exercises the dc_sink_release() of the previous sink. + */ +static void dm_test_handle_hpd_irq_helper_debounce_release_prev(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + 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; + dc->ctx = ctx; + dc->link_srv = link_srv; + ctx->dc = dc; + link->dc = dc; + link->ctx = ctx; + aconn->dc_link = link; + + /* HDMI signal + debounce delay + cached sink -> debounce branch. */ + aconn->hdmi_hpd_debounce_delay_ms = 100; + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + link->local_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link->local_sink); + + /* Stale sink from a previous HPD must be released by the helper. */ + aconn->hdmi_prev_sink = dm_test_sink_create(link); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconn->hdmi_prev_sink); + KUNIT_ASSERT_PTR_NE(test, aconn->hdmi_prev_sink, link->local_sink); + + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + /* Stale sink replaced by the current local_sink. */ + KUNIT_EXPECT_PTR_EQ(test, aconn->hdmi_prev_sink, link->local_sink); + + cancel_delayed_work_sync(&aconn->hdmi_hpd_debounce_work); + dm_test_sink_release(aconn->hdmi_prev_sink); + dm_test_sink_release(link->local_sink); +} + +/** + * dm_test_handle_hpd_irq_helper_detect_false - Test immediate detect branch + * @test: The KUnit test context + * + * With no force, no debounce, and detection stubbed to report no connection, + * the helper takes the else branch: dc_exit_ips_for_hw_access() is a safe + * no-op (no IPS support) and dc_link_detect() returns false, so the connected + * if (ret) block is skipped. fake_enable must still be cleared. + */ +static void dm_test_handle_hpd_irq_helper_detect_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct dm_connector_state *state; + 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); + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + INIT_DELAYED_WORK(&aconn->hdmi_hpd_debounce_work, + amdgpu_dm_hdmi_hpd_debounce_work); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); + aconn->base.state = &state->base; + + 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; + aconn->dc_link = link; + aconn->fake_enable = true; + + /* No debounce delay and no force -> immediate-detect else branch. */ + handle_hpd_irq_helper(aconn, DETECT_REASON_HPD); + + KUNIT_EXPECT_FALSE(test, aconn->fake_enable); +} + +/* Tests for handle_hpd_rx_irq()/schedule_hpd_rx_offload_work() */ + +/** + * dm_test_handle_hpd_rx_irq_disabled - Test HPDRX handler returns when disabled + * @test: The KUnit test context + */ +static void dm_test_handle_hpd_rx_irq_disabled(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + 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); + adev->dm.disable_hpd_irq = true; + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + + 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; + aconn->dc_link = link; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_schedule_hpd_rx_offload_work - Test offload work is queued + * @test: The KUnit test context + */ +static void dm_test_schedule_hpd_rx_offload_work(struct kunit *test) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + struct amdgpu_device *adev; + union hpd_irq_data data = { 0 }; + + adev = dm_kunit_alloc_adev(test); + + offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + + schedule_hpd_rx_offload_work(adev, offload_wq, data); + flush_workqueue(offload_wq->wq); + destroy_workqueue(offload_wq->wq); +} + +static void dm_test_destroy_offload_wq(void *data) +{ + struct workqueue_struct *wq = data; + + flush_workqueue(wq); + destroy_workqueue(wq); +} + +/* + * Build an aconnector wired for handle_hpd_rx_irq(): HPD enabled, an MST-root + * connector on an MST-branch link so the post-detect block and drm_dp_cec_irq + * are skipped, and a flushed offload work queue. Caller sets the link_srv + * stubs (dp_handle_hpd_rx_irq and, if needed, dp_should_allow_hpd_rx_irq). + */ +static struct amdgpu_dm_connector *dm_test_setup_hpd_rx_irq(struct kunit *test, + struct link_service **link_srv_out) +{ + struct hpd_rx_irq_offload_work_queue *offload_wq; + 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); + offload_wq->wq = create_singlethread_workqueue("dm_irq_test_hpd_rx"); + KUNIT_ASSERT_NOT_NULL(test, offload_wq->wq); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_destroy_offload_wq, + offload_wq->wq), 0); + adev->dm.hpd_rx_offload_wq = offload_wq; + + aconn = dm_kunit_alloc_connector(test, adev, NULL); + mutex_init(&aconn->hpd_lock); + aconn->mst_mgr.mst_state = true; + + 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); + + 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; + aconn->dc_link = link; + + *link_srv_out = link_srv; + + return aconn; +} + +/** + * dm_test_handle_hpd_rx_irq_no_left_work - Test HPDRX early out (no left work) + * @test: The KUnit test context + * + * When dc_link_handle_hpd_rx_irq() reports no left-over work, the handler + * jumps to out and returns without scheduling any offload work. + */ +static void dm_test_handle_hpd_rx_irq_no_left_work(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_no_work; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_automated_test - Test HPDRX automated-test path + * @test: The KUnit test context + * + * The AUTOMATED_TEST device-service bit must schedule offload work and jump to + * out before the allow-hpd-rx-irq checks. + */ +static void dm_test_handle_hpd_rx_irq_automated_test(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_automated; + + handle_hpd_rx_irq(aconn); +} + +/** + * dm_test_handle_hpd_rx_irq_msg_rdy - Test HPDRX MST message-ready path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and an UP_REQ_MSG_RDY bit set, the handler must take + * the MST message-ready branch, mark is_handling_mst_msg_rdy_event and schedule + * offload work. + */ +static void dm_test_handle_hpd_rx_irq_msg_rdy(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_msg_rdy; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_mst_msg_rdy_event); +} + +/** + * dm_test_handle_hpd_rx_irq_link_loss - Test HPDRX link-loss path + * @test: The KUnit test context + * + * With HPD RX IRQs allowed and a reported link loss (no message-ready bits), + * the handler must take the link-loss branch, mark is_handling_link_loss and + * schedule offload work. + */ +static void dm_test_handle_hpd_rx_irq_link_loss(struct kunit *test) +{ + struct amdgpu_dm_connector *aconn; + struct link_service *link_srv; + + aconn = dm_test_setup_hpd_rx_irq(test, &link_srv); + link_srv->dp_handle_hpd_rx_irq = dm_test_handle_hpd_rx_link_loss; + link_srv->dp_should_allow_hpd_rx_irq = dm_test_allow_hpd_rx_irq_true; + + handle_hpd_rx_irq(aconn); + + KUNIT_EXPECT_TRUE(test, drm_to_adev(aconn->base.dev) + ->dm.hpd_rx_offload_wq->is_handling_link_loss); +} + 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), @@ -2004,6 +2914,26 @@ static struct kunit_case amdgpu_dm_irq_tests[] = { KUNIT_CASE(dm_test_hpd_init_fini_with_connectors), KUNIT_CASE(dm_test_hpd_init_fini_analog_connector), KUNIT_CASE(dm_test_hpd_init_fini_irq_ref), + /* dm_handle_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connector), + KUNIT_CASE(dm_test_hpd_rx_offload_work_no_connection), + KUNIT_CASE(dm_test_hpd_rx_offload_work_automated_test), + KUNIT_CASE(dm_test_hpd_rx_offload_work_link_loss), + /* amdgpu_dm_hdmi_hpd_debounce_work */ + KUNIT_CASE(dm_test_hdmi_hpd_debounce_detect_false), + KUNIT_CASE(dm_test_hdmi_hpd_debounce_reallow_idle), + /* handle_hpd_irq/handle_hpd_irq_helper */ + KUNIT_CASE(dm_test_handle_hpd_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_schedule), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_debounce_release_prev), + KUNIT_CASE(dm_test_handle_hpd_irq_helper_detect_false), + /* handle_hpd_rx_irq/schedule_hpd_rx_offload_work */ + KUNIT_CASE(dm_test_handle_hpd_rx_irq_disabled), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_no_left_work), + KUNIT_CASE(dm_test_handle_hpd_rx_irq_automated_test), + 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), {} }; -- 2.55.0
