From 5d00879f30a2f9464c785cfc962815ee5780d9a8 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 23 Jun 2026 10:44:10 -0400 Subject: [PATCH] drm/amd/display: Add more tests for HDCP Adds a few more test cases to the hdcp code - process_output(): make sure work gets requeued when callback and watchdog are stopped but also asked to run again - event_property_update(): bail out when the connector is NULL - hdcp_handle_cpirq(): schedules the work and picks the right link - hdcp_update_display(): schedules property validation on enable, resets status and cancels validation on disable - hdcp_create_workqueue(): returns NULL when max links is zero Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 48 ++-- .../amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 240 ++++++++++++++++++ 3 files changed, 276 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 5dbeb1e017d46..1ffe82f0a9f10 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -258,6 +258,30 @@ static void link_lock(struct hdcp_workqueue *work, bool lock) } } +STATIC_IFN_KUNIT +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption) +{ + if (enable_encryption) { + /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp + * (s3 resume case) + */ + if (hdcp_work->srm_size > 0) + psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, + hdcp_work->srm_size, + &hdcp_work->srm_version); + + schedule_delayed_work(&hdcp_w->property_validate_dwork, + msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); + } else { + hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + cancel_delayed_work(&hdcp_w->property_validate_dwork); + } +} +EXPORT_IF_KUNIT(hdcp_update_display_encryption_control); + void hdcp_update_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector, @@ -281,22 +305,8 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, dc->debug.hdcp_lc_force_fw_enable, dc->debug.hdcp_lc_enable_sw_fallback, &link_adjust, &display_adjust); - - if (enable_encryption) { - /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp - * (s3 resume case) - */ - if (hdcp_work->srm_size > 0) - psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm, - hdcp_work->srm_size, - &hdcp_work->srm_version); - - schedule_delayed_work(&hdcp_w->property_validate_dwork, - msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); - } else { - hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; - cancel_delayed_work(&hdcp_w->property_validate_dwork); - } + hdcp_update_display_encryption_control(hdcp_work, hdcp_w, conn_index, + enable_encryption); mod_hdcp_update_display(&hdcp_w->hdcp, conn_index, &link_adjust, &display_adjust, &hdcp_w->output); @@ -363,6 +373,7 @@ void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index schedule_work(&hdcp_w->cpirq_work); } +EXPORT_IF_KUNIT(hdcp_handle_cpirq); static void event_callback(struct work_struct *work) { @@ -381,7 +392,8 @@ static void event_callback(struct work_struct *work) process_output(hdcp_work); } -static void event_property_update(struct work_struct *work) +STATIC_IFN_KUNIT +void event_property_update(struct work_struct *work) { struct hdcp_workqueue *hdcp_work = container_of(work, struct hdcp_workqueue, property_update_work); @@ -440,6 +452,7 @@ static void event_property_update(struct work_struct *work) drm_modeset_unlock(&dev->mode_config.connection_mutex); } } +EXPORT_IF_KUNIT(event_property_update); static void event_property_validate(struct work_struct *work) { @@ -872,4 +885,5 @@ fail_alloc_context: return NULL; } +EXPORT_IF_KUNIT(hdcp_create_workqueue); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 3ba5823aed9f9..a82a20b805185 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -108,6 +108,11 @@ void hdcp_get_link_display_adjustments( bool hdcp_lc_enable_sw_fallback, struct mod_hdcp_link_adjustment *link_adjust, struct mod_hdcp_display_adjustment *display_adjust); +void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, + struct hdcp_workqueue *hdcp_w, + unsigned int conn_index, + bool enable_encryption); +void event_property_update(struct work_struct *work); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 619b4a80c82bc..dbcff92672e69 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -402,8 +402,236 @@ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *te cancel_delayed_work_sync(&work->watchdog_timer_dwork); cancel_delayed_work_sync(&work->property_validate_dwork); } + +/** + * dm_test_process_output_callback_stop_and_needed_requeues - stop+needed requeues callback work + * @test: KUnit test context + * + * When callback_stop and callback_needed are both set, process_output() + * should cancel the previous callback_dwork and queue it again. + */ +static void dm_test_process_output_callback_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + work->output.callback_stop = true; + work->output.callback_needed = true; + work->output.callback_delay = 300; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_process_output_watchdog_stop_and_needed_requeues - stop+needed requeues watchdog work + * @test: KUnit test context + * + * When watchdog_timer_stop and watchdog_timer_needed are both set, + * process_output() should cancel previous watchdog work and queue it again. + */ +static void dm_test_process_output_watchdog_stop_and_needed_requeues(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue(test); + + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + work->output.watchdog_timer_stop = true; + work->output.watchdog_timer_needed = true; + work->output.watchdog_timer_delay = 700; + + process_output(work); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} /* End of tests for process_output() */ +/* Tests for event_property_update() */ + +/** + * alloc_test_workqueue_for_property_update - allocate minimal workqueue for callback tests + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with property_update_work initialised + * so event_property_update() can resolve container_of() safely. + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_property_update_skips_null_connector - null connector is ignored + * @test: KUnit test context + * + * If aconnector entry is NULL, event_property_update() should skip it + * without modifying encryption_status. + */ +static void dm_test_event_property_update_skips_null_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + enum mod_hdcp_encryption_status before; + + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON; + before = work->encryption_status[0]; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[0], before); +} + +/* End of tests for event_property_update() */ + +/* Tests for hdcp_handle_cpirq() */ + +/** + * dm_test_hdcp_handle_cpirq_schedules_work - cpirq handler queues cpirq_work + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected link. + */ +static void dm_test_hdcp_handle_cpirq_schedules_work(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work->cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 0); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->cpirq_work)); + + cancel_work_sync(&work->cpirq_work); +} + +/** + * dm_test_hdcp_handle_cpirq_selects_link_index - only selected link work is queued + * @test: KUnit test context + * + * hdcp_handle_cpirq() should schedule cpirq_work for the selected index + * and not queue unrelated links. + */ +static void dm_test_hdcp_handle_cpirq_selects_link_index(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kcalloc(test, 2, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_WORK(&work[0].cpirq_work, dummy_work_fn); + INIT_WORK(&work[1].cpirq_work, dummy_work_fn); + + hdcp_handle_cpirq(work, 1); + + KUNIT_EXPECT_FALSE(test, work_pending(&work[0].cpirq_work)); + KUNIT_EXPECT_TRUE(test, work_pending(&work[1].cpirq_work)); + + cancel_work_sync(&work[1].cpirq_work); +} + +/* End of tests for hdcp_handle_cpirq() */ + +/* Tests for hdcp_update_display() helper logic */ + +/** + * dm_test_hdcp_update_display_enable_schedules_property_validate - enable path queues validate work + * @test: KUnit test context + * + * hdcp_update_display() should schedule property_validate_dwork when + * encryption is enabled. + */ +static void dm_test_hdcp_update_display_enable_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + work->srm_size = 0; + + hdcp_update_display_encryption_control(work, work, 0, true); + + KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate - disable path state update + * @test: KUnit test context + * + * hdcp_update_display() should set encryption_status to HDCP_OFF and + * cancel property_validate_dwork when encryption is disabled. + */ +static void dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + schedule_delayed_work(&work->property_validate_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->property_validate_dwork)); + + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + hdcp_update_display_encryption_control(work, work, 3, false); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->property_validate_dwork)); +} + +/* End of tests for hdcp_update_display() helper logic */ + +/* Tests for hdcp_create_workqueue() */ + +/** + * dm_test_hdcp_create_workqueue_zero_max_links_returns_null - zero-link creation fails early + * @test: KUnit test context + * + * When dc->caps.max_links is zero, hdcp_create_workqueue() should fail + * the initial allocation path and return NULL. + */ +static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kunit *test) +{ + struct cp_psp cp_psp = {0}; + struct dc *dc; + struct hdcp_workqueue *work; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + dc->caps.max_links = 0; + + work = hdcp_create_workqueue(NULL, &cp_psp, dc); + + KUNIT_EXPECT_PTR_EQ(test, work, NULL); +} + +/* End of tests for hdcp_create_workqueue() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -423,6 +651,18 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_process_output_watchdog_needed), KUNIT_CASE(dm_test_process_output_watchdog_stop), KUNIT_CASE(dm_test_process_output_callback_and_watchdog_needed), + KUNIT_CASE(dm_test_process_output_callback_stop_and_needed_requeues), + KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues), + /* event_property_update() */ + KUNIT_CASE(dm_test_event_property_update_skips_null_connector), + /* hdcp_handle_cpirq() */ + KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), + KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), + /* hdcp_update_display() helper logic */ + KUNIT_CASE(dm_test_hdcp_update_display_enable_schedules_property_validate), + KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), + /* hdcp_create_workqueue() */ + KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), {} }; -- 2.53.0