// SPDX-License-Identifier: GPL-2.0 OR MIT /* * KUnit tests for amdgpu_dm.c * * Copyright 2026 Advanced Micro Devices, Inc. */ #include #include #include #include #include #include #include #include "dc.h" #include "inc/core_types.h" #include "amd_shared.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_kunit_test_helpers.h" /* Tests for simple DM callbacks */ /** * dm_test_is_idle - Test placeholder idle callback returns true * @test: The KUnit test context */ static void dm_test_is_idle(struct kunit *test) { KUNIT_EXPECT_TRUE(test, dm_is_idle(NULL)); } /** * dm_test_wait_for_idle - Test placeholder wait-for-idle callback returns success * @test: The KUnit test context */ static void dm_test_wait_for_idle(struct kunit *test) { KUNIT_EXPECT_EQ(test, dm_wait_for_idle(NULL), 0); } /** * dm_test_soft_reset - Test placeholder soft-reset callback returns success * @test: The KUnit test context */ static void dm_test_soft_reset(struct kunit *test) { KUNIT_EXPECT_EQ(test, dm_soft_reset(NULL), 0); } /** * dm_test_set_clockgating_state - Test placeholder clockgating callback returns success * @test: The KUnit test context */ static void dm_test_set_clockgating_state(struct kunit *test) { KUNIT_EXPECT_EQ(test, dm_set_clockgating_state(NULL, AMD_CG_STATE_GATE), 0); } /** * dm_test_set_powergating_state - Test placeholder powergating callback returns success * @test: The KUnit test context */ static void dm_test_set_powergating_state(struct kunit *test) { KUNIT_EXPECT_EQ(test, dm_set_powergating_state(NULL, AMD_PG_STATE_GATE), 0); } /** * dm_test_bandwidth_update - Test placeholder bandwidth update is callable * @test: The KUnit test context */ static void dm_test_bandwidth_update(struct kunit *test) { dm_bandwidth_update(NULL); } /** * dm_test_crtc_complete_writeback_no_connector - Test no writeback connector returns false * @test: The KUnit test context */ static void dm_test_crtc_complete_writeback_no_connector(struct kunit *test) { struct amdgpu_crtc *acrtc; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, acrtc); KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); } /** * dm_test_crtc_complete_writeback_not_pending - Test non-pending writeback returns false * @test: The KUnit test context */ static void dm_test_crtc_complete_writeback_not_pending(struct kunit *test) { struct amdgpu_crtc *acrtc; struct drm_writeback_connector *wb_conn; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, acrtc); wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, wb_conn); spin_lock_init(&wb_conn->job_lock); acrtc->wb_conn = wb_conn; acrtc->wb_pending = false; KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); } /** * dm_test_vblank_get_counter_out_of_range - Test out-of-range CRTC returns zero * @test: The KUnit test context */ static void dm_test_vblank_get_counter_out_of_range(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); adev->mode_info.num_crtc = 1; KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 1), 0U); } /** * dm_test_vblank_get_counter_no_stream - Test missing stream returns zero * @test: The KUnit test context */ static void dm_test_vblank_get_counter_no_stream(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); struct amdgpu_crtc *acrtc; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, acrtc); adev->mode_info.num_crtc = 1; adev->mode_info.crtcs[0] = acrtc; KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 0), 0U); } /** * dm_test_crtc_get_scanoutpos_invalid_crtc - Test invalid CRTC returns -EINVAL * @test: The KUnit test context */ static void dm_test_crtc_get_scanoutpos_invalid_crtc(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); u32 vbl = 0; u32 position = 0; adev->mode_info.num_crtc = 1; KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, -1, &vbl, &position), -EINVAL); KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 1, &vbl, &position), -EINVAL); } /** * dm_test_crtc_get_scanoutpos_no_stream - Test missing stream returns zero * @test: The KUnit test context */ static void dm_test_crtc_get_scanoutpos_no_stream(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); struct amdgpu_crtc *acrtc; u32 vbl = 0; u32 position = 0; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, acrtc); adev->mode_info.num_crtc = 1; adev->mode_info.crtcs[0] = acrtc; KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 0, &vbl, &position), 0); KUNIT_EXPECT_EQ(test, vbl, 0U); KUNIT_EXPECT_EQ(test, position, 0U); } /** * dm_test_atomic_get_new_state_empty - Test empty atomic state has no DM state * @test: The KUnit test context */ static void dm_test_atomic_get_new_state_empty(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); struct drm_atomic_commit *state; state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, state); state->dev = &adev->ddev; KUNIT_EXPECT_NULL(test, dm_atomic_get_new_state(state)); } /** * dm_test_atomic_get_new_state_match - Test atomic state returns matching DM private state * @test: The KUnit test context */ static void dm_test_atomic_get_new_state_match(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); struct dm_atomic_state *dm_state; struct drm_atomic_commit *state; state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, state); dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, dm_state); state->private_objs = kunit_kzalloc(test, sizeof(*state->private_objs), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, state->private_objs); state->dev = &adev->ddev; state->num_private_objs = 1; state->private_objs[0].ptr = &adev->dm.atomic_obj; state->private_objs[0].new_state = &dm_state->base; KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state), dm_state); } /** * dm_test_atomic_destroy_state_no_context - Test destroying DM atomic state without a DC context * @test: The KUnit test context */ static void dm_test_atomic_destroy_state_no_context(struct kunit *test) { struct dm_atomic_state *dm_state; /* * Use kzalloc(), not kunit_kzalloc(): dm_atomic_destroy_state() frees * the state itself, so KUnit-managed memory would be double-freed. */ dm_state = kzalloc(sizeof(*dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, dm_state); /* context == NULL: dc_state_release() is skipped and the state is freed. */ dm_atomic_destroy_state(NULL, &dm_state->base); } /* Tests for dm_plane_layer_index_cmp() */ /** * dm_test_plane_layer_index_cmp_equal - Test Plane layer index cmp equal * @test: The KUnit test context */ static void dm_test_plane_layer_index_cmp_equal(struct kunit *test) { struct dc_plane_state *plane_a; struct dc_plane_state *plane_b; struct dc_surface_update sa, sb; plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); plane_a->layer_index = 5; plane_b->layer_index = 5; sa.surface = plane_a; sb.surface = plane_b; KUNIT_EXPECT_EQ(test, dm_plane_layer_index_cmp(&sa, &sb), 0); } /** * dm_test_plane_layer_index_cmp_descending - Test Plane layer index cmp descending * @test: The KUnit test context */ static void dm_test_plane_layer_index_cmp_descending(struct kunit *test) { struct dc_plane_state *plane_a; struct dc_plane_state *plane_b; struct dc_surface_update sa, sb; plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); plane_a->layer_index = 3; plane_b->layer_index = 7; sa.surface = plane_a; sb.surface = plane_b; /* b has higher index, so cmp(a,b) = b - a > 0 (b sorts first) */ KUNIT_EXPECT_GT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); } /** * dm_test_plane_layer_index_cmp_ascending - Test Plane layer index cmp ascending * @test: The KUnit test context */ static void dm_test_plane_layer_index_cmp_ascending(struct kunit *test) { struct dc_plane_state *plane_a; struct dc_plane_state *plane_b; struct dc_surface_update sa, sb; plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); plane_a->layer_index = 9; plane_b->layer_index = 2; sa.surface = plane_a; sb.surface = plane_b; /* a has higher index, so cmp(a,b) = b - a < 0 (a sorts first) */ KUNIT_EXPECT_LT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); } /* Tests for fill_plane_color_attributes() */ /** * dm_test_fill_color_attr_rgb_format - Test Fill color attr rgb format * @test: The KUnit test context */ static void dm_test_fill_color_attr_rgb_format(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; /* RGB format: should return 0 and set SRGB regardless of encoding */ plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_SRGB); } /** * dm_test_fill_color_attr_bt601_full - Test Fill color attr bt601 full * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt601_full(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR601); } /** * dm_test_fill_color_attr_bt601_limited - Test Fill color attr bt601 limited * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt601_limited(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR601_LIMITED); } /** * dm_test_fill_color_attr_bt709_full - Test Fill color attr bt709 full * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt709_full(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR709); } /** * dm_test_fill_color_attr_bt709_limited - Test Fill color attr bt709 limited * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt709_limited(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR709_LIMITED); } /** * dm_test_fill_color_attr_bt2020_full - Test Fill color attr bt2020 full * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt2020_full(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_2020_YCBCR_FULL); } /** * dm_test_fill_color_attr_bt2020_limited - Test Fill color attr bt2020 limited * @test: The KUnit test context */ static void dm_test_fill_color_attr_bt2020_limited(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_2020_YCBCR_LIMITED); } /** * dm_test_fill_color_attr_invalid_encoding - Test Fill color attr invalid encoding * @test: The KUnit test context */ static void dm_test_fill_color_attr_invalid_encoding(struct kunit *test) { struct drm_plane_state plane_state = { 0 }; enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; int ret; plane_state.color_encoding = 99; plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; ret = fill_plane_color_attributes(&plane_state, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, &color_space); KUNIT_EXPECT_EQ(test, ret, -EINVAL); } /* Tests for modereset_required() */ /** * dm_test_modereset_required_when_inactive_and_modeset - Test Modereset required when inactive and modeset * @test: The KUnit test context */ static void dm_test_modereset_required_when_inactive_and_modeset(struct kunit *test) { struct drm_crtc_state crtc_state = { 0 }; crtc_state.active = false; crtc_state.mode_changed = true; KUNIT_EXPECT_TRUE(test, modereset_required(&crtc_state)); } /** * dm_test_modereset_not_required_when_active_and_modeset - Test Modereset not required when active and modeset * @test: The KUnit test context */ static void dm_test_modereset_not_required_when_active_and_modeset(struct kunit *test) { struct drm_crtc_state crtc_state = { 0 }; crtc_state.active = true; crtc_state.mode_changed = true; KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); } /** * dm_test_modereset_not_required_when_inactive_without_modeset - Test Modereset not required when inactive without modeset * @test: The KUnit test context */ static void dm_test_modereset_not_required_when_inactive_without_modeset(struct kunit *test) { struct drm_crtc_state crtc_state = { 0 }; crtc_state.active = false; crtc_state.mode_changed = false; KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); } /* Tests for is_scaling_state_different() */ /** * dm_test_scaling_state_same - Test identical scaling states compare equal * @test: The KUnit test context */ static void dm_test_scaling_state_same(struct kunit *test) { struct dm_connector_state *a; struct dm_connector_state *b; a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, a); KUNIT_ASSERT_NOT_NULL(test, b); a->scaling = RMX_FULL; a->underscan_enable = false; *b = *a; KUNIT_EXPECT_FALSE(test, is_scaling_state_different(a, b)); } /** * dm_test_scaling_state_scaling_changed - Test differing scaling mode is detected * @test: The KUnit test context */ static void dm_test_scaling_state_scaling_changed(struct kunit *test) { struct dm_connector_state *a; struct dm_connector_state *b; a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, a); KUNIT_ASSERT_NOT_NULL(test, b); a->scaling = RMX_FULL; b->scaling = RMX_CENTER; KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /** * dm_test_scaling_state_underscan_enabled - Test enabling underscan with borders differs * @test: The KUnit test context */ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) { struct dm_connector_state *old_state; struct dm_connector_state *new_state; old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, old_state); KUNIT_ASSERT_NOT_NULL(test, new_state); /* new enables underscan with non-zero borders, old has it disabled */ new_state->underscan_enable = true; new_state->underscan_hborder = 16; new_state->underscan_vborder = 16; old_state->underscan_enable = false; KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } /** * dm_test_scaling_state_underscan_disabled - Test disabling underscan with borders differs * @test: The KUnit test context */ static void dm_test_scaling_state_underscan_disabled(struct kunit *test) { struct dm_connector_state *old_state; struct dm_connector_state *new_state; old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, old_state); KUNIT_ASSERT_NOT_NULL(test, new_state); old_state->underscan_enable = true; old_state->underscan_hborder = 16; old_state->underscan_vborder = 16; new_state->underscan_enable = false; KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } /** * dm_test_scaling_state_underscan_border_changed - Test changed underscan borders differ * @test: The KUnit test context */ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) { struct dm_connector_state *a; struct dm_connector_state *b; a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, a); KUNIT_ASSERT_NOT_NULL(test, b); a->underscan_enable = true; a->underscan_hborder = 16; a->underscan_vborder = 16; *b = *a; b->underscan_hborder = 32; KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /* Tests for set_multisync_trigger_params() */ /** * dm_test_multisync_trigger_disabled - Test disabled reset leaves params untouched * @test: The KUnit test context */ static void dm_test_multisync_trigger_disabled(struct kunit *test) { struct dc_stream_state *stream; stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); stream->triggered_crtc_reset.enabled = false; stream->triggered_crtc_reset.event = CRTC_EVENT_VSYNC_FALLING; stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_LINE; set_multisync_trigger_params(stream); /* Nothing should change when the reset trigger is disabled */ KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, (int)CRTC_EVENT_VSYNC_FALLING); KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, (int)TRIGGER_DELAY_NEXT_LINE); } /** * dm_test_multisync_trigger_rising - Test positive vsync polarity selects rising edge * @test: The KUnit test context */ static void dm_test_multisync_trigger_rising(struct kunit *test) { struct dc_stream_state *stream; struct dc_stream_state *master; stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); master->timing.flags.VSYNC_POSITIVE_POLARITY = 1; stream->triggered_crtc_reset.enabled = true; stream->triggered_crtc_reset.event_source = master; set_multisync_trigger_params(stream); KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, (int)CRTC_EVENT_VSYNC_RISING); KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, (int)TRIGGER_DELAY_NEXT_PIXEL); } /** * dm_test_multisync_trigger_falling - Test negative vsync polarity selects falling edge * @test: The KUnit test context */ static void dm_test_multisync_trigger_falling(struct kunit *test) { struct dc_stream_state *stream; struct dc_stream_state *master; stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); master->timing.flags.VSYNC_POSITIVE_POLARITY = 0; stream->triggered_crtc_reset.enabled = true; stream->triggered_crtc_reset.event_source = master; set_multisync_trigger_params(stream); KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, (int)CRTC_EVENT_VSYNC_FALLING); KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, (int)TRIGGER_DELAY_NEXT_PIXEL); } /* Tests for set_master_stream() */ /** * dm_test_master_stream_highest_refresh - Test highest refresh-rate stream becomes master * @test: The KUnit test context */ static void dm_test_master_stream_highest_refresh(struct kunit *test) { struct dc_stream_state *stream0, *stream1; struct dc_stream_state *stream_set[2]; stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); stream_set[0] = stream0; stream_set[1] = stream1; /* stream0: 60Hz, stream1: 120Hz -> stream1 is master */ stream0->triggered_crtc_reset.enabled = true; stream0->timing.pix_clk_100hz = 1485000; stream0->timing.h_total = 2200; stream0->timing.v_total = 1125; stream1->triggered_crtc_reset.enabled = true; stream1->timing.pix_clk_100hz = 2970000; stream1->timing.h_total = 2200; stream1->timing.v_total = 1125; set_master_stream(stream_set, 2); KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, stream1); KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, stream1); } /** * dm_test_master_stream_defaults_to_first - Test default master when none triggered * @test: The KUnit test context * * When no stream has the reset trigger enabled, master_stream stays 0 and all * streams point at the first stream as their event source. */ static void dm_test_master_stream_defaults_to_first(struct kunit *test) { struct dc_stream_state *stream0, *stream1; struct dc_stream_state *stream_set[2]; stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); stream_set[0] = stream0; stream_set[1] = stream1; set_master_stream(stream_set, 2); KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, stream0); KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, stream0); } /* Tests for is_content_protection_different() */ struct dm_test_cp_ctx { struct amdgpu_dm_connector *aconnector; struct dm_connector_state *new_dm; /* also connector->state */ struct dm_connector_state *old_dm; struct drm_crtc_state *new_crtc; struct drm_crtc_state *old_crtc; }; static struct dm_test_cp_ctx *dm_test_cp_ctx_alloc(struct kunit *test) { struct dm_test_cp_ctx *ctx; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); ctx->new_dm = kunit_kzalloc(test, sizeof(*ctx->new_dm), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->new_dm); ctx->old_dm = kunit_kzalloc(test, sizeof(*ctx->old_dm), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->old_dm); ctx->new_crtc = kunit_kzalloc(test, sizeof(*ctx->new_crtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->new_crtc); ctx->old_crtc = kunit_kzalloc(test, sizeof(*ctx->old_crtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->old_crtc); /* connector->state must be the new dm connector state */ ctx->aconnector->base.state = &ctx->new_dm->base; ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; return ctx; } static bool dm_test_cp_diff(struct dm_test_cp_ctx *ctx) { return is_content_protection_different(ctx->new_crtc, ctx->old_crtc, &ctx->new_dm->base, &ctx->old_dm->base, &ctx->aconnector->base, NULL); } /** * dm_test_cp_diff_hdcp_type_change - Test an HDCP content-type change forces true * @test: The KUnit test context */ static void dm_test_cp_diff_hdcp_type_change(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.hdcp_content_type = 0; ctx->new_dm->base.hdcp_content_type = 1; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); } /** * dm_test_cp_diff_reenable_mode_changed - Test ENABLED->DESIRED with modeset forces true * @test: The KUnit test context */ static void dm_test_cp_diff_reenable_mode_changed(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_crtc->mode_changed = true; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); } /** * dm_test_cp_diff_reenable_no_change - Test ENABLED->DESIRED without modeset restores ENABLED * @test: The KUnit test context */ static void dm_test_cp_diff_reenable_no_change(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_crtc->mode_changed = false; KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, (int)DRM_MODE_CONTENT_PROTECTION_ENABLED); } /** * dm_test_cp_diff_undesired - Test UNDESIRED->UNDESIRED needs no update * @test: The KUnit test context */ static void dm_test_cp_diff_undesired(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); } /** * dm_test_cp_diff_desired_mode_changed - Test DESIRED->DESIRED with modeset forces true * @test: The KUnit test context */ static void dm_test_cp_diff_desired_mode_changed(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_crtc->mode_changed = true; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); } /** * dm_test_cp_diff_desired_no_change - Test steady DESIRED->DESIRED needs no update * @test: The KUnit test context */ static void dm_test_cp_diff_desired_no_change(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_crtc->mode_changed = false; KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); } /** * dm_test_cp_diff_update_hdcp_hotplug - Test the update_hdcp hot-plug path forces true * @test: The KUnit test context */ static void dm_test_cp_diff_update_hdcp_hotplug(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sink); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->update_hdcp = true; ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; ctx->aconnector->dc_sink = sink; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); } /** * dm_test_cp_diff_stream_reenabled - Test the stream removed-and-re-enabled path forces true * @test: The KUnit test context */ static void dm_test_cp_diff_stream_reenabled(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); struct drm_crtc *crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, crtc); crtc->enabled = true; ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->update_hdcp = true; ctx->old_dm->base.crtc = NULL; ctx->new_dm->base.crtc = crtc; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); } /** * dm_test_cp_diff_s3_undesired_to_enabled - Test the S3 UNDESIRED->ENABLED path forces true * @test: The KUnit test context */ static void dm_test_cp_diff_s3_undesired_to_enabled(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); } /** * dm_test_cp_diff_desired_to_enabled - Test DESIRED->ENABLED needs no update * @test: The KUnit test context */ static void dm_test_cp_diff_desired_to_enabled(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); } /** * dm_test_cp_diff_desired_to_undesired - Test DESIRED->UNDESIRED forces update * @test: The KUnit test context */ static void dm_test_cp_diff_desired_to_undesired(struct kunit *test) { struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); } /* Tests for dm_enable_per_frame_crtc_master_sync() */ /** * dm_test_per_frame_master_sync_single_stream - Test fewer than two streams is a no-op * @test: The KUnit test context */ static void dm_test_per_frame_master_sync_single_stream(struct kunit *test) { struct dc_state *context; struct dc_stream_state *stream; context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, context); stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, stream); stream->triggered_crtc_reset.enabled = true; context->streams[0] = stream; context->stream_count = 1; dm_enable_per_frame_crtc_master_sync(context); /* < 2 streams: early return, event_source stays NULL */ KUNIT_EXPECT_NULL(test, stream->triggered_crtc_reset.event_source); } /** * dm_test_per_frame_master_sync_two_streams - Test the master is picked and applied * @test: The KUnit test context */ static void dm_test_per_frame_master_sync_two_streams(struct kunit *test) { struct dc_state *context; struct dc_stream_state *stream0, *stream1; context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, context); stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, stream0); stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, stream1); /* stream0 60Hz, stream1 120Hz, both trigger-reset enabled */ stream0->triggered_crtc_reset.enabled = true; stream0->timing.pix_clk_100hz = 1485000; stream0->timing.h_total = 2200; stream0->timing.v_total = 1125; stream1->triggered_crtc_reset.enabled = true; stream1->timing.pix_clk_100hz = 2970000; stream1->timing.h_total = 2200; stream1->timing.v_total = 1125; stream1->timing.flags.VSYNC_POSITIVE_POLARITY = 1; context->streams[0] = stream0; context->streams[1] = stream1; context->stream_count = 2; dm_enable_per_frame_crtc_master_sync(context); /* set_master_stream picks the highest refresh (stream1) as event source */ KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, stream1); KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, stream1); /* set_multisync_trigger_params applied to enabled streams */ KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.event, (int)CRTC_EVENT_VSYNC_RISING); KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.delay, (int)TRIGGER_DELAY_NEXT_PIXEL); } /** * dm_test_per_frame_master_sync_skips_null_stream - Test NULL stream entries are skipped * @test: The KUnit test context */ static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test) { struct dc_state *context; struct dc_stream_state *stream; context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, context); stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, stream); stream->triggered_crtc_reset.enabled = true; stream->timing.pix_clk_100hz = 1485000; stream->timing.h_total = 2200; stream->timing.v_total = 1125; context->streams[0] = stream; context->streams[1] = NULL; context->stream_count = 2; dm_enable_per_frame_crtc_master_sync(context); KUNIT_EXPECT_PTR_EQ(test, stream->triggered_crtc_reset.event_source, stream); } /* Tests for amdgpu_dm_apply_delay_after_dpcd_poweroff() */ /** * dm_test_apply_delay_null_sink - Test a NULL sink returns without delay * @test: The KUnit test context */ static void dm_test_apply_delay_null_sink(struct kunit *test) { /* NULL sink: early return, no delay, no dereference */ amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, NULL); } /** * dm_test_apply_delay_zero_wait - Test a zero wait interval skips the delay * @test: The KUnit test context */ static void dm_test_apply_delay_zero_wait(struct kunit *test) { struct dc_sink *sink; sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sink); /* wait == 0: no msleep, adev is unused so NULL is safe */ sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 0; amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, sink); } /** * dm_test_apply_delay_nonzero_wait - Test a non-zero wait interval executes delay path * @test: The KUnit test context */ static void dm_test_apply_delay_nonzero_wait(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); struct dc_sink *sink; sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sink); sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 1; amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, sink); } static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), KUNIT_CASE(dm_test_wait_for_idle), KUNIT_CASE(dm_test_soft_reset), KUNIT_CASE(dm_test_set_clockgating_state), KUNIT_CASE(dm_test_set_powergating_state), KUNIT_CASE(dm_test_bandwidth_update), KUNIT_CASE(dm_test_crtc_complete_writeback_no_connector), KUNIT_CASE(dm_test_crtc_complete_writeback_not_pending), KUNIT_CASE(dm_test_vblank_get_counter_out_of_range), KUNIT_CASE(dm_test_vblank_get_counter_no_stream), KUNIT_CASE(dm_test_crtc_get_scanoutpos_invalid_crtc), KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream), KUNIT_CASE(dm_test_atomic_get_new_state_empty), KUNIT_CASE(dm_test_atomic_get_new_state_match), KUNIT_CASE(dm_test_atomic_destroy_state_no_context), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), KUNIT_CASE(dm_test_plane_layer_index_cmp_ascending), /* fill_plane_color_attributes */ KUNIT_CASE(dm_test_fill_color_attr_rgb_format), KUNIT_CASE(dm_test_fill_color_attr_bt601_full), KUNIT_CASE(dm_test_fill_color_attr_bt601_limited), KUNIT_CASE(dm_test_fill_color_attr_bt709_full), KUNIT_CASE(dm_test_fill_color_attr_bt709_limited), KUNIT_CASE(dm_test_fill_color_attr_bt2020_full), KUNIT_CASE(dm_test_fill_color_attr_bt2020_limited), KUNIT_CASE(dm_test_fill_color_attr_invalid_encoding), /* modereset_required */ KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset), /* is_scaling_state_different */ KUNIT_CASE(dm_test_scaling_state_same), KUNIT_CASE(dm_test_scaling_state_scaling_changed), KUNIT_CASE(dm_test_scaling_state_underscan_enabled), KUNIT_CASE(dm_test_scaling_state_underscan_disabled), KUNIT_CASE(dm_test_scaling_state_underscan_border_changed), /* set_multisync_trigger_params */ KUNIT_CASE(dm_test_multisync_trigger_disabled), KUNIT_CASE(dm_test_multisync_trigger_rising), KUNIT_CASE(dm_test_multisync_trigger_falling), /* set_master_stream */ KUNIT_CASE(dm_test_master_stream_highest_refresh), KUNIT_CASE(dm_test_master_stream_defaults_to_first), /* is_content_protection_different */ KUNIT_CASE(dm_test_cp_diff_hdcp_type_change), KUNIT_CASE(dm_test_cp_diff_reenable_mode_changed), KUNIT_CASE(dm_test_cp_diff_reenable_no_change), KUNIT_CASE(dm_test_cp_diff_undesired), KUNIT_CASE(dm_test_cp_diff_desired_mode_changed), KUNIT_CASE(dm_test_cp_diff_desired_no_change), KUNIT_CASE(dm_test_cp_diff_update_hdcp_hotplug), KUNIT_CASE(dm_test_cp_diff_stream_reenabled), KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_undesired), /* dm_enable_per_frame_crtc_master_sync */ KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream), /* amdgpu_dm_apply_delay_after_dpcd_poweroff */ KUNIT_CASE(dm_test_apply_delay_null_sink), KUNIT_CASE(dm_test_apply_delay_zero_wait), KUNIT_CASE(dm_test_apply_delay_nonzero_wait), {} }; static struct kunit_suite amdgpu_dm_test_suite = { .name = "amdgpu_dm", .test_cases = amdgpu_dm_tests, }; kunit_test_suite(amdgpu_dm_test_suite); MODULE_AUTHOR("AMD"); MODULE_DESCRIPTION("KUnit tests for amdgpu_dm"); MODULE_LICENSE("Dual MIT/GPL");