* [PATCH 0/8] drm/amd/display: Copy probed modes for atomic_check
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
mode_config.mutex protects connector->modes, but amdgpu_dm walks it from
atomic_check, in create_stream_for_sink() and
amdgpu_dm_get_highest_refresh_rate_mode(), without that mutex.
atomic_check can't take the mutex because of lock ordering. An earlier
patch I submitted moved the probe helper's sort under the modeset locks
so this walk couldn't race it, and Ville pointed out that the bug is in
the reader:
https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
atomic_check only needs the preferred mode and the highest refresh rate
mode. This series copies both at the end of each probe, under
mode_config.mutex and connection_mutex, and atomic_check reads the
copies under connection_mutex. The highest refresh rate copy is the mode
add_fs_modes() built the FreeSync video modes from, as upstream's cached
freesync_vid_base is. It's done this way because a search of
connector->modes at the end of the probe could pick a command line mode
with a higher refresh rate, and amdgpu_dm_is_freesync_video_mode() would
then reject the video modes
The copies also need work to stay current after a monitor swap. Until
the next probe they still describe the previous monitor, as
connector->modes could, and two paths read them before that probe.
mode_valid runs before the probe refreshes them, and after a swap the
HPD handlers commit before the next probe. Patch 3 has mode_valid
validate each mode with its own timing, so it no longer needs the
preferred mode. Patch 4 records the EDID of the sink the copies came
from, and patch 7 uses them only for a sink with that EDID. atomic_check
checks them against the sink of the stream it builds, since a hotplug
can swap dc_sink under it. With another sink, atomic_check uses the
requested mode's own timing until the next probe, as before the first
probe
Patch 1 drops a debug check that walked probed_modes and
connector->modes from atomic_check, and asks its author below the cut
whether to keep it behind mutex_trylock() instead. Patch 2 stops
add_fs_modes() matching against connector->modes, which dropped the
FreeSync video modes on every second probe. Patch 4 adds the copies,
patches 5 and 6 pass the sink of the stream being built to the code that
checks them, patch 7 switches the readers to them, and patch 8 adds
tests for the copies
Patch 6 also changes one behaviour, only with amdgpu.freesync_video=1,
which is off by default. A CRTC turned on straight into a FreeSync video
mode now gets its fixed refresh config, which it never got before
---
Hugo Sales (8):
drm/amd/display: Drop the unadvertised mode check
drm/amd/display: Don't match FreeSync video modes against the last probe
drm/amd/display: Validate modes with their own timing
drm/amd/display: Copy the preferred and highest refresh modes after probe
drm/amd/display: Pass the sink to the FreeSync and stream timing helpers
drm/amd/display: Pick the FreeSync video config for the new stream's sink
drm/amd/display: Stop reading connector->modes from atomic_check
drm/amd/display: Add KUnit tests for the mode copies
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 38 +-
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 23 +-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 499 +++++---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 13 +-
.../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 1209 +++++++++++++++++---
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 153 +++
7 files changed, 1644 insertions(+), 293 deletions(-)
---
base-commit: 47df73aa49d1988c8f684b7caf8be6570e966844
change-id: 20261104-amdgpu-modes-four-57aaed43ab18
Best regards,
--
Hugo Sales <hugo@hsal.es>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH 0/8] drm/amd/display: Copy probed modes for atomic_check
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
mode_config.mutex protects connector->modes, but amdgpu_dm walks it from
atomic_check, in create_stream_for_sink() and
amdgpu_dm_get_highest_refresh_rate_mode(), without that mutex.
atomic_check can't take the mutex because of lock ordering. An earlier
patch I submitted moved the probe helper's sort under the modeset locks
so this walk couldn't race it, and Ville pointed out that the bug is in
the reader:
https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
atomic_check only needs the preferred mode and the highest refresh rate
mode. This series copies both at the end of each probe, under
mode_config.mutex and connection_mutex, and atomic_check reads the
copies under connection_mutex. The highest refresh rate copy is the mode
add_fs_modes() built the FreeSync video modes from, as upstream's cached
freesync_vid_base is. It's done this way because a search of
connector->modes at the end of the probe could pick a command line mode
with a higher refresh rate, and amdgpu_dm_is_freesync_video_mode() would
then reject the video modes
The copies also need work to stay current after a monitor swap. Until
the next probe they still describe the previous monitor, as
connector->modes could, and two paths read them before that probe.
mode_valid runs before the probe refreshes them, and after a swap the
HPD handlers commit before the next probe. Patch 3 has mode_valid
validate each mode with its own timing, so it no longer needs the
preferred mode. Patch 4 records the EDID of the sink the copies came
from, and patch 7 uses them only for a sink with that EDID. atomic_check
checks them against the sink of the stream it builds, since a hotplug
can swap dc_sink under it. With another sink, atomic_check uses the
requested mode's own timing until the next probe, as before the first
probe
Patch 1 drops a debug check that walked probed_modes and
connector->modes from atomic_check, and asks its author below the cut
whether to keep it behind mutex_trylock() instead. Patch 2 stops
add_fs_modes() matching against connector->modes, which dropped the
FreeSync video modes on every second probe. Patch 4 adds the copies,
patches 5 and 6 pass the sink of the stream being built to the code that
checks them, patch 7 switches the readers to them, and patch 8 adds
tests for the copies
Patch 6 also changes one behaviour, only with amdgpu.freesync_video=1,
which is off by default. A CRTC turned on straight into a FreeSync video
mode now gets its fixed refresh config, which it never got before
---
Hugo Sales (8):
drm/amd/display: Drop the unadvertised mode check
drm/amd/display: Don't match FreeSync video modes against the last probe
drm/amd/display: Validate modes with their own timing
drm/amd/display: Copy the preferred and highest refresh modes after probe
drm/amd/display: Pass the sink to the FreeSync and stream timing helpers
drm/amd/display: Pick the FreeSync video config for the new stream's sink
drm/amd/display: Stop reading connector->modes from atomic_check
drm/amd/display: Add KUnit tests for the mode copies
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 38 +-
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 23 +-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 499 +++++---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 13 +-
.../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 1209 +++++++++++++++++---
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 153 +++
7 files changed, 1644 insertions(+), 293 deletions(-)
---
base-commit: 47df73aa49d1988c8f684b7caf8be6570e966844
change-id: 20261104-amdgpu-modes-four-57aaed43ab18
Best regards,
--
Hugo Sales <hugo@hsal.es>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH 1/8] drm/amd/display: Drop the unadvertised mode check
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
Commit fa129ff3b6a7 ("drm/amd/display: warn on unadvertised modes
without scaling") made create_stream_for_sink() log "Unadvertised mode
... with RMX_OFF" when the requested mode isn't one the connector
advertises. Commit 828cfa384e23 ("drm/amd/display: Fix unadvertised mode
detection") made is_duplicate_mode() search connector->modes as well as
probed_modes
mode_config.mutex protects both lists, and atomic_check, where
create_stream_for_sink() runs, holds only the modeset locks. A probe on
another thread can free modes in connector->modes during the walk.
atomic_check can't take mode_config.mutex, since the probe takes it
before connection_mutex
The check's only effect is the debug message, so drop it.
is_duplicate_mode() stays for add_fs_modes()
Fixes: fa129ff3b6a7 ("drm/amd/display: warn on unadvertised modes without scaling")
Fixes: 828cfa384e23 ("drm/amd/display: Fix unadvertised mode detection")
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Fangzhi, fa129ff3b6a7 says the message is there to advise users to pick
an advertised mode when they see a visual defect. Do you plan to build
more on it, like rejecting such modes or a warning userspace can see? If
you'd rather keep the message, I can do that instead. atomic_check would
take mode_config.mutex with mutex_trylock() and skip the check while a
probe holds it. The walk is then safe, and the message only goes missing
while a probe runs
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 8 --------
1 file changed, 8 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index e8f050feb6e39..89f8427433af0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1495,7 +1495,6 @@ create_stream_for_sink(struct drm_connector *connector,
struct drm_display_mode saved_mode;
struct drm_display_mode *freesync_mode = NULL;
bool recalculate_timing = false;
- bool unadvertised_mode = false;
bool scale = dm_state->scaling != RMX_OFF;
int mode_refresh;
int preferred_refresh = 0;
@@ -1627,13 +1626,6 @@ create_stream_for_sink(struct drm_connector *connector,
&mode, preferred_mode, scale);
preferred_refresh = drm_mode_vrefresh(preferred_mode);
- unadvertised_mode = !is_duplicate_mode(aconnector, drm_mode);
-
- if (unadvertised_mode && dm_state->scaling == RMX_OFF)
- drm_dbg_driver(dev,
- "Unadvertised mode %dx%d@%d with RMX_OFF\n",
- drm_mode->hdisplay, drm_mode->vdisplay,
- drm_mode_vrefresh(drm_mode));
}
}
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 1/8] drm/amd/display: Drop the unadvertised mode check
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
Commit fa129ff3b6a7 ("drm/amd/display: warn on unadvertised modes
without scaling") made create_stream_for_sink() log "Unadvertised mode
... with RMX_OFF" when the requested mode isn't one the connector
advertises. Commit 828cfa384e23 ("drm/amd/display: Fix unadvertised mode
detection") made is_duplicate_mode() search connector->modes as well as
probed_modes
mode_config.mutex protects both lists, and atomic_check, where
create_stream_for_sink() runs, holds only the modeset locks. A probe on
another thread can free modes in connector->modes during the walk.
atomic_check can't take mode_config.mutex, since the probe takes it
before connection_mutex
The check's only effect is the debug message, so drop it.
is_duplicate_mode() stays for add_fs_modes()
Fixes: fa129ff3b6a7 ("drm/amd/display: warn on unadvertised modes without scaling")
Fixes: 828cfa384e23 ("drm/amd/display: Fix unadvertised mode detection")
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Fangzhi, fa129ff3b6a7 says the message is there to advise users to pick
an advertised mode when they see a visual defect. Do you plan to build
more on it, like rejecting such modes or a warning userspace can see? If
you'd rather keep the message, I can do that instead. atomic_check would
take mode_config.mutex with mutex_trylock() and skip the check while a
probe holds it. The walk is then safe, and the message only goes missing
while a probe runs
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 8 --------
1 file changed, 8 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index e8f050feb6e39..89f8427433af0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1495,7 +1495,6 @@ create_stream_for_sink(struct drm_connector *connector,
struct drm_display_mode saved_mode;
struct drm_display_mode *freesync_mode = NULL;
bool recalculate_timing = false;
- bool unadvertised_mode = false;
bool scale = dm_state->scaling != RMX_OFF;
int mode_refresh;
int preferred_refresh = 0;
@@ -1627,13 +1626,6 @@ create_stream_for_sink(struct drm_connector *connector,
&mode, preferred_mode, scale);
preferred_refresh = drm_mode_vrefresh(preferred_mode);
- unadvertised_mode = !is_duplicate_mode(aconnector, drm_mode);
-
- if (unadvertised_mode && dm_state->scaling == RMX_OFF)
- drm_dbg_driver(dev,
- "Unadvertised mode %dx%d@%d with RMX_OFF\n",
- drm_mode->hdisplay, drm_mode->vdisplay,
- drm_mode_vrefresh(drm_mode));
}
}
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 2/8] drm/amd/display: Don't match FreeSync video modes against the last probe
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
add_fs_modes() runs in ->get_modes() and skips a FreeSync video mode
that is_duplicate_mode() finds. Since commit 828cfa384e23
("drm/amd/display: Fix unadvertised mode detection"),
is_duplicate_mode() also searches connector->modes, which during a
re-probe still lists the previous probe's modes. add_fs_modes() then
takes every FreeSync video mode for a duplicate and adds none, and the
probe helper prunes the old ones as stale. With amdgpu.freesync_video=1,
the FreeSync video modes disappear on every second probe
The connector->modes search was for the unadvertised mode check, which
is gone, so search only probed_modes again
Fixes: 828cfa384e23 ("drm/amd/display: Fix unadvertised mode detection")
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 -----
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 26 +++++++++++-----------
2 files changed, 13 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 89f8427433af0..da2ee6179cb67 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1409,11 +1409,6 @@ STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
return true;
}
- list_for_each_entry(m, &aconnector->base.modes, head) {
- if (drm_mode_equal(m, mode))
- return true;
- }
-
return false;
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index addc407841a2a..5af111698e355 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -1136,7 +1136,6 @@ static void dm_test_is_duplicate_mode_empty_list(struct kunit *test)
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.modes);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
mode.hdisplay = 1920;
mode.vdisplay = 1080;
@@ -1157,12 +1156,11 @@ static void dm_test_is_duplicate_mode_match(struct kunit *test)
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.modes);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
@@ -1172,10 +1170,14 @@ static void dm_test_is_duplicate_mode_match(struct kunit *test)
}
/**
- * dm_test_is_duplicate_mode_probed_match - Test probed mode duplicate match
+ * dm_test_is_duplicate_mode_previous_probe - Test modes of the previous probe don't count
* @test: The KUnit test context
+ *
+ * During a re-probe, base.modes still holds the previous probe's modes. A
+ * mode listed only there is not a duplicate, so add_fs_modes() builds the
+ * FreeSync video modes again on every probe
*/
-static void dm_test_is_duplicate_mode_probed_match(struct kunit *test)
+static void dm_test_is_duplicate_mode_previous_probe(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
struct drm_display_mode existing = {};
@@ -1189,13 +1191,13 @@ static void dm_test_is_duplicate_mode_probed_match(struct kunit *test)
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.probed_modes);
+ list_add_tail(&existing.head, &aconnector->base.modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
candidate.clock = 148500;
- KUNIT_EXPECT_TRUE(test, is_duplicate_mode(aconnector, &candidate));
+ KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate));
}
/**
@@ -1212,11 +1214,10 @@ static void dm_test_is_duplicate_mode_no_match(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, aconnector);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
- INIT_LIST_HEAD(&aconnector->base.modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 2560;
candidate.vdisplay = 1440;
@@ -1238,13 +1239,12 @@ static void dm_test_is_duplicate_mode_same_size_different_clock(struct kunit *te
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.probed_modes);
- INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
@@ -10402,7 +10402,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
/* is_duplicate_mode */
KUNIT_CASE(dm_test_is_duplicate_mode_empty_list),
KUNIT_CASE(dm_test_is_duplicate_mode_match),
- KUNIT_CASE(dm_test_is_duplicate_mode_probed_match),
+ KUNIT_CASE(dm_test_is_duplicate_mode_previous_probe),
KUNIT_CASE(dm_test_is_duplicate_mode_no_match),
KUNIT_CASE(dm_test_is_duplicate_mode_same_size_different_clock),
/* amdgpu_dm_get_encoder_crtc_mask */
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 2/8] drm/amd/display: Don't match FreeSync video modes against the last probe
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
add_fs_modes() runs in ->get_modes() and skips a FreeSync video mode
that is_duplicate_mode() finds. Since commit 828cfa384e23
("drm/amd/display: Fix unadvertised mode detection"),
is_duplicate_mode() also searches connector->modes, which during a
re-probe still lists the previous probe's modes. add_fs_modes() then
takes every FreeSync video mode for a duplicate and adds none, and the
probe helper prunes the old ones as stale. With amdgpu.freesync_video=1,
the FreeSync video modes disappear on every second probe
The connector->modes search was for the unadvertised mode check, which
is gone, so search only probed_modes again
Fixes: 828cfa384e23 ("drm/amd/display: Fix unadvertised mode detection")
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 -----
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 26 +++++++++++-----------
2 files changed, 13 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 89f8427433af0..da2ee6179cb67 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1409,11 +1409,6 @@ STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector,
return true;
}
- list_for_each_entry(m, &aconnector->base.modes, head) {
- if (drm_mode_equal(m, mode))
- return true;
- }
-
return false;
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index addc407841a2a..5af111698e355 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -1136,7 +1136,6 @@ static void dm_test_is_duplicate_mode_empty_list(struct kunit *test)
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.modes);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
mode.hdisplay = 1920;
mode.vdisplay = 1080;
@@ -1157,12 +1156,11 @@ static void dm_test_is_duplicate_mode_match(struct kunit *test)
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.modes);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
@@ -1172,10 +1170,14 @@ static void dm_test_is_duplicate_mode_match(struct kunit *test)
}
/**
- * dm_test_is_duplicate_mode_probed_match - Test probed mode duplicate match
+ * dm_test_is_duplicate_mode_previous_probe - Test modes of the previous probe don't count
* @test: The KUnit test context
+ *
+ * During a re-probe, base.modes still holds the previous probe's modes. A
+ * mode listed only there is not a duplicate, so add_fs_modes() builds the
+ * FreeSync video modes again on every probe
*/
-static void dm_test_is_duplicate_mode_probed_match(struct kunit *test)
+static void dm_test_is_duplicate_mode_previous_probe(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
struct drm_display_mode existing = {};
@@ -1189,13 +1191,13 @@ static void dm_test_is_duplicate_mode_probed_match(struct kunit *test)
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.probed_modes);
+ list_add_tail(&existing.head, &aconnector->base.modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
candidate.clock = 148500;
- KUNIT_EXPECT_TRUE(test, is_duplicate_mode(aconnector, &candidate));
+ KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate));
}
/**
@@ -1212,11 +1214,10 @@ static void dm_test_is_duplicate_mode_no_match(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, aconnector);
INIT_LIST_HEAD(&aconnector->base.probed_modes);
- INIT_LIST_HEAD(&aconnector->base.modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 2560;
candidate.vdisplay = 1440;
@@ -1238,13 +1239,12 @@ static void dm_test_is_duplicate_mode_same_size_different_clock(struct kunit *te
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
- INIT_LIST_HEAD(&aconnector->base.probed_modes);
- INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
existing.hdisplay = 1920;
existing.vdisplay = 1080;
existing.clock = 148500;
- list_add_tail(&existing.head, &aconnector->base.modes);
+ list_add_tail(&existing.head, &aconnector->base.probed_modes);
candidate.hdisplay = 1920;
candidate.vdisplay = 1080;
@@ -10402,7 +10402,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
/* is_duplicate_mode */
KUNIT_CASE(dm_test_is_duplicate_mode_empty_list),
KUNIT_CASE(dm_test_is_duplicate_mode_match),
- KUNIT_CASE(dm_test_is_duplicate_mode_probed_match),
+ KUNIT_CASE(dm_test_is_duplicate_mode_previous_probe),
KUNIT_CASE(dm_test_is_duplicate_mode_no_match),
KUNIT_CASE(dm_test_is_duplicate_mode_same_size_different_clock),
/* amdgpu_dm_get_encoder_crtc_mask */
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 3/8] drm/amd/display: Validate modes with their own timing
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
amdgpu_dm_connector_mode_valid() builds a stream through
create_stream_for_sink(), which takes the preferred mode from
connector->modes. During a probe that list still holds the previous
probe's modes, so after a monitor swap with no probe in between, as
across suspend or behind a KVM switch, mode_valid finds the old
monitor's preferred mode. With scaling on it validates that timing in
place of each mode's own, which can prune every mode of the new monitor
or pass modes its link can't carry. This probe's preferred mode won't do
either, since if it fails validation it prunes every other mode with it
Validate each mode with its own timing, as the first probe already does
on connectors other than eDP and LVDS. create_stream_for_sink() now
takes the preferred mode from its caller. atomic_check still looks it
up, with the eDP and LVDS fallback from
commit ab9e2cfa0e54 ("drm/amd/display: Fall back to the native mode, not
the first, on eDP/LVDS"), and mode_valid passes none
With scaling on, two kinds of mode get a different answer. A scaled
pairing that needs another encoding, such as one whose preferred timing
is YCbCr 4:2:0 only, now fails in atomic_check instead of being pruned
at probe time. A mode the link can carry only at the preferred mode's
timing, such as a smaller mode with a higher refresh rate, is now
pruned. Before, it passed and then ran at the preferred mode's refresh
rate. With scaling off, the same modes pass as before
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
On eDP and LVDS with scaling off, the common modes amdgpu_dm adds differ
from the native mode only in their active area. They keep its pixel
clock and totals, so atomic_check drives them at the native timing.
mode_valid used to validate that timing and now validates their own, but
both need the same link bandwidth, so the same modes pass
With no preferred mode from mode_valid, the "No preferred mode found"
message no longer prints for every depth it tries. Writeback connectors,
which never used the preferred mode, stop looking it up
An upcoming patch makes the preferred mode lookup assert connection_mutex.
The early return on a missing state in
amdgpu_dm_create_validate_stream_for_sink() then also keeps that assert
away from callers without one
The @native_buf argument of amdgpu_dm_connector_preferred_mode() keeps
the eDP and LVDS fallback working until an upcoming patch has the
lookup return the copy taken after the probe, which already holds the
native mode. That patch drops the argument again
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 214 +++++++++++++--------
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 4 +
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 120 +++++++++++-
3 files changed, 249 insertions(+), 89 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index da2ee6179cb67..e477ef61ce109 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1052,6 +1052,62 @@ create_fake_sink(struct drm_device *dev, struct dc_link *link)
return sink;
}
+/*
+ * After a probe with size limits smaller than the preferred mode, @modes no
+ * longer has that mode, and the first mode left would become the stream
+ * timing. fbdev emulation probes with limits that video= can set below the
+ * native size. Only eDP and LVDS panels get a fallback here, since they take
+ * only their native size. On other connectors mode_valid can also reject the
+ * preferred mode, and then the first valid mode is the right one
+ *
+ * Take a mode of the native size from @modes if one is left, since mode_valid
+ * may have rejected the preferred timing itself. The probe sorts the list, so
+ * the first one has the highest refresh rate left. If the limits pruned them
+ * all, fill @native_buf from the copy of the preferred mode that
+ * amdgpu_dm_get_native_mode() keeps in the encoder and refreshes on every
+ * probe, before the limits apply
+ *
+ * @native_buf: storage for the native mode when no mode of its size is left
+ * in @modes
+ *
+ * Returns a mode of the native size from @modes if there is one, otherwise
+ * @native_buf filled with the encoder's native mode, or NULL for other
+ * connectors or when the encoder has no native mode
+ */
+static const struct drm_display_mode *
+amdgpu_dm_panel_native_mode(struct drm_connector *connector, struct list_head *modes,
+ struct drm_display_mode *native_buf)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ const struct drm_display_mode *enc_native;
+ struct drm_encoder *encoder;
+ struct drm_display_mode *m;
+
+ if (!aconnector->drm_edid ||
+ (connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
+ connector->connector_type != DRM_MODE_CONNECTOR_LVDS))
+ return NULL;
+
+ encoder = amdgpu_dm_connector_to_encoder(connector);
+ if (!encoder)
+ return NULL;
+
+ enc_native = &to_amdgpu_encoder(encoder)->native_mode;
+ if (!enc_native->clock)
+ return NULL;
+
+ list_for_each_entry(m, modes, head) {
+ if (m->hdisplay == enc_native->hdisplay &&
+ m->vdisplay == enc_native->vdisplay)
+ return m;
+ }
+
+ drm_mode_init(native_buf, enc_native);
+ drm_mode_set_crtcinfo(native_buf, CRTC_INTERLACE_HALVE_V);
+
+ return native_buf;
+}
+
/**
* DOC: FreeSync Video
*
@@ -1474,6 +1530,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings);
STATIC_IFN_KUNIT struct dc_stream_state *
create_stream_for_sink(struct drm_connector *connector,
const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
const struct dm_connector_state *dm_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
@@ -1482,8 +1539,6 @@ create_stream_for_sink(struct drm_connector *connector,
{
struct drm_device *dev = connector->dev;
struct amdgpu_dm_connector *aconnector = NULL;
- struct drm_display_mode *preferred_mode = NULL;
- struct drm_display_mode *connector_mode;
const struct drm_connector_state *con_state = &dm_state->base;
struct dc_stream_state *stream = NULL;
struct drm_display_mode mode;
@@ -1539,75 +1594,9 @@ create_stream_for_sink(struct drm_connector *connector,
stream->timing.flags.LTE_340MCSC_SCRAMBLE =
connector->display_info.hdmi.scdc.scrambling.low_rates;
- list_for_each_entry(connector_mode, &connector->modes, head) {
- /* Search for preferred mode */
- if (connector_mode->type & DRM_MODE_TYPE_PREFERRED) {
- preferred_mode = connector_mode;
- break;
- }
- }
-
- /*
- * After a probe with size limits smaller than the preferred mode,
- * connector->modes no longer has that mode, and the first mode left
- * would become the stream timing. fbdev emulation probes with limits
- * that video= can set below the native size. Only eDP and LVDS panels
- * get a fallback here, since they take only their native size. On other
- * connectors mode_valid can also reject the preferred mode, and then
- * the first valid mode is the right one
- *
- * Take a mode of the native size from connector->modes if one is left,
- * since mode_valid may have rejected the preferred timing itself. The
- * probe sorts the list, so the first one has the highest refresh rate
- * left. If the limits pruned them all, use the copy of the preferred
- * mode that amdgpu_dm_get_native_mode() keeps in the encoder. It is
- * made on every probe, before the limits apply
- */
- if (!preferred_mode && aconnector && aconnector->drm_edid &&
- (connector->connector_type == DRM_MODE_CONNECTOR_eDP ||
- connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
- struct drm_encoder *encoder = amdgpu_dm_connector_to_encoder(connector);
- const struct drm_display_mode *enc_native =
- encoder ? &to_amdgpu_encoder(encoder)->native_mode : NULL;
-
- if (enc_native && enc_native->clock) {
- list_for_each_entry(connector_mode, &connector->modes, head) {
- if (connector_mode->hdisplay == enc_native->hdisplay &&
- connector_mode->vdisplay == enc_native->vdisplay) {
- preferred_mode = connector_mode;
- break;
- }
- }
-
- /*
- * saved_mode is only used for FreeSync video modes, and
- * that path doesn't read preferred_mode
- */
- if (!preferred_mode) {
- drm_mode_init(&saved_mode, enc_native);
- drm_mode_set_crtcinfo(&saved_mode, CRTC_INTERLACE_HALVE_V);
- preferred_mode = &saved_mode;
- }
- }
- }
-
- if (!preferred_mode)
- preferred_mode = list_first_entry_or_null(
- &connector->modes,
- struct drm_display_mode,
- head);
-
mode_refresh = drm_mode_vrefresh(&mode);
- if (preferred_mode == NULL) {
- /*
- * This may not be an error, the use case is when we have no
- * usermode calls to reset and set mode upon hotplug. In this
- * case, we call set mode ourselves to restore the previous mode
- * and the modelist may not be filled in time.
- */
- drm_dbg_driver(dev, "No preferred mode found\n");
- } else if (aconnector) {
+ if (aconnector && preferred_mode) {
recalculate_timing = amdgpu_freesync_vid_mode &&
amdgpu_dm_is_freesync_video_mode(&mode, aconnector);
if (recalculate_timing) {
@@ -2337,11 +2326,12 @@ dm_validate_stream_color_format(const struct drm_connector_state *drm_state,
DC_OK : DC_UNSUPPORTED_VALUE;
}
-struct dc_stream_state *
-amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
- const struct drm_display_mode *drm_mode,
- const struct dm_connector_state *dm_state,
- const struct dc_stream_state *old_stream)
+static struct dc_stream_state *
+__amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
{
/*
* Ordered lists of the encodings and bit depths we are willing to
@@ -2383,8 +2373,8 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
* meaningful for the writeback path).
*/
if (!aconnector) {
- stream = create_stream_for_sink(connector, drm_mode, dm_state,
- old_stream, requested_bpc,
+ stream = create_stream_for_sink(connector, drm_mode, preferred_mode,
+ dm_state, old_stream, requested_bpc,
PIXEL_ENCODING_RGB, is_hdmi_ep);
if (!stream)
drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n");
@@ -2514,6 +2504,7 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
encoding_mask, bpc_mask, requested_bpc);
stream = create_stream_for_sink(connector, drm_mode,
+ preferred_mode,
dm_state, old_stream,
bpc, enc, is_hdmi_ep);
if (stream == NULL) {
@@ -2555,6 +2546,63 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
*/
return NULL;
}
+
+STATIC_IFN_KUNIT const struct drm_display_mode *
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
+ struct drm_display_mode *native_buf)
+{
+ const struct drm_display_mode *mode;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ return NULL;
+
+ list_for_each_entry(mode, &connector->modes, head) {
+ if (mode->type & DRM_MODE_TYPE_PREFERRED)
+ return mode;
+ }
+
+ mode = amdgpu_dm_panel_native_mode(connector, &connector->modes, native_buf);
+ if (mode)
+ return mode;
+
+ mode = list_first_entry_or_null(&connector->modes,
+ struct drm_display_mode, head);
+ if (!mode) {
+ /*
+ * This may not be an error, the use case is when we have no
+ * usermode calls to reset and set mode upon hotplug. In this
+ * case, we call set mode ourselves to restore the previous mode
+ * and the modelist may not be filled in time
+ */
+ drm_dbg_driver(connector->dev, "No preferred mode found\n");
+ }
+
+ return mode;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_preferred_mode);
+
+struct dc_stream_state *
+amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
+{
+ const struct drm_display_mode *preferred_mode;
+ struct drm_display_mode native_buf;
+
+ /*
+ * __amdgpu_dm_create_validate_stream_for_sink() checks this as well,
+ * but without a state there is no commit, so skip the preferred mode
+ * lookup too
+ */
+ if (!dm_state)
+ return NULL;
+
+ preferred_mode = amdgpu_dm_connector_preferred_mode(connector, &native_buf);
+ return __amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
+ preferred_mode, dm_state,
+ old_stream);
+}
EXPORT_IF_KUNIT(amdgpu_dm_create_validate_stream_for_sink);
enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
@@ -2569,6 +2617,7 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
* here via the amdgpu_dm_connector_helper_funcs
*/
struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct dm_connector_state *dm_state = to_dm_connector_state(connector->state);
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
(mode->flags & DRM_MODE_FLAG_DBLSCAN))
@@ -2596,9 +2645,16 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
drm_mode_set_crtcinfo(test_mode, 0);
- stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
- to_dm_connector_state(connector->state),
- NULL);
+ /*
+ * During a probe connector->modes still holds the previous probe's
+ * modes, possibly of another sink, and the probe knows its preferred
+ * mode only once mode_valid has seen every mode. Validate each mode
+ * with its own timing, even where atomic_check drives another one, the
+ * preferred mode's with scaling on or the native one for eDP and LVDS
+ * common modes
+ */
+ stream = __amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
+ NULL, dm_state, NULL);
drm_mode_destroy(connector->dev, test_mode);
if (stream) {
dc_stream_release(stream);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 7bfbb68538335..e7ed9a0bf56fb 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -196,11 +196,15 @@ void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *
create_stream_for_sink(struct drm_connector *connector,
const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
const struct dm_connector_state *dm_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
enum dc_pixel_encoding requested_encoding,
bool is_hdmi_ep);
+const struct drm_display_mode *
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
+ struct drm_display_mode *native_buf);
enum drm_connector_status
amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force);
enum drm_connector_status
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 5af111698e355..e8cabe25a192d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -4277,7 +4277,7 @@ static void dm_test_create_stream_fake_sink_success(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4294,7 +4294,7 @@ static void dm_test_create_stream_sets_dm_context(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4312,7 +4312,7 @@ static void dm_test_create_stream_virtual_signal(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4332,7 +4332,7 @@ static void dm_test_create_stream_scaling_src(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4364,7 +4364,7 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
ctx->aconnector->dc_sink = sink;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4431,6 +4431,8 @@ static void dm_test_create_stream_preferred_mode(struct kunit *test)
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ list_first_entry(&ctx->aconnector->base.modes,
+ struct drm_display_mode, head),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -4459,6 +4461,8 @@ static void dm_test_create_stream_scaled_keeps_refresh(struct kunit *test)
ctx->dm_state->scaling = RMX_FULL;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ list_first_entry(&ctx->aconnector->base.modes,
+ struct drm_display_mode, head),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -4490,7 +4494,7 @@ static void dm_test_create_stream_timing_override(struct kunit *test)
ctx->aconnector->timing_requested = timing;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4519,7 +4523,7 @@ static void dm_test_create_stream_dp_vsc_colorimetry(struct kunit *test)
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4547,7 +4551,7 @@ static void dm_test_create_stream_dp_colorimetry_quirk(struct kunit *test)
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4574,7 +4578,7 @@ static void dm_test_create_stream_hdmi_vsif(struct kunit *test)
sink = dm_test_stream_add_sink(test, ctx, SIGNAL_TYPE_HDMI_TYPE_A);
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4609,8 +4613,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
static const u8 edid[EDID_LENGTH];
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
struct amdgpu_encoder *aenc;
+ struct drm_display_mode *native, native_buf;
struct dc_stream_state *stream;
- struct drm_display_mode *native;
aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aenc);
@@ -4659,6 +4663,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
}
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ amdgpu_dm_connector_preferred_mode(&ctx->aconnector->base,
+ &native_buf),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -7479,6 +7485,98 @@ static void dm_test_create_validate_stream_hdmi_rgb_first(struct kunit *test)
KUNIT_EXPECT_EQ(test, (int)dm_test_cvs_first_encoding, (int)PIXEL_ENCODING_RGB);
}
+static u32 dm_test_cvs_first_h_addressable;
+
+static bool dm_test_cvs_record_first_h_addressable(struct timing_generator *tg,
+ const struct dc_crtc_timing *timing)
+{
+ if (!dm_test_cvs_first_h_addressable)
+ dm_test_cvs_first_h_addressable = timing->h_addressable;
+
+ return false;
+}
+
+/*
+ * A 3840x2160 preferred mode in connector->modes, as a probe of another sink
+ * leaves it until the next probe prunes it
+ */
+static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_dc *c)
+{
+ struct drm_display_mode *pref = drm_mode_create(c->drm);
+
+ KUNIT_ASSERT_NOT_NULL(test, pref);
+ pref->hdisplay = 3840;
+ pref->hsync_start = 4016;
+ pref->hsync_end = 4104;
+ pref->htotal = 4400;
+ pref->vdisplay = 2160;
+ pref->vsync_start = 2168;
+ pref->vsync_end = 2178;
+ pref->vtotal = 2250;
+ pref->clock = 594000;
+ pref->type = DRM_MODE_TYPE_PREFERRED;
+ drm_mode_set_crtcinfo(pref, CRTC_INTERLACE_HALVE_V);
+ list_add_tail(&pref->head, &c->aconnector->base.modes);
+}
+
+/**
+ * dm_test_create_validate_stream_scaled_preferred - Test atomic_check scales to the preferred mode
+ * @test: The KUnit test context
+ *
+ * With scaling on, the stream for a 1920x1080 mode takes the CRTC timing of
+ * the preferred mode in connector->modes
+ */
+static void dm_test_create_validate_stream_scaled_preferred(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+ c->dm_state->scaling = RMX_FULL;
+ dm_test_cvs_add_preferred_4k(test, c);
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
+}
+
+/**
+ * dm_test_mode_valid_ignores_preferred_mode - Test mode_valid uses the mode's own timing
+ * @test: The KUnit test context
+ *
+ * During a probe connector->modes still holds the previous sink's preferred
+ * mode. Even with scaling on, the mode is validated with its own 1920x1080
+ * timing rather than that mode's 3840x2160 one
+ */
+static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+ struct dc_sink *em_sink;
+
+ em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, em_sink);
+ c->aconnector->dc_em_sink = em_sink;
+ c->aconnector->base.force = DRM_FORCE_ON;
+
+ /* drm_connector_cleanup() kfree()s connector->state */
+ KUNIT_ASSERT_NULL(test, c->aconnector->base.state);
+ amdgpu_dm_connector_funcs_reset(&c->aconnector->base);
+ KUNIT_ASSERT_NOT_NULL(test, c->aconnector->base.state);
+ to_dm_connector_state(c->aconnector->base.state)->scaling = RMX_FULL;
+
+ dm_test_cvs_add_preferred_4k(test, c);
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_EQ(test,
+ amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode),
+ MODE_ERROR);
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
+}
+
/* Further tests for amdgpu_dm_connector_mode_valid() */
/**
@@ -10695,6 +10793,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
+ KUNIT_CASE(dm_test_create_validate_stream_scaled_preferred),
+ KUNIT_CASE(dm_test_mode_valid_ignores_preferred_mode),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 3/8] drm/amd/display: Validate modes with their own timing
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
amdgpu_dm_connector_mode_valid() builds a stream through
create_stream_for_sink(), which takes the preferred mode from
connector->modes. During a probe that list still holds the previous
probe's modes, so after a monitor swap with no probe in between, as
across suspend or behind a KVM switch, mode_valid finds the old
monitor's preferred mode. With scaling on it validates that timing in
place of each mode's own, which can prune every mode of the new monitor
or pass modes its link can't carry. This probe's preferred mode won't do
either, since if it fails validation it prunes every other mode with it
Validate each mode with its own timing, as the first probe already does
on connectors other than eDP and LVDS. create_stream_for_sink() now
takes the preferred mode from its caller. atomic_check still looks it
up, with the eDP and LVDS fallback from
commit ab9e2cfa0e54 ("drm/amd/display: Fall back to the native mode, not
the first, on eDP/LVDS"), and mode_valid passes none
With scaling on, two kinds of mode get a different answer. A scaled
pairing that needs another encoding, such as one whose preferred timing
is YCbCr 4:2:0 only, now fails in atomic_check instead of being pruned
at probe time. A mode the link can carry only at the preferred mode's
timing, such as a smaller mode with a higher refresh rate, is now
pruned. Before, it passed and then ran at the preferred mode's refresh
rate. With scaling off, the same modes pass as before
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
On eDP and LVDS with scaling off, the common modes amdgpu_dm adds differ
from the native mode only in their active area. They keep its pixel
clock and totals, so atomic_check drives them at the native timing.
mode_valid used to validate that timing and now validates their own, but
both need the same link bandwidth, so the same modes pass
With no preferred mode from mode_valid, the "No preferred mode found"
message no longer prints for every depth it tries. Writeback connectors,
which never used the preferred mode, stop looking it up
An upcoming patch makes the preferred mode lookup assert connection_mutex.
The early return on a missing state in
amdgpu_dm_create_validate_stream_for_sink() then also keeps that assert
away from callers without one
The @native_buf argument of amdgpu_dm_connector_preferred_mode() keeps
the eDP and LVDS fallback working until an upcoming patch has the
lookup return the copy taken after the probe, which already holds the
native mode. That patch drops the argument again
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 214 +++++++++++++--------
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 4 +
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 120 +++++++++++-
3 files changed, 249 insertions(+), 89 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index da2ee6179cb67..e477ef61ce109 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1052,6 +1052,62 @@ create_fake_sink(struct drm_device *dev, struct dc_link *link)
return sink;
}
+/*
+ * After a probe with size limits smaller than the preferred mode, @modes no
+ * longer has that mode, and the first mode left would become the stream
+ * timing. fbdev emulation probes with limits that video= can set below the
+ * native size. Only eDP and LVDS panels get a fallback here, since they take
+ * only their native size. On other connectors mode_valid can also reject the
+ * preferred mode, and then the first valid mode is the right one
+ *
+ * Take a mode of the native size from @modes if one is left, since mode_valid
+ * may have rejected the preferred timing itself. The probe sorts the list, so
+ * the first one has the highest refresh rate left. If the limits pruned them
+ * all, fill @native_buf from the copy of the preferred mode that
+ * amdgpu_dm_get_native_mode() keeps in the encoder and refreshes on every
+ * probe, before the limits apply
+ *
+ * @native_buf: storage for the native mode when no mode of its size is left
+ * in @modes
+ *
+ * Returns a mode of the native size from @modes if there is one, otherwise
+ * @native_buf filled with the encoder's native mode, or NULL for other
+ * connectors or when the encoder has no native mode
+ */
+static const struct drm_display_mode *
+amdgpu_dm_panel_native_mode(struct drm_connector *connector, struct list_head *modes,
+ struct drm_display_mode *native_buf)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ const struct drm_display_mode *enc_native;
+ struct drm_encoder *encoder;
+ struct drm_display_mode *m;
+
+ if (!aconnector->drm_edid ||
+ (connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
+ connector->connector_type != DRM_MODE_CONNECTOR_LVDS))
+ return NULL;
+
+ encoder = amdgpu_dm_connector_to_encoder(connector);
+ if (!encoder)
+ return NULL;
+
+ enc_native = &to_amdgpu_encoder(encoder)->native_mode;
+ if (!enc_native->clock)
+ return NULL;
+
+ list_for_each_entry(m, modes, head) {
+ if (m->hdisplay == enc_native->hdisplay &&
+ m->vdisplay == enc_native->vdisplay)
+ return m;
+ }
+
+ drm_mode_init(native_buf, enc_native);
+ drm_mode_set_crtcinfo(native_buf, CRTC_INTERLACE_HALVE_V);
+
+ return native_buf;
+}
+
/**
* DOC: FreeSync Video
*
@@ -1474,6 +1530,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings);
STATIC_IFN_KUNIT struct dc_stream_state *
create_stream_for_sink(struct drm_connector *connector,
const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
const struct dm_connector_state *dm_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
@@ -1482,8 +1539,6 @@ create_stream_for_sink(struct drm_connector *connector,
{
struct drm_device *dev = connector->dev;
struct amdgpu_dm_connector *aconnector = NULL;
- struct drm_display_mode *preferred_mode = NULL;
- struct drm_display_mode *connector_mode;
const struct drm_connector_state *con_state = &dm_state->base;
struct dc_stream_state *stream = NULL;
struct drm_display_mode mode;
@@ -1539,75 +1594,9 @@ create_stream_for_sink(struct drm_connector *connector,
stream->timing.flags.LTE_340MCSC_SCRAMBLE =
connector->display_info.hdmi.scdc.scrambling.low_rates;
- list_for_each_entry(connector_mode, &connector->modes, head) {
- /* Search for preferred mode */
- if (connector_mode->type & DRM_MODE_TYPE_PREFERRED) {
- preferred_mode = connector_mode;
- break;
- }
- }
-
- /*
- * After a probe with size limits smaller than the preferred mode,
- * connector->modes no longer has that mode, and the first mode left
- * would become the stream timing. fbdev emulation probes with limits
- * that video= can set below the native size. Only eDP and LVDS panels
- * get a fallback here, since they take only their native size. On other
- * connectors mode_valid can also reject the preferred mode, and then
- * the first valid mode is the right one
- *
- * Take a mode of the native size from connector->modes if one is left,
- * since mode_valid may have rejected the preferred timing itself. The
- * probe sorts the list, so the first one has the highest refresh rate
- * left. If the limits pruned them all, use the copy of the preferred
- * mode that amdgpu_dm_get_native_mode() keeps in the encoder. It is
- * made on every probe, before the limits apply
- */
- if (!preferred_mode && aconnector && aconnector->drm_edid &&
- (connector->connector_type == DRM_MODE_CONNECTOR_eDP ||
- connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) {
- struct drm_encoder *encoder = amdgpu_dm_connector_to_encoder(connector);
- const struct drm_display_mode *enc_native =
- encoder ? &to_amdgpu_encoder(encoder)->native_mode : NULL;
-
- if (enc_native && enc_native->clock) {
- list_for_each_entry(connector_mode, &connector->modes, head) {
- if (connector_mode->hdisplay == enc_native->hdisplay &&
- connector_mode->vdisplay == enc_native->vdisplay) {
- preferred_mode = connector_mode;
- break;
- }
- }
-
- /*
- * saved_mode is only used for FreeSync video modes, and
- * that path doesn't read preferred_mode
- */
- if (!preferred_mode) {
- drm_mode_init(&saved_mode, enc_native);
- drm_mode_set_crtcinfo(&saved_mode, CRTC_INTERLACE_HALVE_V);
- preferred_mode = &saved_mode;
- }
- }
- }
-
- if (!preferred_mode)
- preferred_mode = list_first_entry_or_null(
- &connector->modes,
- struct drm_display_mode,
- head);
-
mode_refresh = drm_mode_vrefresh(&mode);
- if (preferred_mode == NULL) {
- /*
- * This may not be an error, the use case is when we have no
- * usermode calls to reset and set mode upon hotplug. In this
- * case, we call set mode ourselves to restore the previous mode
- * and the modelist may not be filled in time.
- */
- drm_dbg_driver(dev, "No preferred mode found\n");
- } else if (aconnector) {
+ if (aconnector && preferred_mode) {
recalculate_timing = amdgpu_freesync_vid_mode &&
amdgpu_dm_is_freesync_video_mode(&mode, aconnector);
if (recalculate_timing) {
@@ -2337,11 +2326,12 @@ dm_validate_stream_color_format(const struct drm_connector_state *drm_state,
DC_OK : DC_UNSUPPORTED_VALUE;
}
-struct dc_stream_state *
-amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
- const struct drm_display_mode *drm_mode,
- const struct dm_connector_state *dm_state,
- const struct dc_stream_state *old_stream)
+static struct dc_stream_state *
+__amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
{
/*
* Ordered lists of the encodings and bit depths we are willing to
@@ -2383,8 +2373,8 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
* meaningful for the writeback path).
*/
if (!aconnector) {
- stream = create_stream_for_sink(connector, drm_mode, dm_state,
- old_stream, requested_bpc,
+ stream = create_stream_for_sink(connector, drm_mode, preferred_mode,
+ dm_state, old_stream, requested_bpc,
PIXEL_ENCODING_RGB, is_hdmi_ep);
if (!stream)
drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n");
@@ -2514,6 +2504,7 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
encoding_mask, bpc_mask, requested_bpc);
stream = create_stream_for_sink(connector, drm_mode,
+ preferred_mode,
dm_state, old_stream,
bpc, enc, is_hdmi_ep);
if (stream == NULL) {
@@ -2555,6 +2546,63 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
*/
return NULL;
}
+
+STATIC_IFN_KUNIT const struct drm_display_mode *
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
+ struct drm_display_mode *native_buf)
+{
+ const struct drm_display_mode *mode;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+ return NULL;
+
+ list_for_each_entry(mode, &connector->modes, head) {
+ if (mode->type & DRM_MODE_TYPE_PREFERRED)
+ return mode;
+ }
+
+ mode = amdgpu_dm_panel_native_mode(connector, &connector->modes, native_buf);
+ if (mode)
+ return mode;
+
+ mode = list_first_entry_or_null(&connector->modes,
+ struct drm_display_mode, head);
+ if (!mode) {
+ /*
+ * This may not be an error, the use case is when we have no
+ * usermode calls to reset and set mode upon hotplug. In this
+ * case, we call set mode ourselves to restore the previous mode
+ * and the modelist may not be filled in time
+ */
+ drm_dbg_driver(connector->dev, "No preferred mode found\n");
+ }
+
+ return mode;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_preferred_mode);
+
+struct dc_stream_state *
+amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
+{
+ const struct drm_display_mode *preferred_mode;
+ struct drm_display_mode native_buf;
+
+ /*
+ * __amdgpu_dm_create_validate_stream_for_sink() checks this as well,
+ * but without a state there is no commit, so skip the preferred mode
+ * lookup too
+ */
+ if (!dm_state)
+ return NULL;
+
+ preferred_mode = amdgpu_dm_connector_preferred_mode(connector, &native_buf);
+ return __amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
+ preferred_mode, dm_state,
+ old_stream);
+}
EXPORT_IF_KUNIT(amdgpu_dm_create_validate_stream_for_sink);
enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector,
@@ -2569,6 +2617,7 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
* here via the amdgpu_dm_connector_helper_funcs
*/
struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ struct dm_connector_state *dm_state = to_dm_connector_state(connector->state);
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
(mode->flags & DRM_MODE_FLAG_DBLSCAN))
@@ -2596,9 +2645,16 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
drm_mode_set_crtcinfo(test_mode, 0);
- stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
- to_dm_connector_state(connector->state),
- NULL);
+ /*
+ * During a probe connector->modes still holds the previous probe's
+ * modes, possibly of another sink, and the probe knows its preferred
+ * mode only once mode_valid has seen every mode. Validate each mode
+ * with its own timing, even where atomic_check drives another one, the
+ * preferred mode's with scaling on or the native one for eDP and LVDS
+ * common modes
+ */
+ stream = __amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
+ NULL, dm_state, NULL);
drm_mode_destroy(connector->dev, test_mode);
if (stream) {
dc_stream_release(stream);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 7bfbb68538335..e7ed9a0bf56fb 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -196,11 +196,15 @@ void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *
create_stream_for_sink(struct drm_connector *connector,
const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
const struct dm_connector_state *dm_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
enum dc_pixel_encoding requested_encoding,
bool is_hdmi_ep);
+const struct drm_display_mode *
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
+ struct drm_display_mode *native_buf);
enum drm_connector_status
amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force);
enum drm_connector_status
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 5af111698e355..e8cabe25a192d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -4277,7 +4277,7 @@ static void dm_test_create_stream_fake_sink_success(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4294,7 +4294,7 @@ static void dm_test_create_stream_sets_dm_context(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4312,7 +4312,7 @@ static void dm_test_create_stream_virtual_signal(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4332,7 +4332,7 @@ static void dm_test_create_stream_scaling_src(struct kunit *test)
struct dc_stream_state *stream;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4364,7 +4364,7 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
ctx->aconnector->dc_sink = sink;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4431,6 +4431,8 @@ static void dm_test_create_stream_preferred_mode(struct kunit *test)
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ list_first_entry(&ctx->aconnector->base.modes,
+ struct drm_display_mode, head),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -4459,6 +4461,8 @@ static void dm_test_create_stream_scaled_keeps_refresh(struct kunit *test)
ctx->dm_state->scaling = RMX_FULL;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ list_first_entry(&ctx->aconnector->base.modes,
+ struct drm_display_mode, head),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -4490,7 +4494,7 @@ static void dm_test_create_stream_timing_override(struct kunit *test)
ctx->aconnector->timing_requested = timing;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4519,7 +4523,7 @@ static void dm_test_create_stream_dp_vsc_colorimetry(struct kunit *test)
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4547,7 +4551,7 @@ static void dm_test_create_stream_dp_colorimetry_quirk(struct kunit *test)
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4574,7 +4578,7 @@ static void dm_test_create_stream_hdmi_vsif(struct kunit *test)
sink = dm_test_stream_add_sink(test, ctx, SIGNAL_TYPE_HDMI_TYPE_A);
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL, 8,
+ NULL, ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
@@ -4609,8 +4613,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
static const u8 edid[EDID_LENGTH];
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
struct amdgpu_encoder *aenc;
+ struct drm_display_mode *native, native_buf;
struct dc_stream_state *stream;
- struct drm_display_mode *native;
aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aenc);
@@ -4659,6 +4663,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
}
stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
+ amdgpu_dm_connector_preferred_mode(&ctx->aconnector->base,
+ &native_buf),
ctx->dm_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
@@ -7479,6 +7485,98 @@ static void dm_test_create_validate_stream_hdmi_rgb_first(struct kunit *test)
KUNIT_EXPECT_EQ(test, (int)dm_test_cvs_first_encoding, (int)PIXEL_ENCODING_RGB);
}
+static u32 dm_test_cvs_first_h_addressable;
+
+static bool dm_test_cvs_record_first_h_addressable(struct timing_generator *tg,
+ const struct dc_crtc_timing *timing)
+{
+ if (!dm_test_cvs_first_h_addressable)
+ dm_test_cvs_first_h_addressable = timing->h_addressable;
+
+ return false;
+}
+
+/*
+ * A 3840x2160 preferred mode in connector->modes, as a probe of another sink
+ * leaves it until the next probe prunes it
+ */
+static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_dc *c)
+{
+ struct drm_display_mode *pref = drm_mode_create(c->drm);
+
+ KUNIT_ASSERT_NOT_NULL(test, pref);
+ pref->hdisplay = 3840;
+ pref->hsync_start = 4016;
+ pref->hsync_end = 4104;
+ pref->htotal = 4400;
+ pref->vdisplay = 2160;
+ pref->vsync_start = 2168;
+ pref->vsync_end = 2178;
+ pref->vtotal = 2250;
+ pref->clock = 594000;
+ pref->type = DRM_MODE_TYPE_PREFERRED;
+ drm_mode_set_crtcinfo(pref, CRTC_INTERLACE_HALVE_V);
+ list_add_tail(&pref->head, &c->aconnector->base.modes);
+}
+
+/**
+ * dm_test_create_validate_stream_scaled_preferred - Test atomic_check scales to the preferred mode
+ * @test: The KUnit test context
+ *
+ * With scaling on, the stream for a 1920x1080 mode takes the CRTC timing of
+ * the preferred mode in connector->modes
+ */
+static void dm_test_create_validate_stream_scaled_preferred(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+ c->dm_state->scaling = RMX_FULL;
+ dm_test_cvs_add_preferred_4k(test, c);
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_NULL(test,
+ amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
+}
+
+/**
+ * dm_test_mode_valid_ignores_preferred_mode - Test mode_valid uses the mode's own timing
+ * @test: The KUnit test context
+ *
+ * During a probe connector->modes still holds the previous sink's preferred
+ * mode. Even with scaling on, the mode is validated with its own 1920x1080
+ * timing rather than that mode's 3840x2160 one
+ */
+static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+ struct dc_sink *em_sink;
+
+ em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, em_sink);
+ c->aconnector->dc_em_sink = em_sink;
+ c->aconnector->base.force = DRM_FORCE_ON;
+
+ /* drm_connector_cleanup() kfree()s connector->state */
+ KUNIT_ASSERT_NULL(test, c->aconnector->base.state);
+ amdgpu_dm_connector_funcs_reset(&c->aconnector->base);
+ KUNIT_ASSERT_NOT_NULL(test, c->aconnector->base.state);
+ to_dm_connector_state(c->aconnector->base.state)->scaling = RMX_FULL;
+
+ dm_test_cvs_add_preferred_4k(test, c);
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_EQ(test,
+ amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode),
+ MODE_ERROR);
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
+}
+
/* Further tests for amdgpu_dm_connector_mode_valid() */
/**
@@ -10695,6 +10793,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
+ KUNIT_CASE(dm_test_create_validate_stream_scaled_preferred),
+ KUNIT_CASE(dm_test_mode_valid_ignores_preferred_mode),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 4/8] drm/amd/display: Copy the preferred and highest refresh modes after probe
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
atomic_check walks connector->modes for the preferred mode and the
highest refresh rate mode without mode_config.mutex, so a probe on
another thread can rebuild the list under it
Add amdgpu_dm_connector_fill_modes() as the fill_modes hook of SST and
MST connectors. After the probe it copies both modes and the dc_sink's
EDID into struct amdgpu_dm_connector, under connection_mutex, which
atomic_check holds. On eDP and LVDS the preferred copy falls back to the
panel's native mode, as atomic_check's lookup does
The highest refresh rate copy is the mode add_fs_modes() built the
FreeSync video modes from, which it now records in
probed_freesync_vid_base, if validation kept it
amdgpu_dm_get_highest_refresh_rate_mode() returns the copy, except to
the probe's own callers, add_fs_modes() and the max_vfreq fallback,
which need the ongoing probe's modes. For an EDID without a preferred
mode, those callers now fall back to the first mode of this probe, not
of the previous one
An upcoming patch switches the readers to the copies, for a sink with
the same EDID only
Link: https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Comparing the whole EDID costs about as much as a CRC32 of it and can't
collide. The copy sits inline, as raw_edid does in dc_sink, so it needs
no allocation
A dc_sink pointer match won't do, since dm_resume() and a replug replace
dc_sink, and the copies of the same monitor would be dropped on every
resume
dm_resume() and the MST connector's ->early_unregister() release dc_sink
without mode_config.mutex, so reading its EDID here can race with them,
as ->get_modes() and ->mode_valid() already do. I have an upcoming
series that takes mode_config.mutex around those releases
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 20 +-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 197 ++++++--
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 3 +
.../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 516 +++++++++++++++++++++
5 files changed, 702 insertions(+), 36 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index f8503ebf28f47..97a74178513a9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -902,8 +902,26 @@ struct amdgpu_dm_connector {
struct dsc_preferred_settings dsc_settings;
struct psr_caps psr_caps;
union dp_downstream_port_present mst_downstream_port_present;
- /* Cached display modes */
+ /*
+ * Copies of the preferred mode in base.modes and of the highest
+ * refresh rate mode at its size, with the EDID of the dc_sink they
+ * came from, or a size of 0 if it had none. After each probe,
+ * amdgpu_dm_connector_fill_modes() writes them with
+ * &drm_mode_config.mutex and &drm_mode_config.connection_mutex held,
+ * so either lock is enough to read them. atomic_check holds only the
+ * modeset locks, so it reads these instead of base.modes
+ */
+ struct drm_display_mode preferred_mode;
struct drm_display_mode freesync_vid_base;
+ u8 mode_copies_edid[DC_MAX_EDID_BUFFER_SIZE];
+ u32 mode_copies_edid_size;
+ /*
+ * The mode add_fs_modes() built the FreeSync video modes from in the
+ * last probe, or zeroed if it built none. It comes from the EDID,
+ * while base.modes can also hold a command line mode. Protected by
+ * &drm_mode_config.mutex
+ */
+ struct drm_display_mode probed_freesync_vid_base;
int sr_skip_count;
bool disallow_edp_enter_psr;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index e477ef61ce109..491965266b786 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1108,6 +1108,74 @@ amdgpu_dm_panel_native_mode(struct drm_connector *connector, struct list_head *m
return native_buf;
}
+static struct drm_display_mode *amdgpu_dm_find_preferred_mode(struct list_head *modes)
+{
+ struct drm_display_mode *m;
+
+ list_for_each_entry(m, modes, head) {
+ if (m->type & DRM_MODE_TYPE_PREFERRED)
+ return m;
+ }
+
+ return NULL;
+}
+
+/*
+ * Find the mode with the highest refresh rate at the size of @m_pref. For
+ * some monitors the preferred mode is not the one with the highest supported
+ * refresh rate
+ */
+static struct drm_display_mode *
+amdgpu_dm_find_highest_refresh_mode(struct list_head *modes,
+ struct drm_display_mode *m_pref)
+{
+ int highest_refresh = drm_mode_vrefresh(m_pref);
+ struct drm_display_mode *best = m_pref, *m;
+
+ list_for_each_entry(m, modes, head) {
+ int current_refresh = drm_mode_vrefresh(m);
+
+ if (m->hdisplay == m_pref->hdisplay &&
+ m->vdisplay == m_pref->vdisplay &&
+ highest_refresh < current_refresh) {
+ highest_refresh = current_refresh;
+ best = m;
+ }
+ }
+
+ return best;
+}
+
+/* The size of @sink's EDID, 0 without a sink or EDID */
+static u32 amdgpu_dm_sink_edid_size(const struct dc_sink *sink)
+{
+ return sink ? min_t(u32, sink->dc_edid.length, sizeof(sink->dc_edid.raw_edid)) : 0;
+}
+
+/*
+ * Whether @modes still holds @vid_base, at the size of @preferred. Validation
+ * may have pruned the FreeSync video modes and their base after add_fs_modes()
+ * recorded it
+ */
+static bool
+amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
+ const struct drm_display_mode *vid_base,
+ const struct drm_display_mode *preferred)
+{
+ const struct drm_display_mode *m;
+
+ if (!vid_base->clock || vid_base->hdisplay != preferred->hdisplay ||
+ vid_base->vdisplay != preferred->vdisplay)
+ return false;
+
+ list_for_each_entry(m, modes, head) {
+ if (drm_mode_equal(m, vid_base))
+ return true;
+ }
+
+ return false;
+}
+
/**
* DOC: FreeSync Video
*
@@ -1142,8 +1210,7 @@ struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
bool use_probed_modes)
{
- struct drm_display_mode *m, *m_pref = NULL;
- u16 current_refresh, highest_refresh;
+ struct drm_display_mode *m_pref;
struct list_head *list_head = use_probed_modes ?
&aconnector->base.probed_modes :
&aconnector->base.modes;
@@ -1151,47 +1218,25 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
- if (aconnector->freesync_vid_base.clock != 0)
+ /*
+ * A probe reads its own probed_modes, not the copy of the last one. The
+ * copy stands in while probed_modes is empty
+ */
+ if (aconnector->freesync_vid_base.clock != 0 &&
+ !(use_probed_modes && !list_empty(list_head)))
return &aconnector->freesync_vid_base;
- /* Find the preferred mode */
- list_for_each_entry(m, list_head, head) {
- if (m->type & DRM_MODE_TYPE_PREFERRED) {
- m_pref = m;
- break;
- }
- }
-
+ m_pref = amdgpu_dm_find_preferred_mode(list_head);
if (!m_pref) {
/* Probably an EDID with no preferred mode. Fallback to first entry */
- m_pref = list_first_entry_or_null(
- &aconnector->base.modes, struct drm_display_mode, head);
+ m_pref = list_first_entry_or_null(list_head, struct drm_display_mode, head);
if (!m_pref) {
drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
return NULL;
}
}
- highest_refresh = drm_mode_vrefresh(m_pref);
-
- /*
- * Find the mode with highest refresh rate with same resolution.
- * For some monitors, preferred mode is not the mode with highest
- * supported refresh rate.
- */
- list_for_each_entry(m, list_head, head) {
- current_refresh = drm_mode_vrefresh(m);
-
- if (m->hdisplay == m_pref->hdisplay &&
- m->vdisplay == m_pref->vdisplay &&
- highest_refresh < current_refresh) {
- highest_refresh = current_refresh;
- m_pref = m;
- }
- }
-
- drm_mode_copy(&aconnector->freesync_vid_base, m_pref);
- return m_pref;
+ return amdgpu_dm_find_highest_refresh_mode(list_head, m_pref);
}
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
@@ -1223,6 +1268,87 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
}
EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode);
+STATIC_IFN_KUNIT void
+amdgpu_dm_connector_update_mode_copies(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_mode_config *config = &aconnector->base.dev->mode_config;
+ struct list_head *modes = &aconnector->base.modes;
+ const struct drm_display_mode *preferred, *highest;
+ struct drm_display_mode native_buf;
+ const struct dc_sink *sink;
+
+ lockdep_assert_held(&config->mutex);
+
+ drm_modeset_lock(&config->connection_mutex, NULL);
+
+ sink = aconnector->dc_sink;
+ aconnector->mode_copies_edid_size = amdgpu_dm_sink_edid_size(sink);
+ if (aconnector->mode_copies_edid_size)
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid,
+ aconnector->mode_copies_edid_size);
+
+ preferred = amdgpu_dm_find_preferred_mode(modes);
+ if (!preferred)
+ preferred = amdgpu_dm_panel_native_mode(&aconnector->base, modes, &native_buf);
+ if (!preferred)
+ preferred = list_first_entry_or_null(modes, struct drm_display_mode, head);
+
+ if (!preferred) {
+ memset(&aconnector->preferred_mode, 0, sizeof(aconnector->preferred_mode));
+ memset(&aconnector->freesync_vid_base, 0, sizeof(aconnector->freesync_vid_base));
+ goto unlock;
+ }
+
+ drm_mode_copy(&aconnector->preferred_mode, preferred);
+
+ /*
+ * The FreeSync video modes differ from their base only in vtotal, and
+ * atomic_check matches them against this copy. A command line mode at
+ * the preferred size with a higher refresh rate would win the search
+ * below, so take the base add_fs_modes() used, if validation kept it
+ * and it has the preferred mode's size
+ */
+ if (amdgpu_dm_probed_freesync_vid_base_listed(modes,
+ &aconnector->probed_freesync_vid_base,
+ &aconnector->preferred_mode))
+ highest = &aconnector->probed_freesync_vid_base;
+ else
+ highest = amdgpu_dm_find_highest_refresh_mode(modes, &aconnector->preferred_mode);
+
+ drm_mode_copy(&aconnector->freesync_vid_base, highest);
+
+unlock:
+ drm_modeset_unlock(&config->connection_mutex);
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_update_mode_copies);
+
+/*
+ * atomic_check needs the preferred and highest refresh rate modes, but
+ * &drm_mode_config.mutex protects base.modes and atomic_check can't take it.
+ * Copy them after each probe. amdgpu_dm_connector_update_mode_copies() also
+ * takes &drm_mode_config.connection_mutex, so atomic_check can read the copies
+ * with the lock it holds
+ */
+int amdgpu_dm_connector_fill_modes(struct drm_connector *connector,
+ u32 max_width, u32 max_height)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ int count;
+
+ lockdep_assert_held(&connector->dev->mode_config.mutex);
+
+ /* The probe helper skips ->get_modes() for a disconnected connector */
+ memset(&aconnector->probed_freesync_vid_base, 0,
+ sizeof(aconnector->probed_freesync_vid_base));
+
+ count = drm_helper_probe_single_connector_modes(connector, max_width,
+ max_height);
+ amdgpu_dm_connector_update_mode_copies(aconnector);
+
+ return count;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_fill_modes);
+
#if defined(CONFIG_DRM_AMD_DC_FP)
static void update_dsc_caps(struct amdgpu_dm_connector *aconnector,
struct dc_sink *sink, struct dc_stream_state *stream,
@@ -2150,7 +2276,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_force);
static const struct drm_connector_funcs amdgpu_dm_connector_funcs = {
.reset = amdgpu_dm_connector_funcs_reset,
.detect = amdgpu_dm_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
+ .fill_modes = amdgpu_dm_connector_fill_modes,
.destroy = amdgpu_dm_connector_destroy,
.atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
@@ -3199,6 +3325,9 @@ STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
drm_mode_destroy(aconnector->base.dev, new_mode);
}
out:
+ if (new_modes_count)
+ drm_mode_copy(&aconnector->probed_freesync_vid_base, m);
+
return new_modes_count;
}
EXPORT_IF_KUNIT(add_fs_modes);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index e7ed9a0bf56fb..ee8d7eabd7c13 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -51,6 +51,8 @@ struct drm_edid;
struct drm_property;
void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector);
+int amdgpu_dm_connector_fill_modes(struct drm_connector *connector,
+ u32 max_width, u32 max_height);
struct drm_connector_state *
amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector);
@@ -170,6 +172,7 @@ void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector);
uint add_fs_modes(struct amdgpu_dm_connector *aconnector);
+void amdgpu_dm_connector_update_mode_copies(struct amdgpu_dm_connector *aconnector);
void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
const struct drm_edid *drm_edid);
void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
index 5697645ef7cca..83021470c8b2c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
@@ -256,7 +256,7 @@ amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector)
EXPORT_IF_KUNIT(amdgpu_dm_mst_connector_early_unregister);
static const struct drm_connector_funcs dm_dp_mst_connector_funcs = {
- .fill_modes = drm_helper_probe_single_connector_modes,
+ .fill_modes = amdgpu_dm_connector_fill_modes,
.destroy = dm_dp_mst_connector_destroy,
.reset = amdgpu_dm_connector_funcs_reset,
.atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index e8cabe25a192d..49e85aec3c665 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2457,6 +2457,383 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode);
}
+/* Tests for amdgpu_dm_connector_update_mode_copies() */
+
+static struct drm_display_mode *
+dm_test_copy_add_mode(struct kunit *test, struct list_head *modes,
+ u16 hdisplay, u16 vdisplay, u16 htotal, u16 vtotal,
+ int clock, u32 type)
+{
+ struct drm_display_mode *mode;
+
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ mode->hdisplay = hdisplay;
+ mode->vdisplay = vdisplay;
+ mode->htotal = htotal;
+ mode->vtotal = vtotal;
+ mode->clock = clock;
+ mode->type = type;
+ list_add_tail(&mode->head, modes);
+
+ return mode;
+}
+
+static struct drm_device *dm_test_alloc_drm(struct kunit *test);
+
+/*
+ * Take the copies as amdgpu_dm_connector_fill_modes() does, with
+ * &drm_mode_config.mutex held. A connector without a device gets one for the
+ * locks, without registering the connector with it
+ */
+static void dm_test_copy_locked(struct kunit *test, struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_mode_config *config;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ config = &aconnector->base.dev->mode_config;
+
+ mutex_lock(&config->mutex);
+ amdgpu_dm_connector_update_mode_copies(aconnector);
+ mutex_unlock(&config->mutex);
+}
+
+/**
+ * dm_test_highest_refresh_probed_skips_copy - Test a probe reads probed_modes
+ * @test: The KUnit test context
+ *
+ * The copy is from the previous probe, possibly of another monitor, so a
+ * caller that passes use_probed_modes must get this probe's mode
+ */
+static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+
+ mode = dm_test_copy_add_mode(test, &aconnector->base.probed_modes, 2560, 1440,
+ 2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
+
+ KUNIT_EXPECT_PTR_EQ(test,
+ amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true),
+ mode);
+}
+
+/**
+ * dm_test_update_mode_copies_highest_refresh - Test the copies taken from base.modes
+ * @test: The KUnit test context
+ *
+ * The preferred mode is copied as is. The highest refresh rate copy comes from
+ * the preferred size only, even when another size has a higher refresh rate
+ */
+static void dm_test_update_mode_copies_highest_refresh(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1280, 720, 1650, 750, 297000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500,
+ DRM_MODE_TYPE_PREFERRED);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 297000, 0);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 148500);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_TRUE(test, aconnector->preferred_mode.type & DRM_MODE_TYPE_PREFERRED);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 297000);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.vdisplay, 1080);
+}
+
+/**
+ * dm_test_update_mode_copies_no_preferred - Test the first mode stands in for a preferred one
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_no_preferred(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500, 0);
+ dm_test_copy_add_mode(test, modes, 1280, 720, 1650, 750, 297000, 0);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 148500);
+}
+
+/**
+ * dm_test_update_mode_copies_records_edid - Test the copies record the sink's EDID
+ * @test: The KUnit test context
+ *
+ * atomic_check compares the EDID of the sink it builds a stream for with the
+ * one the copies were taken with
+ */
+static void dm_test_update_mode_copies_records_edid(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+ u8 *edid;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->dc_sink = sink;
+
+ edid = sink->dc_edid.raw_edid;
+ memset(edid, 0xa5, 256);
+ sink->dc_edid.length = 256;
+
+ dm_test_copy_add_mode(test, &aconnector->base.modes, 1920, 1080, 2200, 1125,
+ 148500, DRM_MODE_TYPE_PREFERRED);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 256);
+ KUNIT_EXPECT_MEMEQ(test, aconnector->mode_copies_edid, edid, 256);
+}
+
+/**
+ * dm_test_update_mode_copies_no_edid - Test copies without a sink EDID record none
+ * @test: The KUnit test context
+ *
+ * Without a sink, or with one that has no EDID, nothing identifies the
+ * monitor, so the copies must not match any sink later
+ */
+static void dm_test_update_mode_copies_no_edid(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->mode_copies_edid_size = 128;
+ memset(aconnector->mode_copies_edid, 0x5a, 128);
+
+ dm_test_copy_add_mode(test, &aconnector->base.modes, 1920, 1080, 2200, 1125,
+ 148500, DRM_MODE_TYPE_PREFERRED);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 0);
+
+ aconnector->dc_sink = sink;
+ aconnector->mode_copies_edid_size = 128;
+ memset(aconnector->mode_copies_edid, 0x5a, 128);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 0);
+}
+
+/**
+ * dm_test_update_mode_copies_empty - Test an empty mode list clears both copies
+ * @test: The KUnit test context
+ *
+ * A disconnected connector ends its probe with no modes. Copies from an
+ * earlier probe must not survive that
+ */
+static void dm_test_update_mode_copies_empty(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->preferred_mode.clock = 148500;
+ aconnector->freesync_vid_base.clock = 297000;
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 0);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
+}
+
+/**
+ * dm_test_update_mode_copies_freesync_vid_base - Test the FreeSync base beats a command line mode
+ * @test: The KUnit test context
+ *
+ * A video= mode at the preferred size with a higher refresh rate than any EDID
+ * mode is in base.modes, but the FreeSync video modes were built from
+ * probed_freesync_vid_base, so that is the copy atomic_check gets
+ */
+static void dm_test_update_mode_copies_freesync_vid_base(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500,
+ DRM_MODE_TYPE_PREFERRED);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 297000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 346500,
+ DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_USERDEF);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 346500);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 1920;
+ aconnector->probed_freesync_vid_base.vdisplay = 1080;
+ aconnector->probed_freesync_vid_base.htotal = 2200;
+ aconnector->probed_freesync_vid_base.vtotal = 1125;
+ aconnector->probed_freesync_vid_base.clock = 297000;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 297000);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 148500);
+}
+
+/**
+ * dm_test_update_mode_copies_freesync_vid_base_pruned - Test a pruned FreeSync base is not copied
+ * @test: The KUnit test context
+ *
+ * add_fs_modes() records probed_freesync_vid_base before validation. If
+ * validation pruned its size, or the base itself, the copy must be the highest
+ * refresh rate mode at the preferred size that base.modes still holds
+ */
+static void dm_test_update_mode_copies_freesync_vid_base_pruned(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1200, 2080, 1235, 154000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1200, 2080, 1235, 308000, 0);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 3840;
+ aconnector->probed_freesync_vid_base.vdisplay = 2400;
+ aconnector->probed_freesync_vid_base.htotal = 4000;
+ aconnector->probed_freesync_vid_base.vtotal = 2469;
+ aconnector->probed_freesync_vid_base.clock = 592500;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.vdisplay, 1200);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 308000);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 1920;
+ aconnector->probed_freesync_vid_base.vdisplay = 1200;
+ aconnector->probed_freesync_vid_base.htotal = 2080;
+ aconnector->probed_freesync_vid_base.vtotal = 1235;
+ aconnector->probed_freesync_vid_base.clock = 231000;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 308000);
+}
+
+/* Tests for amdgpu_dm_connector_fill_modes() */
+
+static int dm_test_fill_modes_get_modes(struct drm_connector *connector)
+{
+ static const int refresh[] = { 60, 120 };
+ struct drm_display_mode *mode;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(refresh); i++) {
+ mode = drm_cvt_mode(connector->dev, 1920, 1080, refresh[i],
+ false, false, false);
+ if (!mode)
+ return i;
+ if (i == 0)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ }
+
+ return i;
+}
+
+static const struct drm_connector_helper_funcs dm_test_fill_modes_helper_funcs = {
+ .get_modes = dm_test_fill_modes_get_modes,
+};
+
+static const struct drm_connector_funcs dm_test_fill_modes_funcs = {
+ .fill_modes = amdgpu_dm_connector_fill_modes,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/**
+ * dm_test_fill_modes_copies_probe - Test fill_modes copies the modes of its probe
+ * @test: The KUnit test context
+ *
+ * The hook returns the probe helper's mode count and takes the copies and the
+ * sink's EDID from the modes that probe left in base.modes
+ */
+static void dm_test_fill_modes_copies_probe(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_device *drm;
+ struct device *dev;
+ struct dc_sink *sink;
+ int count;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ connector = &aconnector->base;
+ KUNIT_ASSERT_EQ(test, drmm_connector_init(drm, connector, &dm_test_fill_modes_funcs,
+ DRM_MODE_CONNECTOR_HDMIA, NULL), 0);
+ drm_connector_helper_add(connector, &dm_test_fill_modes_helper_funcs);
+
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->dc_sink = sink;
+
+ mutex_lock(&drm->mode_config.mutex);
+ count = connector->funcs->fill_modes(connector, 4096, 4096);
+ mutex_unlock(&drm->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 2);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(&aconnector->preferred_mode), 60);
+ KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(&aconnector->freesync_vid_base), 120);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 128);
+ KUNIT_EXPECT_MEMEQ(test, aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+}
+
/* Tests for amdgpu_dm_is_freesync_video_mode() */
/**
@@ -4800,6 +5177,127 @@ static void dm_test_create_stream_native_mode_no_edid_edp(struct kunit *test)
DM_TEST_NATIVE_NO_EDID));
}
+/*
+ * Copy the modes after a probe with size limits below the native mode. The
+ * probe left a 1920x1200 mode in base.modes, and with @listed also a 3840x2400
+ * mode at half the native clock, while the encoder keeps the 3840x2400 native
+ * mode
+ */
+static struct amdgpu_dm_connector *
+dm_test_copy_pruned(struct kunit *test, int connector_type, bool listed)
+{
+ static const u8 edid[EDID_LENGTH];
+ struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
+ struct amdgpu_dm_connector *aconnector = ctx->aconnector;
+ struct list_head *modes = &aconnector->base.modes;
+ struct drm_display_mode *native, *mode;
+ struct amdgpu_encoder *aenc;
+
+ aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aenc);
+ KUNIT_ASSERT_EQ(test, drmm_encoder_init(ctx->drm, &aenc->base, NULL,
+ DRM_MODE_ENCODER_TMDS, NULL), 0);
+ KUNIT_ASSERT_EQ(test, drm_connector_attach_encoder(&aconnector->base, &aenc->base), 0);
+
+ native = &aenc->native_mode;
+ native->clock = 592500;
+ native->hdisplay = 3840;
+ native->hsync_start = 3888;
+ native->hsync_end = 3920;
+ native->htotal = 4000;
+ native->vdisplay = 2400;
+ native->vsync_start = 2403;
+ native->vsync_end = 2409;
+ native->vtotal = 2469;
+
+ mode = drm_mode_duplicate(ctx->drm, native);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ mode->hdisplay = 1920;
+ mode->vdisplay = 1200;
+ list_add_tail(&mode->head, modes);
+
+ if (listed) {
+ mode = drm_mode_duplicate(ctx->drm, native);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ mode->clock /= 2;
+ list_add_tail(&mode->head, modes);
+ }
+
+ aconnector->drm_edid = drm_edid_alloc(edid, sizeof(edid));
+ KUNIT_ASSERT_NOT_NULL(test, aconnector->drm_edid);
+
+ dm_test_copy_locked(test, aconnector);
+
+ drm_edid_free(aconnector->drm_edid);
+ aconnector->drm_edid = NULL;
+
+ return aconnector;
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_edp - Test eDP copies the encoder's native mode
+ * @test: The KUnit test context
+ *
+ * With the native size pruned from base.modes, the preferred mode copy must be
+ * the panel's native mode, with its CRTC timing set, so that atomic_check
+ * still scales to it
+ */
+static void dm_test_update_mode_copies_pruned_edp(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_eDP, false);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.vdisplay, 2400);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 592500);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.crtc_clock, 592500);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.crtc_htotal, 4000);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 592500);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_lvds - Test LVDS copies the encoder's native mode
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_pruned_lvds(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_LVDS, false);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 592500);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_listed_edp - Test eDP copies a listed native size mode
+ * @test: The KUnit test context
+ *
+ * mode_valid may have rejected the encoder's native timing, so a mode of the
+ * native size left in base.modes must be copied in its place
+ */
+static void dm_test_update_mode_copies_pruned_listed_edp(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_eDP, true);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 296250);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_hdmi - Test HDMI copies the first mode
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_pruned_hdmi(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_HDMIA, true);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.vdisplay, 1200);
+}
+
/* Tests for amdgpu_dm_connector_detect() */
/*
@@ -6332,6 +6830,8 @@ static void dm_test_add_fs_modes_generates(struct kunit *test)
KUNIT_EXPECT_EQ(test, count, 8);
KUNIT_EXPECT_EQ(test, duplicate_count, 0);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 148500);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.vtotal, 1125);
}
/**
@@ -6350,6 +6850,7 @@ static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
aconnector->max_vfreq = 120;
KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 0);
}
/**
@@ -10576,6 +11077,17 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_highest_refresh_writeback_null),
KUNIT_CASE(dm_test_highest_refresh_cached_base),
KUNIT_CASE(dm_test_highest_refresh_preferred_mode),
+ KUNIT_CASE(dm_test_highest_refresh_probed_skips_copy),
+ /* amdgpu_dm_connector_update_mode_copies */
+ KUNIT_CASE(dm_test_update_mode_copies_highest_refresh),
+ KUNIT_CASE(dm_test_update_mode_copies_no_preferred),
+ KUNIT_CASE(dm_test_update_mode_copies_records_edid),
+ KUNIT_CASE(dm_test_update_mode_copies_no_edid),
+ KUNIT_CASE(dm_test_update_mode_copies_empty),
+ KUNIT_CASE(dm_test_update_mode_copies_freesync_vid_base),
+ KUNIT_CASE(dm_test_update_mode_copies_freesync_vid_base_pruned),
+ /* amdgpu_dm_connector_fill_modes */
+ KUNIT_CASE(dm_test_fill_modes_copies_probe),
/* amdgpu_dm_is_freesync_video_mode */
KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode),
KUNIT_CASE(dm_test_is_freesync_video_mode_match),
@@ -10664,6 +11176,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_stream_native_preferred_edp),
KUNIT_CASE(dm_test_create_stream_native_mode_no_clock_edp),
KUNIT_CASE(dm_test_create_stream_native_mode_no_edid_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_lvds),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_listed_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_hdmi),
/* amdgpu_dm_connector_detect */
KUNIT_CASE(dm_test_detect_force_on),
KUNIT_CASE(dm_test_detect_force_on_digital),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 4/8] drm/amd/display: Copy the preferred and highest refresh modes after probe
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
atomic_check walks connector->modes for the preferred mode and the
highest refresh rate mode without mode_config.mutex, so a probe on
another thread can rebuild the list under it
Add amdgpu_dm_connector_fill_modes() as the fill_modes hook of SST and
MST connectors. After the probe it copies both modes and the dc_sink's
EDID into struct amdgpu_dm_connector, under connection_mutex, which
atomic_check holds. On eDP and LVDS the preferred copy falls back to the
panel's native mode, as atomic_check's lookup does
The highest refresh rate copy is the mode add_fs_modes() built the
FreeSync video modes from, which it now records in
probed_freesync_vid_base, if validation kept it
amdgpu_dm_get_highest_refresh_rate_mode() returns the copy, except to
the probe's own callers, add_fs_modes() and the max_vfreq fallback,
which need the ongoing probe's modes. For an EDID without a preferred
mode, those callers now fall back to the first mode of this probe, not
of the previous one
An upcoming patch switches the readers to the copies, for a sink with
the same EDID only
Link: https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Comparing the whole EDID costs about as much as a CRC32 of it and can't
collide. The copy sits inline, as raw_edid does in dc_sink, so it needs
no allocation
A dc_sink pointer match won't do, since dm_resume() and a replug replace
dc_sink, and the copies of the same monitor would be dropped on every
resume
dm_resume() and the MST connector's ->early_unregister() release dc_sink
without mode_config.mutex, so reading its EDID here can race with them,
as ->get_modes() and ->mode_valid() already do. I have an upcoming
series that takes mode_config.mutex around those releases
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 20 +-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 197 ++++++--
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 3 +
.../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 516 +++++++++++++++++++++
5 files changed, 702 insertions(+), 36 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index f8503ebf28f47..97a74178513a9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -902,8 +902,26 @@ struct amdgpu_dm_connector {
struct dsc_preferred_settings dsc_settings;
struct psr_caps psr_caps;
union dp_downstream_port_present mst_downstream_port_present;
- /* Cached display modes */
+ /*
+ * Copies of the preferred mode in base.modes and of the highest
+ * refresh rate mode at its size, with the EDID of the dc_sink they
+ * came from, or a size of 0 if it had none. After each probe,
+ * amdgpu_dm_connector_fill_modes() writes them with
+ * &drm_mode_config.mutex and &drm_mode_config.connection_mutex held,
+ * so either lock is enough to read them. atomic_check holds only the
+ * modeset locks, so it reads these instead of base.modes
+ */
+ struct drm_display_mode preferred_mode;
struct drm_display_mode freesync_vid_base;
+ u8 mode_copies_edid[DC_MAX_EDID_BUFFER_SIZE];
+ u32 mode_copies_edid_size;
+ /*
+ * The mode add_fs_modes() built the FreeSync video modes from in the
+ * last probe, or zeroed if it built none. It comes from the EDID,
+ * while base.modes can also hold a command line mode. Protected by
+ * &drm_mode_config.mutex
+ */
+ struct drm_display_mode probed_freesync_vid_base;
int sr_skip_count;
bool disallow_edp_enter_psr;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index e477ef61ce109..491965266b786 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1108,6 +1108,74 @@ amdgpu_dm_panel_native_mode(struct drm_connector *connector, struct list_head *m
return native_buf;
}
+static struct drm_display_mode *amdgpu_dm_find_preferred_mode(struct list_head *modes)
+{
+ struct drm_display_mode *m;
+
+ list_for_each_entry(m, modes, head) {
+ if (m->type & DRM_MODE_TYPE_PREFERRED)
+ return m;
+ }
+
+ return NULL;
+}
+
+/*
+ * Find the mode with the highest refresh rate at the size of @m_pref. For
+ * some monitors the preferred mode is not the one with the highest supported
+ * refresh rate
+ */
+static struct drm_display_mode *
+amdgpu_dm_find_highest_refresh_mode(struct list_head *modes,
+ struct drm_display_mode *m_pref)
+{
+ int highest_refresh = drm_mode_vrefresh(m_pref);
+ struct drm_display_mode *best = m_pref, *m;
+
+ list_for_each_entry(m, modes, head) {
+ int current_refresh = drm_mode_vrefresh(m);
+
+ if (m->hdisplay == m_pref->hdisplay &&
+ m->vdisplay == m_pref->vdisplay &&
+ highest_refresh < current_refresh) {
+ highest_refresh = current_refresh;
+ best = m;
+ }
+ }
+
+ return best;
+}
+
+/* The size of @sink's EDID, 0 without a sink or EDID */
+static u32 amdgpu_dm_sink_edid_size(const struct dc_sink *sink)
+{
+ return sink ? min_t(u32, sink->dc_edid.length, sizeof(sink->dc_edid.raw_edid)) : 0;
+}
+
+/*
+ * Whether @modes still holds @vid_base, at the size of @preferred. Validation
+ * may have pruned the FreeSync video modes and their base after add_fs_modes()
+ * recorded it
+ */
+static bool
+amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
+ const struct drm_display_mode *vid_base,
+ const struct drm_display_mode *preferred)
+{
+ const struct drm_display_mode *m;
+
+ if (!vid_base->clock || vid_base->hdisplay != preferred->hdisplay ||
+ vid_base->vdisplay != preferred->vdisplay)
+ return false;
+
+ list_for_each_entry(m, modes, head) {
+ if (drm_mode_equal(m, vid_base))
+ return true;
+ }
+
+ return false;
+}
+
/**
* DOC: FreeSync Video
*
@@ -1142,8 +1210,7 @@ struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
bool use_probed_modes)
{
- struct drm_display_mode *m, *m_pref = NULL;
- u16 current_refresh, highest_refresh;
+ struct drm_display_mode *m_pref;
struct list_head *list_head = use_probed_modes ?
&aconnector->base.probed_modes :
&aconnector->base.modes;
@@ -1151,47 +1218,25 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
- if (aconnector->freesync_vid_base.clock != 0)
+ /*
+ * A probe reads its own probed_modes, not the copy of the last one. The
+ * copy stands in while probed_modes is empty
+ */
+ if (aconnector->freesync_vid_base.clock != 0 &&
+ !(use_probed_modes && !list_empty(list_head)))
return &aconnector->freesync_vid_base;
- /* Find the preferred mode */
- list_for_each_entry(m, list_head, head) {
- if (m->type & DRM_MODE_TYPE_PREFERRED) {
- m_pref = m;
- break;
- }
- }
-
+ m_pref = amdgpu_dm_find_preferred_mode(list_head);
if (!m_pref) {
/* Probably an EDID with no preferred mode. Fallback to first entry */
- m_pref = list_first_entry_or_null(
- &aconnector->base.modes, struct drm_display_mode, head);
+ m_pref = list_first_entry_or_null(list_head, struct drm_display_mode, head);
if (!m_pref) {
drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
return NULL;
}
}
- highest_refresh = drm_mode_vrefresh(m_pref);
-
- /*
- * Find the mode with highest refresh rate with same resolution.
- * For some monitors, preferred mode is not the mode with highest
- * supported refresh rate.
- */
- list_for_each_entry(m, list_head, head) {
- current_refresh = drm_mode_vrefresh(m);
-
- if (m->hdisplay == m_pref->hdisplay &&
- m->vdisplay == m_pref->vdisplay &&
- highest_refresh < current_refresh) {
- highest_refresh = current_refresh;
- m_pref = m;
- }
- }
-
- drm_mode_copy(&aconnector->freesync_vid_base, m_pref);
- return m_pref;
+ return amdgpu_dm_find_highest_refresh_mode(list_head, m_pref);
}
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
@@ -1223,6 +1268,87 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
}
EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode);
+STATIC_IFN_KUNIT void
+amdgpu_dm_connector_update_mode_copies(struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_mode_config *config = &aconnector->base.dev->mode_config;
+ struct list_head *modes = &aconnector->base.modes;
+ const struct drm_display_mode *preferred, *highest;
+ struct drm_display_mode native_buf;
+ const struct dc_sink *sink;
+
+ lockdep_assert_held(&config->mutex);
+
+ drm_modeset_lock(&config->connection_mutex, NULL);
+
+ sink = aconnector->dc_sink;
+ aconnector->mode_copies_edid_size = amdgpu_dm_sink_edid_size(sink);
+ if (aconnector->mode_copies_edid_size)
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid,
+ aconnector->mode_copies_edid_size);
+
+ preferred = amdgpu_dm_find_preferred_mode(modes);
+ if (!preferred)
+ preferred = amdgpu_dm_panel_native_mode(&aconnector->base, modes, &native_buf);
+ if (!preferred)
+ preferred = list_first_entry_or_null(modes, struct drm_display_mode, head);
+
+ if (!preferred) {
+ memset(&aconnector->preferred_mode, 0, sizeof(aconnector->preferred_mode));
+ memset(&aconnector->freesync_vid_base, 0, sizeof(aconnector->freesync_vid_base));
+ goto unlock;
+ }
+
+ drm_mode_copy(&aconnector->preferred_mode, preferred);
+
+ /*
+ * The FreeSync video modes differ from their base only in vtotal, and
+ * atomic_check matches them against this copy. A command line mode at
+ * the preferred size with a higher refresh rate would win the search
+ * below, so take the base add_fs_modes() used, if validation kept it
+ * and it has the preferred mode's size
+ */
+ if (amdgpu_dm_probed_freesync_vid_base_listed(modes,
+ &aconnector->probed_freesync_vid_base,
+ &aconnector->preferred_mode))
+ highest = &aconnector->probed_freesync_vid_base;
+ else
+ highest = amdgpu_dm_find_highest_refresh_mode(modes, &aconnector->preferred_mode);
+
+ drm_mode_copy(&aconnector->freesync_vid_base, highest);
+
+unlock:
+ drm_modeset_unlock(&config->connection_mutex);
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_update_mode_copies);
+
+/*
+ * atomic_check needs the preferred and highest refresh rate modes, but
+ * &drm_mode_config.mutex protects base.modes and atomic_check can't take it.
+ * Copy them after each probe. amdgpu_dm_connector_update_mode_copies() also
+ * takes &drm_mode_config.connection_mutex, so atomic_check can read the copies
+ * with the lock it holds
+ */
+int amdgpu_dm_connector_fill_modes(struct drm_connector *connector,
+ u32 max_width, u32 max_height)
+{
+ struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
+ int count;
+
+ lockdep_assert_held(&connector->dev->mode_config.mutex);
+
+ /* The probe helper skips ->get_modes() for a disconnected connector */
+ memset(&aconnector->probed_freesync_vid_base, 0,
+ sizeof(aconnector->probed_freesync_vid_base));
+
+ count = drm_helper_probe_single_connector_modes(connector, max_width,
+ max_height);
+ amdgpu_dm_connector_update_mode_copies(aconnector);
+
+ return count;
+}
+EXPORT_IF_KUNIT(amdgpu_dm_connector_fill_modes);
+
#if defined(CONFIG_DRM_AMD_DC_FP)
static void update_dsc_caps(struct amdgpu_dm_connector *aconnector,
struct dc_sink *sink, struct dc_stream_state *stream,
@@ -2150,7 +2276,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_force);
static const struct drm_connector_funcs amdgpu_dm_connector_funcs = {
.reset = amdgpu_dm_connector_funcs_reset,
.detect = amdgpu_dm_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
+ .fill_modes = amdgpu_dm_connector_fill_modes,
.destroy = amdgpu_dm_connector_destroy,
.atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
@@ -3199,6 +3325,9 @@ STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
drm_mode_destroy(aconnector->base.dev, new_mode);
}
out:
+ if (new_modes_count)
+ drm_mode_copy(&aconnector->probed_freesync_vid_base, m);
+
return new_modes_count;
}
EXPORT_IF_KUNIT(add_fs_modes);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index e7ed9a0bf56fb..ee8d7eabd7c13 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -51,6 +51,8 @@ struct drm_edid;
struct drm_property;
void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector);
+int amdgpu_dm_connector_fill_modes(struct drm_connector *connector,
+ u32 max_width, u32 max_height);
struct drm_connector_state *
amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector);
@@ -170,6 +172,7 @@ void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector,
int amdgpu_dm_connector_get_modes(struct drm_connector *connector);
void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector);
uint add_fs_modes(struct amdgpu_dm_connector *aconnector);
+void amdgpu_dm_connector_update_mode_copies(struct amdgpu_dm_connector *aconnector);
void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector,
const struct drm_edid *drm_edid);
void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
index 5697645ef7cca..83021470c8b2c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c
@@ -256,7 +256,7 @@ amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector)
EXPORT_IF_KUNIT(amdgpu_dm_mst_connector_early_unregister);
static const struct drm_connector_funcs dm_dp_mst_connector_funcs = {
- .fill_modes = drm_helper_probe_single_connector_modes,
+ .fill_modes = amdgpu_dm_connector_fill_modes,
.destroy = dm_dp_mst_connector_destroy,
.reset = amdgpu_dm_connector_funcs_reset,
.atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index e8cabe25a192d..49e85aec3c665 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2457,6 +2457,383 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode);
}
+/* Tests for amdgpu_dm_connector_update_mode_copies() */
+
+static struct drm_display_mode *
+dm_test_copy_add_mode(struct kunit *test, struct list_head *modes,
+ u16 hdisplay, u16 vdisplay, u16 htotal, u16 vtotal,
+ int clock, u32 type)
+{
+ struct drm_display_mode *mode;
+
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ mode->hdisplay = hdisplay;
+ mode->vdisplay = vdisplay;
+ mode->htotal = htotal;
+ mode->vtotal = vtotal;
+ mode->clock = clock;
+ mode->type = type;
+ list_add_tail(&mode->head, modes);
+
+ return mode;
+}
+
+static struct drm_device *dm_test_alloc_drm(struct kunit *test);
+
+/*
+ * Take the copies as amdgpu_dm_connector_fill_modes() does, with
+ * &drm_mode_config.mutex held. A connector without a device gets one for the
+ * locks, without registering the connector with it
+ */
+static void dm_test_copy_locked(struct kunit *test, struct amdgpu_dm_connector *aconnector)
+{
+ struct drm_mode_config *config;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ config = &aconnector->base.dev->mode_config;
+
+ mutex_lock(&config->mutex);
+ amdgpu_dm_connector_update_mode_copies(aconnector);
+ mutex_unlock(&config->mutex);
+}
+
+/**
+ * dm_test_highest_refresh_probed_skips_copy - Test a probe reads probed_modes
+ * @test: The KUnit test context
+ *
+ * The copy is from the previous probe, possibly of another monitor, so a
+ * caller that passes use_probed_modes must get this probe's mode
+ */
+static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+
+ mode = dm_test_copy_add_mode(test, &aconnector->base.probed_modes, 2560, 1440,
+ 2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
+
+ KUNIT_EXPECT_PTR_EQ(test,
+ amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true),
+ mode);
+}
+
+/**
+ * dm_test_update_mode_copies_highest_refresh - Test the copies taken from base.modes
+ * @test: The KUnit test context
+ *
+ * The preferred mode is copied as is. The highest refresh rate copy comes from
+ * the preferred size only, even when another size has a higher refresh rate
+ */
+static void dm_test_update_mode_copies_highest_refresh(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1280, 720, 1650, 750, 297000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500,
+ DRM_MODE_TYPE_PREFERRED);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 297000, 0);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 148500);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_TRUE(test, aconnector->preferred_mode.type & DRM_MODE_TYPE_PREFERRED);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 297000);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.vdisplay, 1080);
+}
+
+/**
+ * dm_test_update_mode_copies_no_preferred - Test the first mode stands in for a preferred one
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_no_preferred(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500, 0);
+ dm_test_copy_add_mode(test, modes, 1280, 720, 1650, 750, 297000, 0);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 148500);
+}
+
+/**
+ * dm_test_update_mode_copies_records_edid - Test the copies record the sink's EDID
+ * @test: The KUnit test context
+ *
+ * atomic_check compares the EDID of the sink it builds a stream for with the
+ * one the copies were taken with
+ */
+static void dm_test_update_mode_copies_records_edid(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+ u8 *edid;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->dc_sink = sink;
+
+ edid = sink->dc_edid.raw_edid;
+ memset(edid, 0xa5, 256);
+ sink->dc_edid.length = 256;
+
+ dm_test_copy_add_mode(test, &aconnector->base.modes, 1920, 1080, 2200, 1125,
+ 148500, DRM_MODE_TYPE_PREFERRED);
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 256);
+ KUNIT_EXPECT_MEMEQ(test, aconnector->mode_copies_edid, edid, 256);
+}
+
+/**
+ * dm_test_update_mode_copies_no_edid - Test copies without a sink EDID record none
+ * @test: The KUnit test context
+ *
+ * Without a sink, or with one that has no EDID, nothing identifies the
+ * monitor, so the copies must not match any sink later
+ */
+static void dm_test_update_mode_copies_no_edid(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->mode_copies_edid_size = 128;
+ memset(aconnector->mode_copies_edid, 0x5a, 128);
+
+ dm_test_copy_add_mode(test, &aconnector->base.modes, 1920, 1080, 2200, 1125,
+ 148500, DRM_MODE_TYPE_PREFERRED);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 0);
+
+ aconnector->dc_sink = sink;
+ aconnector->mode_copies_edid_size = 128;
+ memset(aconnector->mode_copies_edid, 0x5a, 128);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 0);
+}
+
+/**
+ * dm_test_update_mode_copies_empty - Test an empty mode list clears both copies
+ * @test: The KUnit test context
+ *
+ * A disconnected connector ends its probe with no modes. Copies from an
+ * earlier probe must not survive that
+ */
+static void dm_test_update_mode_copies_empty(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ aconnector->preferred_mode.clock = 148500;
+ aconnector->freesync_vid_base.clock = 297000;
+
+ dm_test_copy_locked(test, aconnector);
+
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 0);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
+}
+
+/**
+ * dm_test_update_mode_copies_freesync_vid_base - Test the FreeSync base beats a command line mode
+ * @test: The KUnit test context
+ *
+ * A video= mode at the preferred size with a higher refresh rate than any EDID
+ * mode is in base.modes, but the FreeSync video modes were built from
+ * probed_freesync_vid_base, so that is the copy atomic_check gets
+ */
+static void dm_test_update_mode_copies_freesync_vid_base(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 148500,
+ DRM_MODE_TYPE_PREFERRED);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 297000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1080, 2200, 1125, 346500,
+ DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_USERDEF);
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 346500);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 1920;
+ aconnector->probed_freesync_vid_base.vdisplay = 1080;
+ aconnector->probed_freesync_vid_base.htotal = 2200;
+ aconnector->probed_freesync_vid_base.vtotal = 1125;
+ aconnector->probed_freesync_vid_base.clock = 297000;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 297000);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 148500);
+}
+
+/**
+ * dm_test_update_mode_copies_freesync_vid_base_pruned - Test a pruned FreeSync base is not copied
+ * @test: The KUnit test context
+ *
+ * add_fs_modes() records probed_freesync_vid_base before validation. If
+ * validation pruned its size, or the base itself, the copy must be the highest
+ * refresh rate mode at the preferred size that base.modes still holds
+ */
+static void dm_test_update_mode_copies_freesync_vid_base_pruned(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct list_head *modes;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ modes = &aconnector->base.modes;
+ INIT_LIST_HEAD(modes);
+
+ dm_test_copy_add_mode(test, modes, 1920, 1200, 2080, 1235, 154000, 0);
+ dm_test_copy_add_mode(test, modes, 1920, 1200, 2080, 1235, 308000, 0);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 3840;
+ aconnector->probed_freesync_vid_base.vdisplay = 2400;
+ aconnector->probed_freesync_vid_base.htotal = 4000;
+ aconnector->probed_freesync_vid_base.vtotal = 2469;
+ aconnector->probed_freesync_vid_base.clock = 592500;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.vdisplay, 1200);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 308000);
+
+ aconnector->probed_freesync_vid_base.hdisplay = 1920;
+ aconnector->probed_freesync_vid_base.vdisplay = 1200;
+ aconnector->probed_freesync_vid_base.htotal = 2080;
+ aconnector->probed_freesync_vid_base.vtotal = 1235;
+ aconnector->probed_freesync_vid_base.clock = 231000;
+
+ dm_test_copy_locked(test, aconnector);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 308000);
+}
+
+/* Tests for amdgpu_dm_connector_fill_modes() */
+
+static int dm_test_fill_modes_get_modes(struct drm_connector *connector)
+{
+ static const int refresh[] = { 60, 120 };
+ struct drm_display_mode *mode;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(refresh); i++) {
+ mode = drm_cvt_mode(connector->dev, 1920, 1080, refresh[i],
+ false, false, false);
+ if (!mode)
+ return i;
+ if (i == 0)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ }
+
+ return i;
+}
+
+static const struct drm_connector_helper_funcs dm_test_fill_modes_helper_funcs = {
+ .get_modes = dm_test_fill_modes_get_modes,
+};
+
+static const struct drm_connector_funcs dm_test_fill_modes_funcs = {
+ .fill_modes = amdgpu_dm_connector_fill_modes,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/**
+ * dm_test_fill_modes_copies_probe - Test fill_modes copies the modes of its probe
+ * @test: The KUnit test context
+ *
+ * The hook returns the probe helper's mode count and takes the copies and the
+ * sink's EDID from the modes that probe left in base.modes
+ */
+static void dm_test_fill_modes_copies_probe(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_device *drm;
+ struct device *dev;
+ struct dc_sink *sink;
+ int count;
+
+ dev = drm_kunit_helper_alloc_device(test);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0,
+ DRIVER_MODESET);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
+
+ aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ connector = &aconnector->base;
+ KUNIT_ASSERT_EQ(test, drmm_connector_init(drm, connector, &dm_test_fill_modes_funcs,
+ DRM_MODE_CONNECTOR_HDMIA, NULL), 0);
+ drm_connector_helper_add(connector, &dm_test_fill_modes_helper_funcs);
+
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->dc_sink = sink;
+
+ mutex_lock(&drm->mode_config.mutex);
+ count = connector->funcs->fill_modes(connector, 4096, 4096);
+ mutex_unlock(&drm->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 2);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(&aconnector->preferred_mode), 60);
+ KUNIT_EXPECT_EQ(test, drm_mode_vrefresh(&aconnector->freesync_vid_base), 120);
+ KUNIT_EXPECT_EQ(test, aconnector->mode_copies_edid_size, 128);
+ KUNIT_EXPECT_MEMEQ(test, aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+}
+
/* Tests for amdgpu_dm_is_freesync_video_mode() */
/**
@@ -4800,6 +5177,127 @@ static void dm_test_create_stream_native_mode_no_edid_edp(struct kunit *test)
DM_TEST_NATIVE_NO_EDID));
}
+/*
+ * Copy the modes after a probe with size limits below the native mode. The
+ * probe left a 1920x1200 mode in base.modes, and with @listed also a 3840x2400
+ * mode at half the native clock, while the encoder keeps the 3840x2400 native
+ * mode
+ */
+static struct amdgpu_dm_connector *
+dm_test_copy_pruned(struct kunit *test, int connector_type, bool listed)
+{
+ static const u8 edid[EDID_LENGTH];
+ struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
+ struct amdgpu_dm_connector *aconnector = ctx->aconnector;
+ struct list_head *modes = &aconnector->base.modes;
+ struct drm_display_mode *native, *mode;
+ struct amdgpu_encoder *aenc;
+
+ aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aenc);
+ KUNIT_ASSERT_EQ(test, drmm_encoder_init(ctx->drm, &aenc->base, NULL,
+ DRM_MODE_ENCODER_TMDS, NULL), 0);
+ KUNIT_ASSERT_EQ(test, drm_connector_attach_encoder(&aconnector->base, &aenc->base), 0);
+
+ native = &aenc->native_mode;
+ native->clock = 592500;
+ native->hdisplay = 3840;
+ native->hsync_start = 3888;
+ native->hsync_end = 3920;
+ native->htotal = 4000;
+ native->vdisplay = 2400;
+ native->vsync_start = 2403;
+ native->vsync_end = 2409;
+ native->vtotal = 2469;
+
+ mode = drm_mode_duplicate(ctx->drm, native);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ mode->hdisplay = 1920;
+ mode->vdisplay = 1200;
+ list_add_tail(&mode->head, modes);
+
+ if (listed) {
+ mode = drm_mode_duplicate(ctx->drm, native);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ mode->clock /= 2;
+ list_add_tail(&mode->head, modes);
+ }
+
+ aconnector->drm_edid = drm_edid_alloc(edid, sizeof(edid));
+ KUNIT_ASSERT_NOT_NULL(test, aconnector->drm_edid);
+
+ dm_test_copy_locked(test, aconnector);
+
+ drm_edid_free(aconnector->drm_edid);
+ aconnector->drm_edid = NULL;
+
+ return aconnector;
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_edp - Test eDP copies the encoder's native mode
+ * @test: The KUnit test context
+ *
+ * With the native size pruned from base.modes, the preferred mode copy must be
+ * the panel's native mode, with its CRTC timing set, so that atomic_check
+ * still scales to it
+ */
+static void dm_test_update_mode_copies_pruned_edp(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_eDP, false);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.vdisplay, 2400);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 592500);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.crtc_clock, 592500);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.crtc_htotal, 4000);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->freesync_vid_base.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 592500);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_lvds - Test LVDS copies the encoder's native mode
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_pruned_lvds(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_LVDS, false);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 592500);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_listed_edp - Test eDP copies a listed native size mode
+ * @test: The KUnit test context
+ *
+ * mode_valid may have rejected the encoder's native timing, so a mode of the
+ * native size left in base.modes must be copied in its place
+ */
+static void dm_test_update_mode_copies_pruned_listed_edp(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_eDP, true);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 3840);
+ KUNIT_EXPECT_EQ(test, aconnector->preferred_mode.clock, 296250);
+}
+
+/**
+ * dm_test_update_mode_copies_pruned_hdmi - Test HDMI copies the first mode
+ * @test: The KUnit test context
+ */
+static void dm_test_update_mode_copies_pruned_hdmi(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector =
+ dm_test_copy_pruned(test, DRM_MODE_CONNECTOR_HDMIA, true);
+
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.hdisplay, 1920);
+ KUNIT_EXPECT_EQ(test, (int)aconnector->preferred_mode.vdisplay, 1200);
+}
+
/* Tests for amdgpu_dm_connector_detect() */
/*
@@ -6332,6 +6830,8 @@ static void dm_test_add_fs_modes_generates(struct kunit *test)
KUNIT_EXPECT_EQ(test, count, 8);
KUNIT_EXPECT_EQ(test, duplicate_count, 0);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 148500);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.vtotal, 1125);
}
/**
@@ -6350,6 +6850,7 @@ static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
aconnector->max_vfreq = 120;
KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 0);
}
/**
@@ -10576,6 +11077,17 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_highest_refresh_writeback_null),
KUNIT_CASE(dm_test_highest_refresh_cached_base),
KUNIT_CASE(dm_test_highest_refresh_preferred_mode),
+ KUNIT_CASE(dm_test_highest_refresh_probed_skips_copy),
+ /* amdgpu_dm_connector_update_mode_copies */
+ KUNIT_CASE(dm_test_update_mode_copies_highest_refresh),
+ KUNIT_CASE(dm_test_update_mode_copies_no_preferred),
+ KUNIT_CASE(dm_test_update_mode_copies_records_edid),
+ KUNIT_CASE(dm_test_update_mode_copies_no_edid),
+ KUNIT_CASE(dm_test_update_mode_copies_empty),
+ KUNIT_CASE(dm_test_update_mode_copies_freesync_vid_base),
+ KUNIT_CASE(dm_test_update_mode_copies_freesync_vid_base_pruned),
+ /* amdgpu_dm_connector_fill_modes */
+ KUNIT_CASE(dm_test_fill_modes_copies_probe),
/* amdgpu_dm_is_freesync_video_mode */
KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode),
KUNIT_CASE(dm_test_is_freesync_video_mode_match),
@@ -10664,6 +11176,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_stream_native_preferred_edp),
KUNIT_CASE(dm_test_create_stream_native_mode_no_clock_edp),
KUNIT_CASE(dm_test_create_stream_native_mode_no_edid_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_lvds),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_listed_edp),
+ KUNIT_CASE(dm_test_update_mode_copies_pruned_hdmi),
/* amdgpu_dm_connector_detect */
KUNIT_CASE(dm_test_detect_force_on),
KUNIT_CASE(dm_test_detect_force_on_digital),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 5/8] drm/amd/display: Pass the sink to the FreeSync and stream timing helpers
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
An upcoming patch has atomic_check use the mode copies only for the sink
they were taken from. atomic_check doesn't hold mode_config.mutex, and a
hotplug can swap dc_sink at any time, so the check has to use the sink of
the stream it builds rather than read dc_sink again
Have amdgpu_dm_is_freesync_video_mode(),
amdgpu_dm_get_highest_refresh_rate_mode() and
fill_stream_properties_from_drm_display_mode() take that sink as an
argument. create_stream_for_sink() passes the sink it builds the stream
for, the probe paths pass the connector's sink, and
dm_update_crtc_state() passes dc_sink for now
Nothing reads the argument yet. No functional change
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 ++-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 24 ++++++-----
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 7 +++-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 46 +++++++++++-----------
4 files changed, 45 insertions(+), 37 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 33c73ca9cff14..d67384eba1553 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -5666,10 +5666,11 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
goto skip_modeset;
} else if (amdgpu_freesync_vid_mode && aconnector &&
amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode,
- aconnector)) {
+ aconnector, aconnector->dc_sink)) {
struct drm_display_mode *high_mode;
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
+ aconnector->dc_sink);
if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 491965266b786..bddab6a462563 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -879,6 +879,7 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
const struct drm_display_mode *mode_in,
const struct drm_connector *connector,
+ const struct dc_sink *sink,
const struct drm_connector_state *connector_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
@@ -949,7 +950,7 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
timing_out->hdmi_vic = hv_frame.vic;
}
- if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector)) {
+ if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector, sink)) {
timing_out->h_addressable = mode_in->hdisplay;
timing_out->h_total = mode_in->htotal;
timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start;
@@ -1208,7 +1209,7 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
*/
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes)
+ bool use_probed_modes, const struct dc_sink *sink)
{
struct drm_display_mode *m_pref;
struct list_head *list_head = use_probed_modes ?
@@ -1241,12 +1242,13 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
- struct amdgpu_dm_connector *aconnector)
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
{
struct drm_display_mode *high_mode;
int timing_diff;
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, sink);
if (!high_mode || !mode)
return false;
@@ -1724,9 +1726,10 @@ create_stream_for_sink(struct drm_connector *connector,
if (aconnector && preferred_mode) {
recalculate_timing = amdgpu_freesync_vid_mode &&
- amdgpu_dm_is_freesync_video_mode(&mode, aconnector);
+ amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink);
if (recalculate_timing) {
- freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector,
+ false, sink);
drm_mode_copy(&saved_mode, &mode);
saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
drm_mode_copy(&mode, freesync_mode);
@@ -1748,11 +1751,11 @@ create_stream_for_sink(struct drm_connector *connector,
*/
if (!scale || mode_refresh != preferred_refresh)
fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, NULL,
+ stream, &mode, connector, sink, con_state, NULL,
requested_bpc, requested_encoding, is_hdmi_ep);
else
fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, old_stream,
+ stream, &mode, connector, sink, con_state, old_stream,
requested_bpc, requested_encoding, is_hdmi_ep);
/* The rest isn't needed for writeback connectors */
@@ -3282,7 +3285,7 @@ STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
* with lower resolution than the highest refresh rate supported.
*/
- m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true);
+ m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, aconnector->dc_sink);
if (!m)
return 0;
@@ -4152,7 +4155,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
*/
if (!amdgpu_dm_connector->max_vfreq) {
struct drm_display_mode *brr_mode =
- amdgpu_dm_get_highest_refresh_rate_mode(amdgpu_dm_connector, true);
+ amdgpu_dm_get_highest_refresh_rate_mode(amdgpu_dm_connector,
+ true, sink);
if (brr_mode)
amdgpu_dm_connector->max_vfreq =
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index ee8d7eabd7c13..7f475403ad220 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -34,6 +34,7 @@ struct amdgpu_encoder;
struct amdgpu_i2c_adapter;
struct dc_crtc_timing;
struct dc_link;
+struct dc_sink;
enum signal_type;
struct dc_state;
struct dc_stream_state;
@@ -124,7 +125,8 @@ void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
struct dc_stream_state *stream);
bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
- struct amdgpu_dm_connector *aconnector);
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink);
int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
struct dc_info_packet *out);
@@ -135,7 +137,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes);
+ bool use_probed_modes, const struct dc_sink *sink);
struct amdgpu_i2c_adapter *
amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem);
@@ -191,6 +193,7 @@ void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
const struct drm_display_mode *mode_in,
const struct drm_connector *connector,
+ const struct dc_sink *sink,
const struct drm_connector_state *connector_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 49e85aec3c665..832fce79f8acd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2407,7 +2407,7 @@ static void dm_test_highest_refresh_writeback_null(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, aconnector);
aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
- KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false));
+ KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL));
}
/**
@@ -2424,7 +2424,7 @@ static void dm_test_highest_refresh_cached_base(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
&aconnector->freesync_vid_base);
}
@@ -2453,7 +2453,7 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode->vtotal = 1125;
list_add_tail(&mode->head, &aconnector->base.modes);
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
mode);
}
@@ -2525,7 +2525,7 @@ static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
KUNIT_EXPECT_PTR_EQ(test,
- amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true),
+ amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, NULL),
mode);
}
@@ -2850,7 +2850,7 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector));
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector, NULL));
}
/**
@@ -2887,7 +2887,7 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
candidate.vsync_end = 1089;
candidate.vtotal = 1125;
- KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
}
/**
@@ -2916,7 +2916,7 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
candidate.htotal = 1650;
candidate.vtotal = 750;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
}
/* Tests for amdgpu_dm_update_cacp_caps() */
@@ -4183,7 +4183,7 @@ static void dm_test_fill_stream_borders_zeroed(struct kunit *test)
timing->v_border_bottom = 8;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0);
@@ -4205,7 +4205,7 @@ static void dm_test_fill_stream_rgb_defaults(struct kunit *test)
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB);
@@ -4232,7 +4232,7 @@ static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
@@ -4252,7 +4252,7 @@ static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test)
ctx->mode->flags = 0;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0);
@@ -4282,7 +4282,7 @@ static void dm_test_fill_stream_inherits_old_stream(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, old_stream, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
@@ -4313,7 +4313,7 @@ static void dm_test_fill_stream_timing_from_crtc(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
@@ -4341,7 +4341,7 @@ static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test)
ctx->aconnector->base.display_info.bpc = 12;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4359,7 +4359,7 @@ static void dm_test_fill_stream_content_type(struct kunit *test)
ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type,
@@ -4378,7 +4378,7 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test)
ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio,
@@ -4399,7 +4399,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr420(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR420, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4418,7 +4418,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr422(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR422, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4437,7 +4437,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr444(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR444, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4462,7 +4462,7 @@ static void dm_test_fill_stream_hdmi_ep_clamps_depth(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, true);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4486,7 +4486,7 @@ static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4520,7 +4520,7 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
ctx->stream->signal = SIGNAL_TYPE_HDMI_TYPE_A;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, old_stream, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
PIXEL_ENCODING_RGB, false);
/* The HDMI block overwrote the old stream's VIC of 99 with the AVI code. */
@@ -4561,7 +4561,7 @@ static void dm_test_fill_stream_freesync_video(struct kunit *test)
ctx->mode->vtotal = 1125;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 5/8] drm/amd/display: Pass the sink to the FreeSync and stream timing helpers
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
An upcoming patch has atomic_check use the mode copies only for the sink
they were taken from. atomic_check doesn't hold mode_config.mutex, and a
hotplug can swap dc_sink at any time, so the check has to use the sink of
the stream it builds rather than read dc_sink again
Have amdgpu_dm_is_freesync_video_mode(),
amdgpu_dm_get_highest_refresh_rate_mode() and
fill_stream_properties_from_drm_display_mode() take that sink as an
argument. create_stream_for_sink() passes the sink it builds the stream
for, the probe paths pass the connector's sink, and
dm_update_crtc_state() passes dc_sink for now
Nothing reads the argument yet. No functional change
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 ++-
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 24 ++++++-----
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 7 +++-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 46 +++++++++++-----------
4 files changed, 45 insertions(+), 37 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 33c73ca9cff14..d67384eba1553 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -5666,10 +5666,11 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
goto skip_modeset;
} else if (amdgpu_freesync_vid_mode && aconnector &&
amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode,
- aconnector)) {
+ aconnector, aconnector->dc_sink)) {
struct drm_display_mode *high_mode;
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
+ aconnector->dc_sink);
if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 491965266b786..bddab6a462563 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -879,6 +879,7 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
const struct drm_display_mode *mode_in,
const struct drm_connector *connector,
+ const struct dc_sink *sink,
const struct drm_connector_state *connector_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
@@ -949,7 +950,7 @@ STATIC_IFN_KUNIT void fill_stream_properties_from_drm_display_mode(
timing_out->hdmi_vic = hv_frame.vic;
}
- if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector)) {
+ if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector, sink)) {
timing_out->h_addressable = mode_in->hdisplay;
timing_out->h_total = mode_in->htotal;
timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start;
@@ -1208,7 +1209,7 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
*/
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes)
+ bool use_probed_modes, const struct dc_sink *sink)
{
struct drm_display_mode *m_pref;
struct list_head *list_head = use_probed_modes ?
@@ -1241,12 +1242,13 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
- struct amdgpu_dm_connector *aconnector)
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
{
struct drm_display_mode *high_mode;
int timing_diff;
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, sink);
if (!high_mode || !mode)
return false;
@@ -1724,9 +1726,10 @@ create_stream_for_sink(struct drm_connector *connector,
if (aconnector && preferred_mode) {
recalculate_timing = amdgpu_freesync_vid_mode &&
- amdgpu_dm_is_freesync_video_mode(&mode, aconnector);
+ amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink);
if (recalculate_timing) {
- freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false);
+ freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector,
+ false, sink);
drm_mode_copy(&saved_mode, &mode);
saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
drm_mode_copy(&mode, freesync_mode);
@@ -1748,11 +1751,11 @@ create_stream_for_sink(struct drm_connector *connector,
*/
if (!scale || mode_refresh != preferred_refresh)
fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, NULL,
+ stream, &mode, connector, sink, con_state, NULL,
requested_bpc, requested_encoding, is_hdmi_ep);
else
fill_stream_properties_from_drm_display_mode(
- stream, &mode, connector, con_state, old_stream,
+ stream, &mode, connector, sink, con_state, old_stream,
requested_bpc, requested_encoding, is_hdmi_ep);
/* The rest isn't needed for writeback connectors */
@@ -3282,7 +3285,7 @@ STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector)
* with lower resolution than the highest refresh rate supported.
*/
- m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true);
+ m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, aconnector->dc_sink);
if (!m)
return 0;
@@ -4152,7 +4155,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
*/
if (!amdgpu_dm_connector->max_vfreq) {
struct drm_display_mode *brr_mode =
- amdgpu_dm_get_highest_refresh_rate_mode(amdgpu_dm_connector, true);
+ amdgpu_dm_get_highest_refresh_rate_mode(amdgpu_dm_connector,
+ true, sink);
if (brr_mode)
amdgpu_dm_connector->max_vfreq =
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index ee8d7eabd7c13..7f475403ad220 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -34,6 +34,7 @@ struct amdgpu_encoder;
struct amdgpu_i2c_adapter;
struct dc_crtc_timing;
struct dc_link;
+struct dc_sink;
enum signal_type;
struct dc_state;
struct dc_stream_state;
@@ -124,7 +125,8 @@ void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev,
struct dc_stream_state *stream);
bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
- struct amdgpu_dm_connector *aconnector);
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink);
int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state,
struct dc_info_packet *out);
@@ -135,7 +137,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes);
+ bool use_probed_modes, const struct dc_sink *sink);
struct amdgpu_i2c_adapter *
amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem);
@@ -191,6 +193,7 @@ void fill_stream_properties_from_drm_display_mode(
struct dc_stream_state *stream,
const struct drm_display_mode *mode_in,
const struct drm_connector *connector,
+ const struct dc_sink *sink,
const struct drm_connector_state *connector_state,
const struct dc_stream_state *old_stream,
int requested_bpc,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 49e85aec3c665..832fce79f8acd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2407,7 +2407,7 @@ static void dm_test_highest_refresh_writeback_null(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, aconnector);
aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK;
- KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false));
+ KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL));
}
/**
@@ -2424,7 +2424,7 @@ static void dm_test_highest_refresh_cached_base(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
&aconnector->freesync_vid_base);
}
@@ -2453,7 +2453,7 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode->vtotal = 1125;
list_add_tail(&mode->head, &aconnector->base.modes);
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false),
+ KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
mode);
}
@@ -2525,7 +2525,7 @@ static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
KUNIT_EXPECT_PTR_EQ(test,
- amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true),
+ amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, NULL),
mode);
}
@@ -2850,7 +2850,7 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector));
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector, NULL));
}
/**
@@ -2887,7 +2887,7 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
candidate.vsync_end = 1089;
candidate.vtotal = 1125;
- KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+ KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
}
/**
@@ -2916,7 +2916,7 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
candidate.htotal = 1650;
candidate.vtotal = 750;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector));
+ KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
}
/* Tests for amdgpu_dm_update_cacp_caps() */
@@ -4183,7 +4183,7 @@ static void dm_test_fill_stream_borders_zeroed(struct kunit *test)
timing->v_border_bottom = 8;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0);
@@ -4205,7 +4205,7 @@ static void dm_test_fill_stream_rgb_defaults(struct kunit *test)
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB);
@@ -4232,7 +4232,7 @@ static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
@@ -4252,7 +4252,7 @@ static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test)
ctx->mode->flags = 0;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0);
@@ -4282,7 +4282,7 @@ static void dm_test_fill_stream_inherits_old_stream(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, old_stream, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
@@ -4313,7 +4313,7 @@ static void dm_test_fill_stream_timing_from_crtc(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
@@ -4341,7 +4341,7 @@ static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test)
ctx->aconnector->base.display_info.bpc = 12;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4359,7 +4359,7 @@ static void dm_test_fill_stream_content_type(struct kunit *test)
ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type,
@@ -4378,7 +4378,7 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test)
ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio,
@@ -4399,7 +4399,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr420(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR420, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4418,7 +4418,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr422(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR422, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4437,7 +4437,7 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr444(struct kunit *test
struct dc_crtc_timing *timing = &ctx->stream->timing;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_YCBCR444, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
@@ -4462,7 +4462,7 @@ static void dm_test_fill_stream_hdmi_ep_clamps_depth(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, true);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4486,7 +4486,7 @@ static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test)
ctx->mode->crtc_clock = 148500;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 10,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
@@ -4520,7 +4520,7 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
ctx->stream->signal = SIGNAL_TYPE_HDMI_TYPE_A;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, old_stream, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
PIXEL_ENCODING_RGB, false);
/* The HDMI block overwrote the old stream's VIC of 99 with the AVI code. */
@@ -4561,7 +4561,7 @@ static void dm_test_fill_stream_freesync_video(struct kunit *test)
ctx->mode->vtotal = 1125;
fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, ctx->conn_state, NULL, 8,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 6/8] drm/amd/display: Pick the FreeSync video config for the new stream's sink
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
dm_update_crtc_state() picks the fixed refresh config for a FreeSync
video mode in its first pass, while it removes the old stream, and
create_stream_for_sink() picks that mode's timing in the second pass,
for the new stream's sink. The two answers agree only because neither
lookup depends on the sink yet. An upcoming patch makes them depend on
it, and atomic_check doesn't hold mode_config.mutex, so a hotplug can
swap dc_sink between the two passes
Move the choice to dm_set_freesync_video_config(), called whenever a
stream is added, and ask about new_stream->sink. A CRTC that the commit
turns off no longer gets the fixed config
A DPMS off commit now only clears the fixed config, and the DPMS on
commit, which adds the stream again, sets it. Before, the DPMS on
commit inherited it through the duplicated CRTC state. A CRTC turned on
straight into a FreeSync video mode now gets the fixed config too, which
the old code never set
This only applies with the amdgpu.freesync_video=1 module parameter,
which is off by default
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Before this patch, a CRTC turned on straight into a FreeSync video mode
never got the fixed refresh config, since the first pass sets it only
in a commit with an old stream. That looks like a bug from commit
6f59f229f8ed ("drm/amd/display: Skip modeset for front porch change"),
but I haven't confirmed it on hardware. Was leaving it unset intended?
If not, I can split the fix out into its own patch with a Fixes tag
dm_set_freesync_video_config() reads new_stream->sink, which
dc_stream_state marks deprecated. The stream holds the only reference
to the sink it was built for, and keeping a second one in dm_crtc_state
would need its own retain and release on duplicate and destroy. I can
do that, or have amdgpu_dm_create_validate_stream_for_sink() return the
sink, if you'd rather not read the field
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 39 +++++++++++----
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 ++
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 58 ++++++++++++++++++++++
3 files changed, 91 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index d67384eba1553..706a63b93ef6b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -5516,6 +5516,33 @@ STATIC_IFN_KUNIT int do_aquire_global_lock(struct drm_device *dev,
}
EXPORT_IF_KUNIT(do_aquire_global_lock);
+/*
+ * Use the fixed refresh config for a FreeSync video mode other than its base.
+ * create_stream_for_sink() picked @new_stream's timing for new_stream->sink,
+ * so decide for that sink too, even if a hotplug swapped dc_sink after the
+ * old stream was built. dc_stream_state marks sink deprecated, but
+ * dm_update_crtc_state() keeps no other record of the sink the stream was
+ * built for
+ */
+STATIC_IFN_KUNIT void
+dm_set_freesync_video_config(struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *dm_new_crtc_state,
+ const struct dc_stream_state *new_stream)
+{
+ const struct drm_display_mode *mode = &dm_new_crtc_state->base.mode;
+ struct drm_display_mode *high_mode;
+
+ if (!amdgpu_freesync_vid_mode ||
+ !amdgpu_dm_is_freesync_video_mode(mode, aconnector, new_stream->sink))
+ return;
+
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
+ new_stream->sink);
+ if (high_mode && !drm_mode_equal(mode, high_mode))
+ amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
+}
+EXPORT_IF_KUNIT(dm_set_freesync_video_config);
+
STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_commit *state,
struct drm_crtc *crtc,
@@ -5664,15 +5691,6 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
goto skip_modeset;
- } else if (amdgpu_freesync_vid_mode && aconnector &&
- amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode,
- aconnector, aconnector->dc_sink)) {
- struct drm_display_mode *high_mode;
-
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
- aconnector->dc_sink);
- if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
- amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
}
ret = dm_atomic_get_state(state, &dm_state);
@@ -5735,6 +5753,9 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
}
*needs_dc_state_realloc = true;
+
+ dm_set_freesync_video_config(aconnector, dm_new_crtc_state,
+ new_stream);
}
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index 97a74178513a9..926f1794d24e5 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1286,6 +1286,9 @@ void amdgpu_dm_update_hdcp(struct drm_atomic_commit *state);
int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state);
int do_aquire_global_lock(struct drm_device *dev,
struct drm_atomic_commit *state);
+void dm_set_freesync_video_config(struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *dm_new_crtc_state,
+ const struct dc_stream_state *new_stream);
int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_commit *state,
struct drm_crtc *crtc,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index b9272b28f53c7..fab6df40ed989 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3213,6 +3213,63 @@ static void dm_test_aquire_global_lock_waits_commit(struct kunit *test)
KUNIT_EXPECT_EQ(test, ret, 0);
}
+/**
+ * dm_test_freesync_video_config_no_old_stream - Test the fixed config needs no old stream
+ * @test: The KUnit test context
+ *
+ * A DPMS off commit clears the fixed refresh config while it removes the old
+ * stream, so the DPMS on commit has no old stream and starts from an inactive
+ * config. A 50 Hz FreeSync video mode of a 1080p60 base must get the fixed
+ * config back from the new stream alone, and the base itself must not get it
+ */
+static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
+{
+ uint saved_vid_mode = amdgpu_freesync_vid_mode;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *base, *mode;
+ struct dm_crtc_state *crtc_state;
+ struct dc_stream_state *stream;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ base = &aconnector->freesync_vid_base;
+ base->clock = 148500;
+ base->hdisplay = 1920;
+ base->hsync_start = 2008;
+ base->hsync_end = 2052;
+ base->htotal = 2200;
+ base->vdisplay = 1080;
+ base->vsync_start = 1084;
+ base->vsync_end = 1089;
+ base->vtotal = 1125;
+
+ crtc_state->freesync_config.state = VRR_STATE_INACTIVE;
+ mode = &crtc_state->base.mode;
+ drm_mode_copy(mode, base);
+
+ amdgpu_freesync_vid_mode = 1;
+
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_INACTIVE);
+
+ mode->vsync_start += 225;
+ mode->vsync_end += 225;
+ mode->vtotal += 225;
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+ KUNIT_EXPECT_EQ(test, crtc_state->freesync_config.fixed_refresh_in_uhz, 50000000U);
+
+ amdgpu_freesync_vid_mode = saved_vid_mode;
+}
+
/**
* dm_test_update_crtc_state_unchanged - Test unchanged state needs no validation
* @test: The KUnit test context
@@ -5359,6 +5416,7 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_aquire_global_lock_no_crtc),
KUNIT_CASE(dm_test_aquire_global_lock_no_commit),
KUNIT_CASE(dm_test_aquire_global_lock_waits_commit),
+ KUNIT_CASE(dm_test_freesync_video_config_no_old_stream),
KUNIT_CASE(dm_test_update_crtc_state_unchanged),
KUNIT_CASE(dm_test_update_plane_state_detached),
KUNIT_CASE(dm_test_mod_power_update_streams_empty),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 6/8] drm/amd/display: Pick the FreeSync video config for the new stream's sink
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
dm_update_crtc_state() picks the fixed refresh config for a FreeSync
video mode in its first pass, while it removes the old stream, and
create_stream_for_sink() picks that mode's timing in the second pass,
for the new stream's sink. The two answers agree only because neither
lookup depends on the sink yet. An upcoming patch makes them depend on
it, and atomic_check doesn't hold mode_config.mutex, so a hotplug can
swap dc_sink between the two passes
Move the choice to dm_set_freesync_video_config(), called whenever a
stream is added, and ask about new_stream->sink. A CRTC that the commit
turns off no longer gets the fixed config
A DPMS off commit now only clears the fixed config, and the DPMS on
commit, which adds the stream again, sets it. Before, the DPMS on
commit inherited it through the duplicated CRTC state. A CRTC turned on
straight into a FreeSync video mode now gets the fixed config too, which
the old code never set
This only applies with the amdgpu.freesync_video=1 module parameter,
which is off by default
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Before this patch, a CRTC turned on straight into a FreeSync video mode
never got the fixed refresh config, since the first pass sets it only
in a commit with an old stream. That looks like a bug from commit
6f59f229f8ed ("drm/amd/display: Skip modeset for front porch change"),
but I haven't confirmed it on hardware. Was leaving it unset intended?
If not, I can split the fix out into its own patch with a Fixes tag
dm_set_freesync_video_config() reads new_stream->sink, which
dc_stream_state marks deprecated. The stream holds the only reference
to the sink it was built for, and keeping a second one in dm_crtc_state
would need its own retain and release on duplicate and destroy. I can
do that, or have amdgpu_dm_create_validate_stream_for_sink() return the
sink, if you'd rather not read the field
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 39 +++++++++++----
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 ++
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 58 ++++++++++++++++++++++
3 files changed, 91 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index d67384eba1553..706a63b93ef6b 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -5516,6 +5516,33 @@ STATIC_IFN_KUNIT int do_aquire_global_lock(struct drm_device *dev,
}
EXPORT_IF_KUNIT(do_aquire_global_lock);
+/*
+ * Use the fixed refresh config for a FreeSync video mode other than its base.
+ * create_stream_for_sink() picked @new_stream's timing for new_stream->sink,
+ * so decide for that sink too, even if a hotplug swapped dc_sink after the
+ * old stream was built. dc_stream_state marks sink deprecated, but
+ * dm_update_crtc_state() keeps no other record of the sink the stream was
+ * built for
+ */
+STATIC_IFN_KUNIT void
+dm_set_freesync_video_config(struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *dm_new_crtc_state,
+ const struct dc_stream_state *new_stream)
+{
+ const struct drm_display_mode *mode = &dm_new_crtc_state->base.mode;
+ struct drm_display_mode *high_mode;
+
+ if (!amdgpu_freesync_vid_mode ||
+ !amdgpu_dm_is_freesync_video_mode(mode, aconnector, new_stream->sink))
+ return;
+
+ high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
+ new_stream->sink);
+ if (high_mode && !drm_mode_equal(mode, high_mode))
+ amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
+}
+EXPORT_IF_KUNIT(dm_set_freesync_video_config);
+
STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_commit *state,
struct drm_crtc *crtc,
@@ -5664,15 +5691,6 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
goto skip_modeset;
- } else if (amdgpu_freesync_vid_mode && aconnector &&
- amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode,
- aconnector, aconnector->dc_sink)) {
- struct drm_display_mode *high_mode;
-
- high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false,
- aconnector->dc_sink);
- if (!drm_mode_equal(&new_crtc_state->mode, high_mode))
- amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state);
}
ret = dm_atomic_get_state(state, &dm_state);
@@ -5735,6 +5753,9 @@ STATIC_IFN_KUNIT int dm_update_crtc_state(struct amdgpu_display_manager *dm,
}
*needs_dc_state_realloc = true;
+
+ dm_set_freesync_video_config(aconnector, dm_new_crtc_state,
+ new_stream);
}
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index 97a74178513a9..926f1794d24e5 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1286,6 +1286,9 @@ void amdgpu_dm_update_hdcp(struct drm_atomic_commit *state);
int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state);
int do_aquire_global_lock(struct drm_device *dev,
struct drm_atomic_commit *state);
+void dm_set_freesync_video_config(struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *dm_new_crtc_state,
+ const struct dc_stream_state *new_stream);
int dm_update_crtc_state(struct amdgpu_display_manager *dm,
struct drm_atomic_commit *state,
struct drm_crtc *crtc,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index b9272b28f53c7..fab6df40ed989 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3213,6 +3213,63 @@ static void dm_test_aquire_global_lock_waits_commit(struct kunit *test)
KUNIT_EXPECT_EQ(test, ret, 0);
}
+/**
+ * dm_test_freesync_video_config_no_old_stream - Test the fixed config needs no old stream
+ * @test: The KUnit test context
+ *
+ * A DPMS off commit clears the fixed refresh config while it removes the old
+ * stream, so the DPMS on commit has no old stream and starts from an inactive
+ * config. A 50 Hz FreeSync video mode of a 1080p60 base must get the fixed
+ * config back from the new stream alone, and the base itself must not get it
+ */
+static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
+{
+ uint saved_vid_mode = amdgpu_freesync_vid_mode;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *base, *mode;
+ struct dm_crtc_state *crtc_state;
+ struct dc_stream_state *stream;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ base = &aconnector->freesync_vid_base;
+ base->clock = 148500;
+ base->hdisplay = 1920;
+ base->hsync_start = 2008;
+ base->hsync_end = 2052;
+ base->htotal = 2200;
+ base->vdisplay = 1080;
+ base->vsync_start = 1084;
+ base->vsync_end = 1089;
+ base->vtotal = 1125;
+
+ crtc_state->freesync_config.state = VRR_STATE_INACTIVE;
+ mode = &crtc_state->base.mode;
+ drm_mode_copy(mode, base);
+
+ amdgpu_freesync_vid_mode = 1;
+
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_INACTIVE);
+
+ mode->vsync_start += 225;
+ mode->vsync_end += 225;
+ mode->vtotal += 225;
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+ KUNIT_EXPECT_EQ(test, crtc_state->freesync_config.fixed_refresh_in_uhz, 50000000U);
+
+ amdgpu_freesync_vid_mode = saved_vid_mode;
+}
+
/**
* dm_test_update_crtc_state_unchanged - Test unchanged state needs no validation
* @test: The KUnit test context
@@ -5359,6 +5416,7 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_aquire_global_lock_no_crtc),
KUNIT_CASE(dm_test_aquire_global_lock_no_commit),
KUNIT_CASE(dm_test_aquire_global_lock_waits_commit),
+ KUNIT_CASE(dm_test_freesync_video_config_no_old_stream),
KUNIT_CASE(dm_test_update_crtc_state_unchanged),
KUNIT_CASE(dm_test_update_plane_state_detached),
KUNIT_CASE(dm_test_mod_power_update_streams_empty),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 7/8] drm/amd/display: Stop reading connector->modes from atomic_check
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
Have amdgpu_dm_create_validate_stream_for_sink() and
amdgpu_dm_get_highest_refresh_rate_mode() read the copies that
amdgpu_dm_connector_update_mode_copies() writes under connection_mutex,
instead of walking connector->modes without mode_config.mutex. Their
atomic_check callers hold connection_mutex, and both lookups now assert
the lock they need. A missing copy counts as an empty list
Use the copies only for a sink with the EDID recorded with them. After a
monitor swap the HPD handlers run an atomic commit before the next
probe, and the old copies would have scaling drive the new monitor at
the old native timing. Without copies, create_stream_for_sink() uses the
requested mode's own timing, as before the first probe
A sink without EDID never matches. create_stream_for_sink() used to
scale such a sink to the first entry of connector->modes, the largest of
the drm_add_modes_noedid() defaults, and now drives the requested mode's
own timing
A hotplug can swap dc_sink while atomic_check runs, so the copies are
checked against the sink of the stream being built.
create_stream_for_sink() passes its own, and
dm_set_freesync_video_config() passes new_stream->sink
The probe and hotplug paths hold mode_config.mutex and pass in_probe,
renamed from use_probed_modes since the other case no longer walks any
list. For them amdgpu_dm_get_highest_refresh_rate_mode() walks
probed_modes, falling back to the copy when it is empty. On a first
probe, with no copy yet, mode_valid checks FreeSync video modes with
their own vtotal, which is fine since their pixel clock and htotal match
the base mode
Link: https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 131 ++++--
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 5 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 523 ++++++++++++++-------
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 30 +-
4 files changed, 474 insertions(+), 215 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index bddab6a462563..c1b814c9fa865 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1177,6 +1177,22 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
return false;
}
+/*
+ * Whether the mode copies were taken from a probe of @sink, or of a sink with
+ * the same EDID, as after a replug of the same monitor. A sink without EDID
+ * never matches, since nothing tells two of them apart. A hotplug can swap
+ * dc_sink while atomic_check runs, so atomic_check passes the sink of the
+ * stream it builds or checks rather than reading dc_sink again
+ */
+static bool amdgpu_dm_mode_copies_match(const struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
+{
+ u32 size = amdgpu_dm_sink_edid_size(sink);
+
+ return size && size == aconnector->mode_copies_edid_size &&
+ !memcmp(sink->dc_edid.raw_edid, aconnector->mode_copies_edid, size);
+}
+
/**
* DOC: FreeSync Video
*
@@ -1209,35 +1225,49 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
*/
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes, const struct dc_sink *sink)
+ bool in_probe, const struct dc_sink *sink)
{
+ struct drm_mode_config *config = &aconnector->base.dev->mode_config;
+ struct list_head *probed = &aconnector->base.probed_modes;
struct drm_display_mode *m_pref;
- struct list_head *list_head = use_probed_modes ?
- &aconnector->base.probed_modes :
- &aconnector->base.modes;
if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
/*
- * A probe reads its own probed_modes, not the copy of the last one. The
- * copy stands in while probed_modes is empty
+ * With @in_probe this walks probed_modes, which needs
+ * &drm_mode_config.mutex. Other callers read only the copies, which are
+ * written under both locks, so either is enough. atomic_check holds
+ * connection_mutex and mode_valid holds &drm_mode_config.mutex
*/
- if (aconnector->freesync_vid_base.clock != 0 &&
- !(use_probed_modes && !list_empty(list_head)))
- return &aconnector->freesync_vid_base;
+ if (in_probe)
+ lockdep_assert_held(&config->mutex);
+ else
+ lockdep_assert(lockdep_is_held(&config->mutex) ||
+ lockdep_is_held(&config->connection_mutex.mutex.base));
- m_pref = amdgpu_dm_find_preferred_mode(list_head);
- if (!m_pref) {
+ /*
+ * During a probe, probed_modes holds this probe's modes, which the
+ * previous probe's copy may not match
+ */
+ if (in_probe && !list_empty(probed)) {
+ m_pref = amdgpu_dm_find_preferred_mode(probed);
/* Probably an EDID with no preferred mode. Fallback to first entry */
- m_pref = list_first_entry_or_null(list_head, struct drm_display_mode, head);
- if (!m_pref) {
- drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
- return NULL;
- }
+ if (!m_pref)
+ m_pref = list_first_entry(probed, struct drm_display_mode, head);
+
+ return amdgpu_dm_find_highest_refresh_mode(probed, m_pref);
}
- return amdgpu_dm_find_highest_refresh_mode(list_head, m_pref);
+ /*
+ * atomic_check can't take &drm_mode_config.mutex, so it reads only the
+ * copy, and only for the sink it was taken from
+ */
+ if (aconnector->freesync_vid_base.clock != 0 &&
+ amdgpu_dm_mode_copies_match(aconnector, sink))
+ return &aconnector->freesync_vid_base;
+
+ return NULL;
}
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
@@ -1724,12 +1754,24 @@ create_stream_for_sink(struct drm_connector *connector,
mode_refresh = drm_mode_vrefresh(&mode);
+ /*
+ * After a monitor swap, the copies describe the previous monitor until
+ * the next probe. Check them against the sink this stream is for, which
+ * a hotplug may swap at any time since atomic_check doesn't hold
+ * &drm_mode_config.mutex
+ */
+ if (aconnector && preferred_mode && !amdgpu_dm_mode_copies_match(aconnector, sink)) {
+ drm_dbg_driver(dev, "Preferred mode copy is from another sink\n");
+ preferred_mode = NULL;
+ }
+
if (aconnector && preferred_mode) {
- recalculate_timing = amdgpu_freesync_vid_mode &&
- amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink);
- if (recalculate_timing) {
+ if (amdgpu_freesync_vid_mode &&
+ amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink))
freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector,
false, sink);
+ if (freesync_mode) {
+ recalculate_timing = true;
drm_mode_copy(&saved_mode, &mode);
saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
drm_mode_copy(&mode, freesync_mode);
@@ -2676,27 +2718,25 @@ __amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
return NULL;
}
+/*
+ * atomic_check builds streams with the preferred mode copied at the end of
+ * the last probe. amdgpu_dm_connector_fill_modes() writes it with
+ * connection_mutex held, which atomic_check holds for every connector in the
+ * commit. create_stream_for_sink() uses it only for the sink it was taken
+ * from
+ */
STATIC_IFN_KUNIT const struct drm_display_mode *
-amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
- struct drm_display_mode *native_buf)
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector)
{
- const struct drm_display_mode *mode;
+ struct amdgpu_dm_connector *aconnector;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
- list_for_each_entry(mode, &connector->modes, head) {
- if (mode->type & DRM_MODE_TYPE_PREFERRED)
- return mode;
- }
-
- mode = amdgpu_dm_panel_native_mode(connector, &connector->modes, native_buf);
- if (mode)
- return mode;
+ drm_modeset_lock_assert_held(&connector->dev->mode_config.connection_mutex);
- mode = list_first_entry_or_null(&connector->modes,
- struct drm_display_mode, head);
- if (!mode) {
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (!aconnector->preferred_mode.clock) {
/*
* This may not be an error, the use case is when we have no
* usermode calls to reset and set mode upon hotplug. In this
@@ -2704,9 +2744,10 @@ amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
* and the modelist may not be filled in time
*/
drm_dbg_driver(connector->dev, "No preferred mode found\n");
+ return NULL;
}
- return mode;
+ return &aconnector->preferred_mode;
}
EXPORT_IF_KUNIT(amdgpu_dm_connector_preferred_mode);
@@ -2717,17 +2758,17 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
const struct dc_stream_state *old_stream)
{
const struct drm_display_mode *preferred_mode;
- struct drm_display_mode native_buf;
/*
* __amdgpu_dm_create_validate_stream_for_sink() checks this as well,
- * but without a state there is no commit, so skip the preferred mode
- * lookup too
+ * but without a state there is no commit, and the caller may not hold
+ * connection_mutex, which the preferred mode lookup asserts, so skip
+ * the lookup too
*/
if (!dm_state)
return NULL;
- preferred_mode = amdgpu_dm_connector_preferred_mode(connector, &native_buf);
+ preferred_mode = amdgpu_dm_connector_preferred_mode(connector);
return __amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
preferred_mode, dm_state,
old_stream);
@@ -2775,12 +2816,12 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
drm_mode_set_crtcinfo(test_mode, 0);
/*
- * During a probe connector->modes still holds the previous probe's
- * modes, possibly of another sink, and the probe knows its preferred
- * mode only once mode_valid has seen every mode. Validate each mode
- * with its own timing, even where atomic_check drives another one, the
- * preferred mode's with scaling on or the native one for eDP and LVDS
- * common modes
+ * The preferred mode copy is from the previous probe, possibly of
+ * another sink, and the probe knows its preferred mode only once
+ * mode_valid has seen every mode. Validate each mode with its own
+ * timing, even where atomic_check drives another one, the preferred
+ * mode's with scaling on or the native one for eDP and LVDS common
+ * modes
*/
stream = __amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
NULL, dm_state, NULL);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 7f475403ad220..ebaef4a6c4bc0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -137,7 +137,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes, const struct dc_sink *sink);
+ bool in_probe, const struct dc_sink *sink);
struct amdgpu_i2c_adapter *
amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem);
@@ -209,8 +209,7 @@ create_stream_for_sink(struct drm_connector *connector,
enum dc_pixel_encoding requested_encoding,
bool is_hdmi_ep);
const struct drm_display_mode *
-amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
- struct drm_display_mode *native_buf);
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector);
enum drm_connector_status
amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force);
enum drm_connector_status
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 832fce79f8acd..71ad7e4ea5c9e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2393,6 +2393,75 @@ static void dm_test_get_property_unknown(struct kunit *test)
other, &val), -EINVAL);
}
+/*
+ * A sink with an EDID, recorded as the sink the mode copies came from, as
+ * amdgpu_dm_connector_update_mode_copies() records it
+ */
+static struct dc_sink *dm_test_copies_sink(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector)
+{
+ struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ return sink;
+}
+
+static struct drm_device *dm_test_alloc_drm(struct kunit *test);
+
+/*
+ * Look up the highest refresh rate mode with the lock its callers hold,
+ * &drm_mode_config.mutex in a probe and connection_mutex in atomic_check. A
+ * connector without a device gets one for the locks
+ */
+static struct drm_display_mode *
+dm_test_highest_locked(struct kunit *test, struct amdgpu_dm_connector *aconnector,
+ bool in_probe, const struct dc_sink *sink)
+{
+ struct drm_mode_config *config;
+ struct drm_display_mode *mode;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ config = &aconnector->base.dev->mode_config;
+
+ if (in_probe) {
+ mutex_lock(&config->mutex);
+ mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, sink);
+ mutex_unlock(&config->mutex);
+ } else {
+ drm_modeset_lock(&config->connection_mutex, NULL);
+ mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, sink);
+ drm_modeset_unlock(&config->connection_mutex);
+ }
+
+ return mode;
+}
+
+/* Ask as atomic_check does, with connection_mutex held */
+static bool dm_test_is_fs_video_locked(struct kunit *test,
+ const struct drm_display_mode *mode,
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
+{
+ struct drm_modeset_lock *lock;
+ bool ret;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ lock = &aconnector->base.dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ ret = amdgpu_dm_is_freesync_video_mode(mode, aconnector, sink);
+ drm_modeset_unlock(lock);
+
+ return ret;
+}
+
/* Tests for amdgpu_dm_get_highest_refresh_rate_mode() */
/**
@@ -2417,6 +2486,7 @@ static void dm_test_highest_refresh_writeback_null(struct kunit *test)
static void dm_test_highest_refresh_cached_base(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -2424,13 +2494,18 @@ static void dm_test_highest_refresh_cached_base(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
+ sink = dm_test_copies_sink(test, aconnector);
+
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_highest_locked(test, aconnector, false, sink),
&aconnector->freesync_vid_base);
}
/**
* dm_test_highest_refresh_preferred_mode - Test preferred mode is selected
* @test: The KUnit test context
+ *
+ * With no copy taken yet, the probe path picks the preferred mode from
+ * probed_modes and leaves the copy alone
*/
static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
{
@@ -2444,6 +2519,7 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
mode->type = DRM_MODE_TYPE_PREFERRED;
mode->clock = 148500;
@@ -2451,10 +2527,11 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode->vdisplay = 1080;
mode->htotal = 2200;
mode->vtotal = 1125;
- list_add_tail(&mode->head, &aconnector->base.modes);
+ list_add_tail(&mode->head, &aconnector->base.probed_modes);
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_highest_locked(test, aconnector, true, NULL),
mode);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
}
/* Tests for amdgpu_dm_connector_update_mode_copies() */
@@ -2480,8 +2557,6 @@ dm_test_copy_add_mode(struct kunit *test, struct list_head *modes,
return mode;
}
-static struct drm_device *dm_test_alloc_drm(struct kunit *test);
-
/*
* Take the copies as amdgpu_dm_connector_fill_modes() does, with
* &drm_mode_config.mutex held. A connector without a device gets one for the
@@ -2505,7 +2580,7 @@ static void dm_test_copy_locked(struct kunit *test, struct amdgpu_dm_connector *
* @test: The KUnit test context
*
* The copy is from the previous probe, possibly of another monitor, so a
- * caller that passes use_probed_modes must get this probe's mode
+ * caller that passes in_probe must get this probe's mode
*/
static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
{
@@ -2525,7 +2600,7 @@ static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
KUNIT_EXPECT_PTR_EQ(test,
- amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, NULL),
+ dm_test_highest_locked(test, aconnector, true, NULL),
mode);
}
@@ -2843,6 +2918,7 @@ static void dm_test_fill_modes_copies_probe(struct kunit *test)
static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -2850,7 +2926,8 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, NULL, aconnector, sink));
}
/**
@@ -2860,6 +2937,7 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
static void dm_test_is_freesync_video_mode_match(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
struct drm_display_mode candidate = {};
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
@@ -2887,7 +2965,8 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
candidate.vsync_end = 1089;
candidate.vtotal = 1125;
- KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_TRUE(test, dm_test_is_fs_video_locked(test, &candidate, aconnector, sink));
}
/**
@@ -2897,6 +2976,7 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
struct drm_display_mode candidate = {};
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
@@ -2916,7 +2996,8 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
candidate.htotal = 1650;
candidate.vtotal = 750;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, &candidate, aconnector, sink));
}
/* Tests for amdgpu_dm_update_cacp_caps() */
@@ -4167,6 +4248,27 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test)
return ctx;
}
+/* atomic_check fills the stream with connection_mutex held */
+static void dm_test_fill_stream_locked(struct dc_stream_state *stream,
+ const struct drm_display_mode *mode_in,
+ const struct drm_connector *connector,
+ const struct dc_sink *sink,
+ const struct drm_connector_state *connector_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc,
+ enum dc_pixel_encoding requested_encoding,
+ bool is_hdmi_ep)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ fill_stream_properties_from_drm_display_mode(stream, mode_in, connector, sink,
+ connector_state, old_stream,
+ requested_bpc, requested_encoding,
+ is_hdmi_ep);
+ drm_modeset_unlock(lock);
+}
+
/**
* dm_test_fill_stream_borders_zeroed - Test the timing borders are cleared
* @test: The KUnit test context
@@ -4182,9 +4284,9 @@ static void dm_test_fill_stream_borders_zeroed(struct kunit *test)
timing->v_border_top = 7;
timing->v_border_bottom = 8;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_right, 0);
@@ -4204,9 +4306,9 @@ static void dm_test_fill_stream_rgb_defaults(struct kunit *test)
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB);
KUNIT_EXPECT_EQ(test, (int)timing->timing_3d_format,
@@ -4231,9 +4333,9 @@ static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 1);
@@ -4251,9 +4353,9 @@ static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test)
/* No sync flags set on the mode → polarity stays negative (0). */
ctx->mode->flags = 0;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0);
KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0);
@@ -4281,9 +4383,9 @@ static void dm_test_fill_stream_inherits_old_stream(struct kunit *test)
/* Mode flags would force positive polarity if the old stream were ignored. */
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
@@ -4312,9 +4414,9 @@ static void dm_test_fill_stream_timing_from_crtc(struct kunit *test)
ctx->mode->crtc_vsync_end = 1089;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
KUNIT_EXPECT_EQ(test, (int)timing->h_total, 2200);
@@ -4340,9 +4442,9 @@ static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test)
ctx->aconnector->base.display_info.bpc = 12;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_101010);
@@ -4358,9 +4460,9 @@ static void dm_test_fill_stream_content_type(struct kunit *test)
ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type,
(int)DISPLAY_CONTENT_TYPE_GRAPHICS);
@@ -4377,9 +4479,9 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test)
ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio,
(int)ASPECT_RATIO_16_9);
@@ -4398,9 +4500,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr420(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR420, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR420, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR420);
@@ -4417,9 +4519,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr422(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR422, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR422, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR422);
@@ -4436,9 +4538,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr444(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR444, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR444, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR444);
@@ -4461,9 +4563,9 @@ static void dm_test_fill_stream_hdmi_ep_clamps_depth(struct kunit *test)
ctx->aconnector->base.display_info.max_tmds_clock = 160000;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, true);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, true);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_888);
@@ -4485,9 +4587,9 @@ static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test)
ctx->aconnector->base.display_info.max_tmds_clock = 160000;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_101010);
@@ -4519,9 +4621,9 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
ctx->stream->signal = SIGNAL_TYPE_HDMI_TYPE_A;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
+ PIXEL_ENCODING_RGB, false);
/* The HDMI block overwrote the old stream's VIC of 99 with the AVI code. */
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
@@ -4531,13 +4633,15 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
* dm_test_fill_stream_freesync_video - Test timing taken from the mode for FS video
* @test: The KUnit test context
*
- * When the mode matches the connector's cached freesync base mode the timing is
- * taken from the drm mode fields (not crtc_*), including a 10x pixel clock.
+ * When the mode matches the connector's cached freesync base mode, taken from
+ * a probe of the sink passed in, the timing is taken from the drm mode fields
+ * (not crtc_*), including a 10x pixel clock.
*/
static void dm_test_fill_stream_freesync_video(struct kunit *test)
{
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
+ struct dc_sink *sink = dm_test_copies_sink(test, ctx->aconnector);
/* Cached high mode == the mode passed in => freesync video mode. */
ctx->aconnector->freesync_vid_base.clock = 148500;
@@ -4560,9 +4664,9 @@ static void dm_test_fill_stream_freesync_video(struct kunit *test)
ctx->mode->vsync_end = 1089;
ctx->mode->vtotal = 1125;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, sink, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
KUNIT_EXPECT_EQ(test, (int)timing->v_addressable, 1080);
@@ -4644,6 +4748,29 @@ static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test)
return dm_test_stream_ctx_alloc_type(test, DRM_MODE_CONNECTOR_DisplayPort);
}
+/* atomic_check builds streams with connection_mutex held */
+static struct dc_stream_state *
+dm_test_create_stream_locked(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc,
+ enum dc_pixel_encoding requested_encoding,
+ bool is_hdmi_ep)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+ struct dc_stream_state *stream;
+
+ drm_modeset_lock(lock, NULL);
+ stream = create_stream_for_sink(connector, drm_mode, preferred_mode, dm_state,
+ old_stream, requested_bpc, requested_encoding,
+ is_hdmi_ep);
+ drm_modeset_unlock(lock);
+
+ return stream;
+}
+
/**
* dm_test_create_stream_fake_sink_success - Test a stream is built from a fake sink
* @test: The KUnit test context
@@ -4653,9 +4780,9 @@ static void dm_test_create_stream_fake_sink_success(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
dc_stream_release(stream);
@@ -4670,9 +4797,9 @@ static void dm_test_create_stream_sets_dm_context(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_PTR_EQ(test, stream->dm_stream_context, ctx->aconnector);
@@ -4688,9 +4815,9 @@ static void dm_test_create_stream_virtual_signal(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_VIRTUAL);
@@ -4708,9 +4835,9 @@ static void dm_test_create_stream_scaling_src(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920);
@@ -4740,9 +4867,9 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
ctx->aconnector->dc_sink = sink;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_PTR_EQ(test, stream->sink, sink);
@@ -4751,7 +4878,43 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
dc_sink_release(sink);
}
-/* Append a copy of the context mode to the connector's mode list. */
+static void dm_test_release_sink(void *sink)
+{
+ dc_sink_release(sink);
+}
+
+/*
+ * Attach a sink with an EDID on @link to @aconnector, as a detect would, and
+ * record it as the sink the mode copies came from. The sink is released when
+ * the test ends
+ */
+static struct dc_sink *dm_test_attach_copies_sink(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector,
+ struct dc_link *link)
+{
+ struct dc_sink_init_data init = {
+ .link = link,
+ .sink_signal = link->connector_signal,
+ };
+ struct dc_sink *sink = dc_sink_create(&init);
+
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_release_sink, sink), 0);
+ sink->sink_signal = SIGNAL_TYPE_VIRTUAL;
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+
+ aconnector->dc_sink = sink;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ return sink;
+}
+
+/*
+ * Append a copy of the context mode to the connector's mode list and take the
+ * copies that amdgpu_dm_connector_fill_modes() takes after a probe
+ */
static void dm_test_stream_add_mode(struct kunit *test, struct dm_test_stream_ctx *ctx,
u32 type)
{
@@ -4761,6 +4924,9 @@ static void dm_test_stream_add_mode(struct kunit *test, struct dm_test_stream_ct
drm_mode_copy(mode, ctx->mode);
mode->type = type;
list_add_tail(&mode->head, &ctx->aconnector->base.modes);
+ if (!ctx->aconnector->dc_sink)
+ dm_test_attach_copies_sink(test, ctx->aconnector, ctx->link);
+ dm_test_copy_locked(test, ctx->aconnector);
}
/* Give the context mode a full set of CRTC timings. */
@@ -4807,11 +4973,10 @@ static void dm_test_create_stream_preferred_mode(struct kunit *test)
dm_test_stream_set_timings(ctx);
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- list_first_entry(&ctx->aconnector->base.modes,
- struct drm_display_mode, head),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1920);
@@ -4837,11 +5002,10 @@ static void dm_test_create_stream_scaled_keeps_refresh(struct kunit *test)
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
ctx->dm_state->scaling = RMX_FULL;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- list_first_entry(&ctx->aconnector->base.modes,
- struct drm_display_mode, head),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1920);
@@ -4870,9 +5034,9 @@ static void dm_test_create_stream_timing_override(struct kunit *test)
ctx->aconnector->timing_changed = true;
ctx->aconnector->timing_requested = timing;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1280);
@@ -4899,9 +5063,9 @@ static void dm_test_create_stream_dp_vsc_colorimetry(struct kunit *test)
ctx->link->dpcd_caps.dpcd_rev.raw = 0x14;
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_DISPLAY_PORT);
@@ -4927,9 +5091,9 @@ static void dm_test_create_stream_dp_colorimetry_quirk(struct kunit *test)
ctx->link->dpcd_caps.dpcd_rev.raw = 0x14;
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_FALSE(test, stream->use_vsc_sdp_for_colorimetry);
@@ -4954,9 +5118,9 @@ static void dm_test_create_stream_hdmi_vsif(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
sink = dm_test_stream_add_sink(test, ctx, SIGNAL_TYPE_HDMI_TYPE_A);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_HDMI_TYPE_A);
@@ -4990,8 +5154,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
static const u8 edid[EDID_LENGTH];
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
struct amdgpu_encoder *aenc;
- struct drm_display_mode *native, native_buf;
struct dc_stream_state *stream;
+ struct drm_display_mode *native;
aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aenc);
@@ -5009,8 +5173,6 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
native->vsync_start = 2403;
native->vsync_end = 2409;
native->vtotal = 2469;
- /* Cached by the first, unlimited probe */
- drm_mode_copy(&ctx->aconnector->freesync_vid_base, native);
drm_mode_copy(ctx->mode, native);
ctx->mode->hdisplay = 1920;
@@ -5038,12 +5200,12 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
ctx->aconnector->drm_edid = drm_edid_alloc(edid, sizeof(edid));
KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->drm_edid);
}
+ dm_test_copy_locked(test, ctx->aconnector);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- amdgpu_dm_connector_preferred_mode(&ctx->aconnector->base,
- &native_buf),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
drm_edid_free(ctx->aconnector->drm_edid);
ctx->aconnector->drm_edid = NULL;
@@ -6784,7 +6946,8 @@ static void dm_test_add_fs_modes_no_preferred_mode(struct kunit *test)
/*
* Build a DisplayPort connector whose highest-refresh mode is a fixed
- * 1920x1080@60 timing. A non-zero freesync_vid_base.clock makes
+ * 1920x1080@60 timing. While probed_modes is empty, a non-zero
+ * freesync_vid_base.clock taken from the connector's dc_sink makes
* amdgpu_dm_get_highest_refresh_rate_mode() return that timing directly,
* giving the test full control of the reference mode.
*/
@@ -6802,6 +6965,7 @@ static struct amdgpu_dm_connector *dm_test_fs_setup(struct kunit *test)
m->vsync_start = 1084;
m->vsync_end = 1089;
m->vtotal = 1125;
+ aconnector->dc_sink = dm_test_copies_sink(test, aconnector);
return aconnector;
}
@@ -6845,11 +7009,16 @@ static void dm_test_add_fs_modes_generates(struct kunit *test)
static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+ unsigned int count;
aconnector->min_vfreq = 100;
aconnector->max_vfreq = 120;
- KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ mutex_lock(&aconnector->base.dev->mode_config.mutex);
+ count = add_fs_modes(aconnector);
+ mutex_unlock(&aconnector->base.dev->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 0);
KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 0);
}
@@ -6865,12 +7034,17 @@ static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
static void dm_test_add_fs_modes_skips_illegal(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+ unsigned int count;
aconnector->freesync_vid_base.vtotal = 1133;
aconnector->min_vfreq = 59;
aconnector->max_vfreq = 60;
- KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ mutex_lock(&aconnector->base.dev->mode_config.mutex);
+ count = add_fs_modes(aconnector);
+ mutex_unlock(&aconnector->base.dev->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 0);
}
/**
@@ -7535,6 +7709,24 @@ static void dm_test_create_validate_stream_null_dm_state(struct kunit *test)
/* Tests for amdgpu_dm_create_validate_stream_for_sink() */
+/* atomic_check holds connection_mutex for every connector it validates */
+static struct dc_stream_state *
+dm_test_validate_locked(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+ struct dc_stream_state *stream;
+
+ drm_modeset_lock(lock, NULL);
+ stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
+ dm_state, old_stream);
+ drm_modeset_unlock(lock);
+
+ return stream;
+}
+
/*
* Build a connector embedded in an amdgpu_device (so drm_to_adev() resolves)
* carrying a dc_link but a deliberately low atomic-requested bpc. Every
@@ -7670,16 +7862,16 @@ static void dm_test_create_validate_stream_no_valid_bpc(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
/* Same result when the sink-reported bpc is the limiting cap. */
ctx->dm_state->base.max_requested_bpc = 16;
ctx->aconnector->base.display_info.bpc = 4;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base, ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7704,9 +7896,9 @@ static void dm_test_create_validate_stream_hdmi_ycbcr(struct kunit *test)
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
dm_test_set_420_also_mode(test, ctx->drm, &ctx->aconnector->base, ctx->mode);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7726,9 +7918,9 @@ static void dm_test_create_validate_stream_force_ycbcr420(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR420;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7751,9 +7943,9 @@ static void dm_test_create_validate_stream_force_ycbcr422(struct kunit *test)
ctx->aconnector->base.display_info.color_formats =
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7776,9 +7968,9 @@ static void dm_test_create_validate_stream_force_ycbcr444(struct kunit *test)
ctx->aconnector->base.display_info.color_formats =
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7925,10 +8117,10 @@ static void dm_test_create_validate_stream_prune_timing(struct kunit *test)
c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_fail;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
}
/**
@@ -7947,10 +8139,10 @@ static void dm_test_create_validate_stream_prune_context(struct kunit *test)
c->link_srv->validate_mode_timing = dm_test_cvs_validate_mode_timing_ok;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
}
static enum dc_pixel_encoding dm_test_cvs_first_encoding;
@@ -7979,10 +8171,10 @@ static void dm_test_create_validate_stream_hdmi_rgb_first(struct kunit *test)
dm_test_cvs_first_encoding = PIXEL_ENCODING_UNDEFINED;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
KUNIT_EXPECT_EQ(test, (int)dm_test_cvs_first_encoding, (int)PIXEL_ENCODING_RGB);
}
@@ -7997,15 +8189,13 @@ static bool dm_test_cvs_record_first_h_addressable(struct timing_generator *tg,
return false;
}
-/*
- * A 3840x2160 preferred mode in connector->modes, as a probe of another sink
- * leaves it until the next probe prunes it
- */
-static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_dc *c)
+/* A 3840x2160 preferred mode copy, taken from a probe of the connector's sink */
+static void dm_test_cvs_set_preferred_copy(struct kunit *test, struct dm_test_cvs_dc *c)
{
- struct drm_display_mode *pref = drm_mode_create(c->drm);
+ struct drm_display_mode *pref = &c->aconnector->preferred_mode;
+
+ dm_test_attach_copies_sink(test, c->aconnector, c->link);
- KUNIT_ASSERT_NOT_NULL(test, pref);
pref->hdisplay = 3840;
pref->hsync_start = 4016;
pref->hsync_end = 4104;
@@ -8017,44 +8207,45 @@ static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_
pref->clock = 594000;
pref->type = DRM_MODE_TYPE_PREFERRED;
drm_mode_set_crtcinfo(pref, CRTC_INTERLACE_HALVE_V);
- list_add_tail(&pref->head, &c->aconnector->base.modes);
}
/**
- * dm_test_create_validate_stream_scaled_preferred - Test atomic_check scales to the preferred mode
+ * dm_test_create_validate_stream_scaled_copy - Test atomic_check scales to the copy
* @test: The KUnit test context
*
* With scaling on, the stream for a 1920x1080 mode takes the CRTC timing of
- * the preferred mode in connector->modes
+ * the preferred mode copy
*/
-static void dm_test_create_validate_stream_scaled_preferred(struct kunit *test)
+static void dm_test_create_validate_stream_scaled_copy(struct kunit *test)
{
struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
c->dm_state->scaling = RMX_FULL;
- dm_test_cvs_add_preferred_4k(test, c);
+ dm_test_cvs_set_preferred_copy(test, c);
c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
dm_test_cvs_first_h_addressable = 0;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
}
/**
- * dm_test_mode_valid_ignores_preferred_mode - Test mode_valid uses the mode's own timing
+ * dm_test_mode_valid_ignores_preferred_copy - Test mode_valid uses the mode's own timing
* @test: The KUnit test context
*
- * During a probe connector->modes still holds the previous sink's preferred
- * mode. Even with scaling on, the mode is validated with its own 1920x1080
- * timing rather than that mode's 3840x2160 one
+ * mode_valid runs inside a probe, before the copy is refreshed, so the copy
+ * may come from another sink. Even with scaling on, the mode is validated
+ * with its own 1920x1080 timing rather than the copy's 3840x2160 one
*/
-static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
+static void dm_test_mode_valid_ignores_preferred_copy(struct kunit *test)
{
struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+ struct mutex *lock = &c->aconnector->base.dev->mode_config.mutex;
+ enum drm_mode_status status;
struct dc_sink *em_sink;
em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
@@ -8068,13 +8259,15 @@ static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, c->aconnector->base.state);
to_dm_connector_state(c->aconnector->base.state)->scaling = RMX_FULL;
- dm_test_cvs_add_preferred_4k(test, c);
+ dm_test_cvs_set_preferred_copy(test, c);
c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
dm_test_cvs_first_h_addressable = 0;
- KUNIT_EXPECT_EQ(test,
- amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode),
- MODE_ERROR);
+ mutex_lock(lock);
+ status = amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode);
+ mutex_unlock(lock);
+
+ KUNIT_EXPECT_EQ(test, status, MODE_ERROR);
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
}
@@ -11309,8 +11502,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
- KUNIT_CASE(dm_test_create_validate_stream_scaled_preferred),
- KUNIT_CASE(dm_test_mode_valid_ignores_preferred_mode),
+ KUNIT_CASE(dm_test_create_validate_stream_scaled_copy),
+ KUNIT_CASE(dm_test_mode_valid_ignores_preferred_copy),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index fab6df40ed989..8eaa5909068bc 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3213,6 +3213,23 @@ static void dm_test_aquire_global_lock_waits_commit(struct kunit *test)
KUNIT_EXPECT_EQ(test, ret, 0);
}
+/* Pick the config with connection_mutex held, as atomic_check does */
+static void dm_test_fs_video_config_locked(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *crtc_state,
+ const struct dc_stream_state *stream)
+{
+ struct drm_modeset_lock *lock;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = adev_to_drm(dm_kunit_alloc_adev(test));
+ lock = &aconnector->base.dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ drm_modeset_unlock(lock);
+}
+
/**
* dm_test_freesync_video_config_no_old_stream - Test the fixed config needs no old stream
* @test: The KUnit test context
@@ -3229,6 +3246,7 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
struct drm_display_mode *base, *mode;
struct dm_crtc_state *crtc_state;
struct dc_stream_state *stream;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -3236,6 +3254,8 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, crtc_state);
stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, stream);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
base = &aconnector->freesync_vid_base;
@@ -3249,20 +3269,26 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
base->vsync_end = 1089;
base->vtotal = 1125;
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+ stream->sink = sink;
+
crtc_state->freesync_config.state = VRR_STATE_INACTIVE;
mode = &crtc_state->base.mode;
drm_mode_copy(mode, base);
amdgpu_freesync_vid_mode = 1;
- dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
(int)VRR_STATE_INACTIVE);
mode->vsync_start += 225;
mode->vsync_end += 225;
mode->vtotal += 225;
- dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
(int)VRR_STATE_ACTIVE_FIXED);
KUNIT_EXPECT_EQ(test, crtc_state->freesync_config.fixed_refresh_in_uhz, 50000000U);
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 7/8] drm/amd/display: Stop reading connector->modes from atomic_check
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
Have amdgpu_dm_create_validate_stream_for_sink() and
amdgpu_dm_get_highest_refresh_rate_mode() read the copies that
amdgpu_dm_connector_update_mode_copies() writes under connection_mutex,
instead of walking connector->modes without mode_config.mutex. Their
atomic_check callers hold connection_mutex, and both lookups now assert
the lock they need. A missing copy counts as an empty list
Use the copies only for a sink with the EDID recorded with them. After a
monitor swap the HPD handlers run an atomic commit before the next
probe, and the old copies would have scaling drive the new monitor at
the old native timing. Without copies, create_stream_for_sink() uses the
requested mode's own timing, as before the first probe
A sink without EDID never matches. create_stream_for_sink() used to
scale such a sink to the first entry of connector->modes, the largest of
the drm_add_modes_noedid() defaults, and now drives the requested mode's
own timing
A hotplug can swap dc_sink while atomic_check runs, so the copies are
checked against the sink of the stream being built.
create_stream_for_sink() passes its own, and
dm_set_freesync_video_config() passes new_stream->sink
The probe and hotplug paths hold mode_config.mutex and pass in_probe,
renamed from use_probed_modes since the other case no longer walks any
list. For them amdgpu_dm_get_highest_refresh_rate_mode() walks
probed_modes, falling back to the copy when it is empty. On a first
probe, with no copy yet, mode_valid checks FreeSync video modes with
their own vtotal, which is fine since their pixel clock and htotal match
the base mode
Link: https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 131 ++++--
.../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 5 +-
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 523 ++++++++++++++-------
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 30 +-
4 files changed, 474 insertions(+), 215 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index bddab6a462563..c1b814c9fa865 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1177,6 +1177,22 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
return false;
}
+/*
+ * Whether the mode copies were taken from a probe of @sink, or of a sink with
+ * the same EDID, as after a replug of the same monitor. A sink without EDID
+ * never matches, since nothing tells two of them apart. A hotplug can swap
+ * dc_sink while atomic_check runs, so atomic_check passes the sink of the
+ * stream it builds or checks rather than reading dc_sink again
+ */
+static bool amdgpu_dm_mode_copies_match(const struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
+{
+ u32 size = amdgpu_dm_sink_edid_size(sink);
+
+ return size && size == aconnector->mode_copies_edid_size &&
+ !memcmp(sink->dc_edid.raw_edid, aconnector->mode_copies_edid, size);
+}
+
/**
* DOC: FreeSync Video
*
@@ -1209,35 +1225,49 @@ amdgpu_dm_probed_freesync_vid_base_listed(struct list_head *modes,
*/
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes, const struct dc_sink *sink)
+ bool in_probe, const struct dc_sink *sink)
{
+ struct drm_mode_config *config = &aconnector->base.dev->mode_config;
+ struct list_head *probed = &aconnector->base.probed_modes;
struct drm_display_mode *m_pref;
- struct list_head *list_head = use_probed_modes ?
- &aconnector->base.probed_modes :
- &aconnector->base.modes;
if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
/*
- * A probe reads its own probed_modes, not the copy of the last one. The
- * copy stands in while probed_modes is empty
+ * With @in_probe this walks probed_modes, which needs
+ * &drm_mode_config.mutex. Other callers read only the copies, which are
+ * written under both locks, so either is enough. atomic_check holds
+ * connection_mutex and mode_valid holds &drm_mode_config.mutex
*/
- if (aconnector->freesync_vid_base.clock != 0 &&
- !(use_probed_modes && !list_empty(list_head)))
- return &aconnector->freesync_vid_base;
+ if (in_probe)
+ lockdep_assert_held(&config->mutex);
+ else
+ lockdep_assert(lockdep_is_held(&config->mutex) ||
+ lockdep_is_held(&config->connection_mutex.mutex.base));
- m_pref = amdgpu_dm_find_preferred_mode(list_head);
- if (!m_pref) {
+ /*
+ * During a probe, probed_modes holds this probe's modes, which the
+ * previous probe's copy may not match
+ */
+ if (in_probe && !list_empty(probed)) {
+ m_pref = amdgpu_dm_find_preferred_mode(probed);
/* Probably an EDID with no preferred mode. Fallback to first entry */
- m_pref = list_first_entry_or_null(list_head, struct drm_display_mode, head);
- if (!m_pref) {
- drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n");
- return NULL;
- }
+ if (!m_pref)
+ m_pref = list_first_entry(probed, struct drm_display_mode, head);
+
+ return amdgpu_dm_find_highest_refresh_mode(probed, m_pref);
}
- return amdgpu_dm_find_highest_refresh_mode(list_head, m_pref);
+ /*
+ * atomic_check can't take &drm_mode_config.mutex, so it reads only the
+ * copy, and only for the sink it was taken from
+ */
+ if (aconnector->freesync_vid_base.clock != 0 &&
+ amdgpu_dm_mode_copies_match(aconnector, sink))
+ return &aconnector->freesync_vid_base;
+
+ return NULL;
}
EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode);
@@ -1724,12 +1754,24 @@ create_stream_for_sink(struct drm_connector *connector,
mode_refresh = drm_mode_vrefresh(&mode);
+ /*
+ * After a monitor swap, the copies describe the previous monitor until
+ * the next probe. Check them against the sink this stream is for, which
+ * a hotplug may swap at any time since atomic_check doesn't hold
+ * &drm_mode_config.mutex
+ */
+ if (aconnector && preferred_mode && !amdgpu_dm_mode_copies_match(aconnector, sink)) {
+ drm_dbg_driver(dev, "Preferred mode copy is from another sink\n");
+ preferred_mode = NULL;
+ }
+
if (aconnector && preferred_mode) {
- recalculate_timing = amdgpu_freesync_vid_mode &&
- amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink);
- if (recalculate_timing) {
+ if (amdgpu_freesync_vid_mode &&
+ amdgpu_dm_is_freesync_video_mode(&mode, aconnector, sink))
freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector,
false, sink);
+ if (freesync_mode) {
+ recalculate_timing = true;
drm_mode_copy(&saved_mode, &mode);
saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio;
drm_mode_copy(&mode, freesync_mode);
@@ -2676,27 +2718,25 @@ __amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
return NULL;
}
+/*
+ * atomic_check builds streams with the preferred mode copied at the end of
+ * the last probe. amdgpu_dm_connector_fill_modes() writes it with
+ * connection_mutex held, which atomic_check holds for every connector in the
+ * commit. create_stream_for_sink() uses it only for the sink it was taken
+ * from
+ */
STATIC_IFN_KUNIT const struct drm_display_mode *
-amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
- struct drm_display_mode *native_buf)
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector)
{
- const struct drm_display_mode *mode;
+ struct amdgpu_dm_connector *aconnector;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
return NULL;
- list_for_each_entry(mode, &connector->modes, head) {
- if (mode->type & DRM_MODE_TYPE_PREFERRED)
- return mode;
- }
-
- mode = amdgpu_dm_panel_native_mode(connector, &connector->modes, native_buf);
- if (mode)
- return mode;
+ drm_modeset_lock_assert_held(&connector->dev->mode_config.connection_mutex);
- mode = list_first_entry_or_null(&connector->modes,
- struct drm_display_mode, head);
- if (!mode) {
+ aconnector = to_amdgpu_dm_connector(connector);
+ if (!aconnector->preferred_mode.clock) {
/*
* This may not be an error, the use case is when we have no
* usermode calls to reset and set mode upon hotplug. In this
@@ -2704,9 +2744,10 @@ amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
* and the modelist may not be filled in time
*/
drm_dbg_driver(connector->dev, "No preferred mode found\n");
+ return NULL;
}
- return mode;
+ return &aconnector->preferred_mode;
}
EXPORT_IF_KUNIT(amdgpu_dm_connector_preferred_mode);
@@ -2717,17 +2758,17 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector,
const struct dc_stream_state *old_stream)
{
const struct drm_display_mode *preferred_mode;
- struct drm_display_mode native_buf;
/*
* __amdgpu_dm_create_validate_stream_for_sink() checks this as well,
- * but without a state there is no commit, so skip the preferred mode
- * lookup too
+ * but without a state there is no commit, and the caller may not hold
+ * connection_mutex, which the preferred mode lookup asserts, so skip
+ * the lookup too
*/
if (!dm_state)
return NULL;
- preferred_mode = amdgpu_dm_connector_preferred_mode(connector, &native_buf);
+ preferred_mode = amdgpu_dm_connector_preferred_mode(connector);
return __amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
preferred_mode, dm_state,
old_stream);
@@ -2775,12 +2816,12 @@ enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connec
drm_mode_set_crtcinfo(test_mode, 0);
/*
- * During a probe connector->modes still holds the previous probe's
- * modes, possibly of another sink, and the probe knows its preferred
- * mode only once mode_valid has seen every mode. Validate each mode
- * with its own timing, even where atomic_check drives another one, the
- * preferred mode's with scaling on or the native one for eDP and LVDS
- * common modes
+ * The preferred mode copy is from the previous probe, possibly of
+ * another sink, and the probe knows its preferred mode only once
+ * mode_valid has seen every mode. Validate each mode with its own
+ * timing, even where atomic_check drives another one, the preferred
+ * mode's with scaling on or the native one for eDP and LVDS common
+ * modes
*/
stream = __amdgpu_dm_create_validate_stream_for_sink(connector, test_mode,
NULL, dm_state, NULL);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
index 7f475403ad220..ebaef4a6c4bc0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h
@@ -137,7 +137,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing,
struct drm_display_mode *
amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector,
- bool use_probed_modes, const struct dc_sink *sink);
+ bool in_probe, const struct dc_sink *sink);
struct amdgpu_i2c_adapter *
amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem);
@@ -209,8 +209,7 @@ create_stream_for_sink(struct drm_connector *connector,
enum dc_pixel_encoding requested_encoding,
bool is_hdmi_ep);
const struct drm_display_mode *
-amdgpu_dm_connector_preferred_mode(struct drm_connector *connector,
- struct drm_display_mode *native_buf);
+amdgpu_dm_connector_preferred_mode(struct drm_connector *connector);
enum drm_connector_status
amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force);
enum drm_connector_status
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 832fce79f8acd..71ad7e4ea5c9e 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2393,6 +2393,75 @@ static void dm_test_get_property_unknown(struct kunit *test)
other, &val), -EINVAL);
}
+/*
+ * A sink with an EDID, recorded as the sink the mode copies came from, as
+ * amdgpu_dm_connector_update_mode_copies() records it
+ */
+static struct dc_sink *dm_test_copies_sink(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector)
+{
+ struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ return sink;
+}
+
+static struct drm_device *dm_test_alloc_drm(struct kunit *test);
+
+/*
+ * Look up the highest refresh rate mode with the lock its callers hold,
+ * &drm_mode_config.mutex in a probe and connection_mutex in atomic_check. A
+ * connector without a device gets one for the locks
+ */
+static struct drm_display_mode *
+dm_test_highest_locked(struct kunit *test, struct amdgpu_dm_connector *aconnector,
+ bool in_probe, const struct dc_sink *sink)
+{
+ struct drm_mode_config *config;
+ struct drm_display_mode *mode;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ config = &aconnector->base.dev->mode_config;
+
+ if (in_probe) {
+ mutex_lock(&config->mutex);
+ mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, sink);
+ mutex_unlock(&config->mutex);
+ } else {
+ drm_modeset_lock(&config->connection_mutex, NULL);
+ mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, sink);
+ drm_modeset_unlock(&config->connection_mutex);
+ }
+
+ return mode;
+}
+
+/* Ask as atomic_check does, with connection_mutex held */
+static bool dm_test_is_fs_video_locked(struct kunit *test,
+ const struct drm_display_mode *mode,
+ struct amdgpu_dm_connector *aconnector,
+ const struct dc_sink *sink)
+{
+ struct drm_modeset_lock *lock;
+ bool ret;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = dm_test_alloc_drm(test);
+ lock = &aconnector->base.dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ ret = amdgpu_dm_is_freesync_video_mode(mode, aconnector, sink);
+ drm_modeset_unlock(lock);
+
+ return ret;
+}
+
/* Tests for amdgpu_dm_get_highest_refresh_rate_mode() */
/**
@@ -2417,6 +2486,7 @@ static void dm_test_highest_refresh_writeback_null(struct kunit *test)
static void dm_test_highest_refresh_cached_base(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -2424,13 +2494,18 @@ static void dm_test_highest_refresh_cached_base(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
+ sink = dm_test_copies_sink(test, aconnector);
+
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_highest_locked(test, aconnector, false, sink),
&aconnector->freesync_vid_base);
}
/**
* dm_test_highest_refresh_preferred_mode - Test preferred mode is selected
* @test: The KUnit test context
+ *
+ * With no copy taken yet, the probe path picks the preferred mode from
+ * probed_modes and leaves the copy alone
*/
static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
{
@@ -2444,6 +2519,7 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
mode->type = DRM_MODE_TYPE_PREFERRED;
mode->clock = 148500;
@@ -2451,10 +2527,11 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
mode->vdisplay = 1080;
mode->htotal = 2200;
mode->vtotal = 1125;
- list_add_tail(&mode->head, &aconnector->base.modes);
+ list_add_tail(&mode->head, &aconnector->base.probed_modes);
- KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false, NULL),
+ KUNIT_EXPECT_PTR_EQ(test, dm_test_highest_locked(test, aconnector, true, NULL),
mode);
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
}
/* Tests for amdgpu_dm_connector_update_mode_copies() */
@@ -2480,8 +2557,6 @@ dm_test_copy_add_mode(struct kunit *test, struct list_head *modes,
return mode;
}
-static struct drm_device *dm_test_alloc_drm(struct kunit *test);
-
/*
* Take the copies as amdgpu_dm_connector_fill_modes() does, with
* &drm_mode_config.mutex held. A connector without a device gets one for the
@@ -2505,7 +2580,7 @@ static void dm_test_copy_locked(struct kunit *test, struct amdgpu_dm_connector *
* @test: The KUnit test context
*
* The copy is from the previous probe, possibly of another monitor, so a
- * caller that passes use_probed_modes must get this probe's mode
+ * caller that passes in_probe must get this probe's mode
*/
static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
{
@@ -2525,7 +2600,7 @@ static void dm_test_highest_refresh_probed_skips_copy(struct kunit *test)
2720, 1481, 241500, DRM_MODE_TYPE_PREFERRED);
KUNIT_EXPECT_PTR_EQ(test,
- amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true, NULL),
+ dm_test_highest_locked(test, aconnector, true, NULL),
mode);
}
@@ -2843,6 +2918,7 @@ static void dm_test_fill_modes_copies_probe(struct kunit *test)
static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -2850,7 +2926,8 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
aconnector->freesync_vid_base.clock = 148500;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, NULL, aconnector, sink));
}
/**
@@ -2860,6 +2937,7 @@ static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test)
static void dm_test_is_freesync_video_mode_match(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
struct drm_display_mode candidate = {};
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
@@ -2887,7 +2965,8 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
candidate.vsync_end = 1089;
candidate.vtotal = 1125;
- KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_TRUE(test, dm_test_is_fs_video_locked(test, &candidate, aconnector, sink));
}
/**
@@ -2897,6 +2976,7 @@ static void dm_test_is_freesync_video_mode_match(struct kunit *test)
static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
struct drm_display_mode candidate = {};
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
@@ -2916,7 +2996,8 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test)
candidate.htotal = 1650;
candidate.vtotal = 750;
- KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector, NULL));
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, &candidate, aconnector, sink));
}
/* Tests for amdgpu_dm_update_cacp_caps() */
@@ -4167,6 +4248,27 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test)
return ctx;
}
+/* atomic_check fills the stream with connection_mutex held */
+static void dm_test_fill_stream_locked(struct dc_stream_state *stream,
+ const struct drm_display_mode *mode_in,
+ const struct drm_connector *connector,
+ const struct dc_sink *sink,
+ const struct drm_connector_state *connector_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc,
+ enum dc_pixel_encoding requested_encoding,
+ bool is_hdmi_ep)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ fill_stream_properties_from_drm_display_mode(stream, mode_in, connector, sink,
+ connector_state, old_stream,
+ requested_bpc, requested_encoding,
+ is_hdmi_ep);
+ drm_modeset_unlock(lock);
+}
+
/**
* dm_test_fill_stream_borders_zeroed - Test the timing borders are cleared
* @test: The KUnit test context
@@ -4182,9 +4284,9 @@ static void dm_test_fill_stream_borders_zeroed(struct kunit *test)
timing->v_border_top = 7;
timing->v_border_bottom = 8;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_left, 0);
KUNIT_EXPECT_EQ(test, (int)timing->h_border_right, 0);
@@ -4204,9 +4306,9 @@ static void dm_test_fill_stream_rgb_defaults(struct kunit *test)
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding, (int)PIXEL_ENCODING_RGB);
KUNIT_EXPECT_EQ(test, (int)timing->timing_3d_format,
@@ -4231,9 +4333,9 @@ static void dm_test_fill_stream_sync_polarity_positive(struct kunit *test)
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 1);
@@ -4251,9 +4353,9 @@ static void dm_test_fill_stream_sync_polarity_negative(struct kunit *test)
/* No sync flags set on the mode → polarity stays negative (0). */
ctx->mode->flags = 0;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 0);
KUNIT_EXPECT_EQ(test, (int)timing->flags.VSYNC_POSITIVE_POLARITY, 0);
@@ -4281,9 +4383,9 @@ static void dm_test_fill_stream_inherits_old_stream(struct kunit *test)
/* Mode flags would force positive polarity if the old stream were ignored. */
ctx->mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
KUNIT_EXPECT_EQ(test, (int)timing->flags.HSYNC_POSITIVE_POLARITY, 1);
@@ -4312,9 +4414,9 @@ static void dm_test_fill_stream_timing_from_crtc(struct kunit *test)
ctx->mode->crtc_vsync_end = 1089;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
KUNIT_EXPECT_EQ(test, (int)timing->h_total, 2200);
@@ -4340,9 +4442,9 @@ static void dm_test_fill_stream_color_depth_requested_bpc(struct kunit *test)
ctx->aconnector->base.display_info.bpc = 12;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_101010);
@@ -4358,9 +4460,9 @@ static void dm_test_fill_stream_content_type(struct kunit *test)
ctx->conn_state->content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)ctx->stream->content_type,
(int)DISPLAY_CONTENT_TYPE_GRAPHICS);
@@ -4377,9 +4479,9 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test)
ctx->mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->aspect_ratio,
(int)ASPECT_RATIO_16_9);
@@ -4398,9 +4500,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr420(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR420, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR420, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR420);
@@ -4417,9 +4519,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr422(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR422, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR422, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR422);
@@ -4436,9 +4538,9 @@ static void dm_test_fill_stream_encoding_from_caller_ycbcr444(struct kunit *test
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_YCBCR444, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_YCBCR444, false);
KUNIT_EXPECT_EQ(test, (int)timing->pixel_encoding,
(int)PIXEL_ENCODING_YCBCR444);
@@ -4461,9 +4563,9 @@ static void dm_test_fill_stream_hdmi_ep_clamps_depth(struct kunit *test)
ctx->aconnector->base.display_info.max_tmds_clock = 160000;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, true);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, true);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_888);
@@ -4485,9 +4587,9 @@ static void dm_test_fill_stream_non_hdmi_ep_keeps_depth(struct kunit *test)
ctx->aconnector->base.display_info.max_tmds_clock = 160000;
ctx->mode->crtc_clock = 148500;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 10,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->display_color_depth,
(int)COLOR_DEPTH_101010);
@@ -4519,9 +4621,9 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
ctx->stream->signal = SIGNAL_TYPE_HDMI_TYPE_A;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, NULL, ctx->conn_state, old_stream, 8,
+ PIXEL_ENCODING_RGB, false);
/* The HDMI block overwrote the old stream's VIC of 99 with the AVI code. */
KUNIT_EXPECT_EQ(test, (int)timing->vic, 16);
@@ -4531,13 +4633,15 @@ static void dm_test_fill_stream_hdmi_infoframe(struct kunit *test)
* dm_test_fill_stream_freesync_video - Test timing taken from the mode for FS video
* @test: The KUnit test context
*
- * When the mode matches the connector's cached freesync base mode the timing is
- * taken from the drm mode fields (not crtc_*), including a 10x pixel clock.
+ * When the mode matches the connector's cached freesync base mode, taken from
+ * a probe of the sink passed in, the timing is taken from the drm mode fields
+ * (not crtc_*), including a 10x pixel clock.
*/
static void dm_test_fill_stream_freesync_video(struct kunit *test)
{
struct dm_test_fill_ctx *ctx = dm_test_fill_ctx_alloc(test);
struct dc_crtc_timing *timing = &ctx->stream->timing;
+ struct dc_sink *sink = dm_test_copies_sink(test, ctx->aconnector);
/* Cached high mode == the mode passed in => freesync video mode. */
ctx->aconnector->freesync_vid_base.clock = 148500;
@@ -4560,9 +4664,9 @@ static void dm_test_fill_stream_freesync_video(struct kunit *test)
ctx->mode->vsync_end = 1089;
ctx->mode->vtotal = 1125;
- fill_stream_properties_from_drm_display_mode(ctx->stream, ctx->mode,
- &ctx->aconnector->base, NULL, ctx->conn_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ dm_test_fill_stream_locked(ctx->stream, ctx->mode,
+ &ctx->aconnector->base, sink, ctx->conn_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_EXPECT_EQ(test, (int)timing->h_addressable, 1920);
KUNIT_EXPECT_EQ(test, (int)timing->v_addressable, 1080);
@@ -4644,6 +4748,29 @@ static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test)
return dm_test_stream_ctx_alloc_type(test, DRM_MODE_CONNECTOR_DisplayPort);
}
+/* atomic_check builds streams with connection_mutex held */
+static struct dc_stream_state *
+dm_test_create_stream_locked(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct drm_display_mode *preferred_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream,
+ int requested_bpc,
+ enum dc_pixel_encoding requested_encoding,
+ bool is_hdmi_ep)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+ struct dc_stream_state *stream;
+
+ drm_modeset_lock(lock, NULL);
+ stream = create_stream_for_sink(connector, drm_mode, preferred_mode, dm_state,
+ old_stream, requested_bpc, requested_encoding,
+ is_hdmi_ep);
+ drm_modeset_unlock(lock);
+
+ return stream;
+}
+
/**
* dm_test_create_stream_fake_sink_success - Test a stream is built from a fake sink
* @test: The KUnit test context
@@ -4653,9 +4780,9 @@ static void dm_test_create_stream_fake_sink_success(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
dc_stream_release(stream);
@@ -4670,9 +4797,9 @@ static void dm_test_create_stream_sets_dm_context(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_PTR_EQ(test, stream->dm_stream_context, ctx->aconnector);
@@ -4688,9 +4815,9 @@ static void dm_test_create_stream_virtual_signal(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_VIRTUAL);
@@ -4708,9 +4835,9 @@ static void dm_test_create_stream_scaling_src(struct kunit *test)
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test);
struct dc_stream_state *stream;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920);
@@ -4740,9 +4867,9 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
ctx->aconnector->dc_sink = sink;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_PTR_EQ(test, stream->sink, sink);
@@ -4751,7 +4878,43 @@ static void dm_test_create_stream_existing_sink(struct kunit *test)
dc_sink_release(sink);
}
-/* Append a copy of the context mode to the connector's mode list. */
+static void dm_test_release_sink(void *sink)
+{
+ dc_sink_release(sink);
+}
+
+/*
+ * Attach a sink with an EDID on @link to @aconnector, as a detect would, and
+ * record it as the sink the mode copies came from. The sink is released when
+ * the test ends
+ */
+static struct dc_sink *dm_test_attach_copies_sink(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector,
+ struct dc_link *link)
+{
+ struct dc_sink_init_data init = {
+ .link = link,
+ .sink_signal = link->connector_signal,
+ };
+ struct dc_sink *sink = dc_sink_create(&init);
+
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_release_sink, sink), 0);
+ sink->sink_signal = SIGNAL_TYPE_VIRTUAL;
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+
+ aconnector->dc_sink = sink;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ return sink;
+}
+
+/*
+ * Append a copy of the context mode to the connector's mode list and take the
+ * copies that amdgpu_dm_connector_fill_modes() takes after a probe
+ */
static void dm_test_stream_add_mode(struct kunit *test, struct dm_test_stream_ctx *ctx,
u32 type)
{
@@ -4761,6 +4924,9 @@ static void dm_test_stream_add_mode(struct kunit *test, struct dm_test_stream_ct
drm_mode_copy(mode, ctx->mode);
mode->type = type;
list_add_tail(&mode->head, &ctx->aconnector->base.modes);
+ if (!ctx->aconnector->dc_sink)
+ dm_test_attach_copies_sink(test, ctx->aconnector, ctx->link);
+ dm_test_copy_locked(test, ctx->aconnector);
}
/* Give the context mode a full set of CRTC timings. */
@@ -4807,11 +4973,10 @@ static void dm_test_create_stream_preferred_mode(struct kunit *test)
dm_test_stream_set_timings(ctx);
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- list_first_entry(&ctx->aconnector->base.modes,
- struct drm_display_mode, head),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1920);
@@ -4837,11 +5002,10 @@ static void dm_test_create_stream_scaled_keeps_refresh(struct kunit *test)
dm_test_stream_add_mode(test, ctx, DRM_MODE_TYPE_PREFERRED);
ctx->dm_state->scaling = RMX_FULL;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- list_first_entry(&ctx->aconnector->base.modes,
- struct drm_display_mode, head),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1920);
@@ -4870,9 +5034,9 @@ static void dm_test_create_stream_timing_override(struct kunit *test)
ctx->aconnector->timing_changed = true;
ctx->aconnector->timing_requested = timing;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->timing.h_addressable, 1280);
@@ -4899,9 +5063,9 @@ static void dm_test_create_stream_dp_vsc_colorimetry(struct kunit *test)
ctx->link->dpcd_caps.dpcd_rev.raw = 0x14;
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_DISPLAY_PORT);
@@ -4927,9 +5091,9 @@ static void dm_test_create_stream_dp_colorimetry_quirk(struct kunit *test)
ctx->link->dpcd_caps.dpcd_rev.raw = 0x14;
ctx->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED = 1;
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_FALSE(test, stream->use_vsc_sdp_for_colorimetry);
@@ -4954,9 +5118,9 @@ static void dm_test_create_stream_hdmi_vsif(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A;
sink = dm_test_stream_add_sink(test, ctx, SIGNAL_TYPE_HDMI_TYPE_A);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- NULL, ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ NULL, ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
KUNIT_ASSERT_NOT_NULL(test, stream);
KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_HDMI_TYPE_A);
@@ -4990,8 +5154,8 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
static const u8 edid[EDID_LENGTH];
struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc_type(test, connector_type);
struct amdgpu_encoder *aenc;
- struct drm_display_mode *native, native_buf;
struct dc_stream_state *stream;
+ struct drm_display_mode *native;
aenc = drmm_kzalloc(ctx->drm, sizeof(*aenc), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aenc);
@@ -5009,8 +5173,6 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
native->vsync_start = 2403;
native->vsync_end = 2409;
native->vtotal = 2469;
- /* Cached by the first, unlimited probe */
- drm_mode_copy(&ctx->aconnector->freesync_vid_base, native);
drm_mode_copy(ctx->mode, native);
ctx->mode->hdisplay = 1920;
@@ -5038,12 +5200,12 @@ dm_test_native_mode_stream(struct kunit *test, int connector_type, unsigned int
ctx->aconnector->drm_edid = drm_edid_alloc(edid, sizeof(edid));
KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector->drm_edid);
}
+ dm_test_copy_locked(test, ctx->aconnector);
- stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- amdgpu_dm_connector_preferred_mode(&ctx->aconnector->base,
- &native_buf),
- ctx->dm_state, NULL, 8,
- PIXEL_ENCODING_RGB, false);
+ stream = dm_test_create_stream_locked(&ctx->aconnector->base, ctx->mode,
+ &ctx->aconnector->preferred_mode,
+ ctx->dm_state, NULL, 8,
+ PIXEL_ENCODING_RGB, false);
drm_edid_free(ctx->aconnector->drm_edid);
ctx->aconnector->drm_edid = NULL;
@@ -6784,7 +6946,8 @@ static void dm_test_add_fs_modes_no_preferred_mode(struct kunit *test)
/*
* Build a DisplayPort connector whose highest-refresh mode is a fixed
- * 1920x1080@60 timing. A non-zero freesync_vid_base.clock makes
+ * 1920x1080@60 timing. While probed_modes is empty, a non-zero
+ * freesync_vid_base.clock taken from the connector's dc_sink makes
* amdgpu_dm_get_highest_refresh_rate_mode() return that timing directly,
* giving the test full control of the reference mode.
*/
@@ -6802,6 +6965,7 @@ static struct amdgpu_dm_connector *dm_test_fs_setup(struct kunit *test)
m->vsync_start = 1084;
m->vsync_end = 1089;
m->vtotal = 1125;
+ aconnector->dc_sink = dm_test_copies_sink(test, aconnector);
return aconnector;
}
@@ -6845,11 +7009,16 @@ static void dm_test_add_fs_modes_generates(struct kunit *test)
static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+ unsigned int count;
aconnector->min_vfreq = 100;
aconnector->max_vfreq = 120;
- KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ mutex_lock(&aconnector->base.dev->mode_config.mutex);
+ count = add_fs_modes(aconnector);
+ mutex_unlock(&aconnector->base.dev->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 0);
KUNIT_EXPECT_EQ(test, aconnector->probed_freesync_vid_base.clock, 0);
}
@@ -6865,12 +7034,17 @@ static void dm_test_add_fs_modes_out_of_range(struct kunit *test)
static void dm_test_add_fs_modes_skips_illegal(struct kunit *test)
{
struct amdgpu_dm_connector *aconnector = dm_test_fs_setup(test);
+ unsigned int count;
aconnector->freesync_vid_base.vtotal = 1133;
aconnector->min_vfreq = 59;
aconnector->max_vfreq = 60;
- KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0);
+ mutex_lock(&aconnector->base.dev->mode_config.mutex);
+ count = add_fs_modes(aconnector);
+ mutex_unlock(&aconnector->base.dev->mode_config.mutex);
+
+ KUNIT_EXPECT_EQ(test, count, 0);
}
/**
@@ -7535,6 +7709,24 @@ static void dm_test_create_validate_stream_null_dm_state(struct kunit *test)
/* Tests for amdgpu_dm_create_validate_stream_for_sink() */
+/* atomic_check holds connection_mutex for every connector it validates */
+static struct dc_stream_state *
+dm_test_validate_locked(struct drm_connector *connector,
+ const struct drm_display_mode *drm_mode,
+ const struct dm_connector_state *dm_state,
+ const struct dc_stream_state *old_stream)
+{
+ struct drm_modeset_lock *lock = &connector->dev->mode_config.connection_mutex;
+ struct dc_stream_state *stream;
+
+ drm_modeset_lock(lock, NULL);
+ stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode,
+ dm_state, old_stream);
+ drm_modeset_unlock(lock);
+
+ return stream;
+}
+
/*
* Build a connector embedded in an amdgpu_device (so drm_to_adev() resolves)
* carrying a dc_link but a deliberately low atomic-requested bpc. Every
@@ -7670,16 +7862,16 @@ static void dm_test_create_validate_stream_no_valid_bpc(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
/* Same result when the sink-reported bpc is the limiting cap. */
ctx->dm_state->base.max_requested_bpc = 16;
ctx->aconnector->base.display_info.bpc = 4;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base, ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base, ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7704,9 +7896,9 @@ static void dm_test_create_validate_stream_hdmi_ycbcr(struct kunit *test)
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
dm_test_set_420_also_mode(test, ctx->drm, &ctx->aconnector->base, ctx->mode);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7726,9 +7918,9 @@ static void dm_test_create_validate_stream_force_ycbcr420(struct kunit *test)
ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT;
ctx->aconnector->force_yuv_pixel_format = PIXEL_ENCODING_YCBCR420;
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7751,9 +7943,9 @@ static void dm_test_create_validate_stream_force_ycbcr422(struct kunit *test)
ctx->aconnector->base.display_info.color_formats =
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7776,9 +7968,9 @@ static void dm_test_create_validate_stream_force_ycbcr444(struct kunit *test)
ctx->aconnector->base.display_info.color_formats =
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444);
- stream = amdgpu_dm_create_validate_stream_for_sink(&ctx->aconnector->base,
- ctx->mode,
- ctx->dm_state, NULL);
+ stream = dm_test_validate_locked(&ctx->aconnector->base,
+ ctx->mode,
+ ctx->dm_state, NULL);
KUNIT_EXPECT_NULL(test, stream);
}
@@ -7925,10 +8117,10 @@ static void dm_test_create_validate_stream_prune_timing(struct kunit *test)
c->tgfuncs->validate_timing = dm_test_cvs_validate_timing_fail;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
}
/**
@@ -7947,10 +8139,10 @@ static void dm_test_create_validate_stream_prune_context(struct kunit *test)
c->link_srv->validate_mode_timing = dm_test_cvs_validate_mode_timing_ok;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
}
static enum dc_pixel_encoding dm_test_cvs_first_encoding;
@@ -7979,10 +8171,10 @@ static void dm_test_create_validate_stream_hdmi_rgb_first(struct kunit *test)
dm_test_cvs_first_encoding = PIXEL_ENCODING_UNDEFINED;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
KUNIT_EXPECT_EQ(test, (int)dm_test_cvs_first_encoding, (int)PIXEL_ENCODING_RGB);
}
@@ -7997,15 +8189,13 @@ static bool dm_test_cvs_record_first_h_addressable(struct timing_generator *tg,
return false;
}
-/*
- * A 3840x2160 preferred mode in connector->modes, as a probe of another sink
- * leaves it until the next probe prunes it
- */
-static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_dc *c)
+/* A 3840x2160 preferred mode copy, taken from a probe of the connector's sink */
+static void dm_test_cvs_set_preferred_copy(struct kunit *test, struct dm_test_cvs_dc *c)
{
- struct drm_display_mode *pref = drm_mode_create(c->drm);
+ struct drm_display_mode *pref = &c->aconnector->preferred_mode;
+
+ dm_test_attach_copies_sink(test, c->aconnector, c->link);
- KUNIT_ASSERT_NOT_NULL(test, pref);
pref->hdisplay = 3840;
pref->hsync_start = 4016;
pref->hsync_end = 4104;
@@ -8017,44 +8207,45 @@ static void dm_test_cvs_add_preferred_4k(struct kunit *test, struct dm_test_cvs_
pref->clock = 594000;
pref->type = DRM_MODE_TYPE_PREFERRED;
drm_mode_set_crtcinfo(pref, CRTC_INTERLACE_HALVE_V);
- list_add_tail(&pref->head, &c->aconnector->base.modes);
}
/**
- * dm_test_create_validate_stream_scaled_preferred - Test atomic_check scales to the preferred mode
+ * dm_test_create_validate_stream_scaled_copy - Test atomic_check scales to the copy
* @test: The KUnit test context
*
* With scaling on, the stream for a 1920x1080 mode takes the CRTC timing of
- * the preferred mode in connector->modes
+ * the preferred mode copy
*/
-static void dm_test_create_validate_stream_scaled_preferred(struct kunit *test)
+static void dm_test_create_validate_stream_scaled_copy(struct kunit *test)
{
struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
c->dm_state->scaling = RMX_FULL;
- dm_test_cvs_add_preferred_4k(test, c);
+ dm_test_cvs_set_preferred_copy(test, c);
c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
dm_test_cvs_first_h_addressable = 0;
KUNIT_EXPECT_NULL(test,
- amdgpu_dm_create_validate_stream_for_sink(&c->aconnector->base,
- c->mode,
- c->dm_state,
- NULL));
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
}
/**
- * dm_test_mode_valid_ignores_preferred_mode - Test mode_valid uses the mode's own timing
+ * dm_test_mode_valid_ignores_preferred_copy - Test mode_valid uses the mode's own timing
* @test: The KUnit test context
*
- * During a probe connector->modes still holds the previous sink's preferred
- * mode. Even with scaling on, the mode is validated with its own 1920x1080
- * timing rather than that mode's 3840x2160 one
+ * mode_valid runs inside a probe, before the copy is refreshed, so the copy
+ * may come from another sink. Even with scaling on, the mode is validated
+ * with its own 1920x1080 timing rather than the copy's 3840x2160 one
*/
-static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
+static void dm_test_mode_valid_ignores_preferred_copy(struct kunit *test)
{
struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+ struct mutex *lock = &c->aconnector->base.dev->mode_config.mutex;
+ enum drm_mode_status status;
struct dc_sink *em_sink;
em_sink = kunit_kzalloc(test, sizeof(*em_sink), GFP_KERNEL);
@@ -8068,13 +8259,15 @@ static void dm_test_mode_valid_ignores_preferred_mode(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, c->aconnector->base.state);
to_dm_connector_state(c->aconnector->base.state)->scaling = RMX_FULL;
- dm_test_cvs_add_preferred_4k(test, c);
+ dm_test_cvs_set_preferred_copy(test, c);
c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
dm_test_cvs_first_h_addressable = 0;
- KUNIT_EXPECT_EQ(test,
- amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode),
- MODE_ERROR);
+ mutex_lock(lock);
+ status = amdgpu_dm_connector_mode_valid(&c->aconnector->base, c->mode);
+ mutex_unlock(lock);
+
+ KUNIT_EXPECT_EQ(test, status, MODE_ERROR);
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
}
@@ -11309,8 +11502,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_timing),
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
- KUNIT_CASE(dm_test_create_validate_stream_scaled_preferred),
- KUNIT_CASE(dm_test_mode_valid_ignores_preferred_mode),
+ KUNIT_CASE(dm_test_create_validate_stream_scaled_copy),
+ KUNIT_CASE(dm_test_mode_valid_ignores_preferred_copy),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
KUNIT_CASE(dm_test_update_after_detect_sink_unchanged),
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index fab6df40ed989..8eaa5909068bc 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3213,6 +3213,23 @@ static void dm_test_aquire_global_lock_waits_commit(struct kunit *test)
KUNIT_EXPECT_EQ(test, ret, 0);
}
+/* Pick the config with connection_mutex held, as atomic_check does */
+static void dm_test_fs_video_config_locked(struct kunit *test,
+ struct amdgpu_dm_connector *aconnector,
+ struct dm_crtc_state *crtc_state,
+ const struct dc_stream_state *stream)
+{
+ struct drm_modeset_lock *lock;
+
+ if (!aconnector->base.dev)
+ aconnector->base.dev = adev_to_drm(dm_kunit_alloc_adev(test));
+ lock = &aconnector->base.dev->mode_config.connection_mutex;
+
+ drm_modeset_lock(lock, NULL);
+ dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ drm_modeset_unlock(lock);
+}
+
/**
* dm_test_freesync_video_config_no_old_stream - Test the fixed config needs no old stream
* @test: The KUnit test context
@@ -3229,6 +3246,7 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
struct drm_display_mode *base, *mode;
struct dm_crtc_state *crtc_state;
struct dc_stream_state *stream;
+ struct dc_sink *sink;
aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, aconnector);
@@ -3236,6 +3254,8 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
KUNIT_ASSERT_NOT_NULL(test, crtc_state);
stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, stream);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
base = &aconnector->freesync_vid_base;
@@ -3249,20 +3269,26 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
base->vsync_end = 1089;
base->vtotal = 1125;
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+ stream->sink = sink;
+
crtc_state->freesync_config.state = VRR_STATE_INACTIVE;
mode = &crtc_state->base.mode;
drm_mode_copy(mode, base);
amdgpu_freesync_vid_mode = 1;
- dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
(int)VRR_STATE_INACTIVE);
mode->vsync_start += 225;
mode->vsync_end += 225;
mode->vtotal += 225;
- dm_set_freesync_video_config(aconnector, crtc_state, stream);
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
(int)VRR_STATE_ACTIVE_FIXED);
KUNIT_EXPECT_EQ(test, crtc_state->freesync_config.fixed_refresh_in_uhz, 50000000U);
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 8/8] drm/amd/display: Add KUnit tests for the mode copies
2026-11-14 23:09 ` Hugo Sales
@ 2026-11-14 23:09 ` Hugo Sales
-1 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales via B4 Relay @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
From: Hugo Sales <hugo@hsal.es>
Test that atomic_check reads the mode copies, not connector->modes, and
only for the sink they came from. The highest refresh rate lookup
returns nothing without a copy, even with modes in connector->modes, and
nothing for a sink with another EDID or none, while a replugged sink
with the same EDID keeps the copy. Scaling ignores a preferred mode copy
from another sink, and dm_set_freesync_video_config() sets the fixed
refresh config only when the copies match the new stream's sink
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 120 +++++++++++++++++++++
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 69 ++++++++++++
2 files changed, 189 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 71ad7e4ea5c9e..6fbf85e50872c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2534,6 +2534,95 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
}
+/**
+ * dm_test_highest_refresh_skips_modes - Test atomic_check callers never walk base.modes
+ * @test: The KUnit test context
+ *
+ * base.modes needs &drm_mode_config.mutex, which atomic_check doesn't hold.
+ * Without a copy the lookup returns NULL, even though base.modes has a mode
+ */
+static void dm_test_highest_refresh_skips_modes(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+ struct drm_display_mode *mode;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+
+ mode->type = DRM_MODE_TYPE_PREFERRED;
+ mode->clock = 148500;
+ mode->hdisplay = 1920;
+ mode->vdisplay = 1080;
+ mode->htotal = 2200;
+ mode->vtotal = 1125;
+ list_add_tail(&mode->head, &aconnector->base.modes);
+
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, sink));
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
+}
+
+/**
+ * dm_test_highest_refresh_other_sink - Test a copy from another sink is ignored
+ * @test: The KUnit test context
+ *
+ * After a monitor swap the copy describes the previous monitor until the next
+ * probe, so a sink with another EDID, or with none, gets no freesync base. A
+ * new sink with the same EDID, as after a replug, keeps it
+ */
+static void dm_test_highest_refresh_other_sink(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+ struct dc_sink *probed, *other, *replugged, *no_edid;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+ replugged = kunit_kzalloc(test, sizeof(*replugged), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, replugged);
+ no_edid = kunit_kzalloc(test, sizeof(*no_edid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, no_edid);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+ aconnector->freesync_vid_base.htotal = 2200;
+ aconnector->freesync_vid_base.vtotal = 1125;
+ drm_mode_copy(mode, &aconnector->freesync_vid_base);
+
+ probed = dm_test_copies_sink(test, aconnector);
+ replugged->dc_edid = probed->dc_edid;
+ other->dc_edid = probed->dc_edid;
+ other->dc_edid.raw_edid[12]++;
+
+ KUNIT_EXPECT_PTR_EQ(test,
+ dm_test_highest_locked(test, aconnector, false, probed),
+ &aconnector->freesync_vid_base);
+ KUNIT_EXPECT_TRUE(test, dm_test_is_fs_video_locked(test, mode, aconnector, probed));
+ KUNIT_EXPECT_PTR_EQ(test,
+ dm_test_highest_locked(test, aconnector, false, replugged),
+ &aconnector->freesync_vid_base);
+
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, other));
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, mode, aconnector, other));
+ KUNIT_EXPECT_NULL(test,
+ dm_test_highest_locked(test, aconnector, false, no_edid));
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, NULL));
+}
+
/* Tests for amdgpu_dm_connector_update_mode_copies() */
static struct drm_display_mode *
@@ -8233,6 +8322,34 @@ static void dm_test_create_validate_stream_scaled_copy(struct kunit *test)
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
}
+/**
+ * dm_test_create_validate_stream_stale_copy - Test atomic_check ignores a stale copy
+ * @test: The KUnit test context
+ *
+ * After a monitor swap the connector has a sink with another EDID, and the
+ * copy describes the previous monitor. Its timing must not be applied, even
+ * with scaling on
+ */
+static void dm_test_create_validate_stream_stale_copy(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+ /* drm_mode_convert_umode() fills in the CRTC timing of atomic modes */
+ drm_mode_set_crtcinfo(c->mode, CRTC_INTERLACE_HALVE_V);
+ c->dm_state->scaling = RMX_FULL;
+ dm_test_cvs_set_preferred_copy(test, c);
+ c->aconnector->dc_sink->dc_edid.raw_edid[12]++;
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_NULL(test,
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
+}
+
/**
* dm_test_mode_valid_ignores_preferred_copy - Test mode_valid uses the mode's own timing
* @test: The KUnit test context
@@ -11271,6 +11388,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_highest_refresh_cached_base),
KUNIT_CASE(dm_test_highest_refresh_preferred_mode),
KUNIT_CASE(dm_test_highest_refresh_probed_skips_copy),
+ KUNIT_CASE(dm_test_highest_refresh_skips_modes),
+ KUNIT_CASE(dm_test_highest_refresh_other_sink),
/* amdgpu_dm_connector_update_mode_copies */
KUNIT_CASE(dm_test_update_mode_copies_highest_refresh),
KUNIT_CASE(dm_test_update_mode_copies_no_preferred),
@@ -11503,6 +11622,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
KUNIT_CASE(dm_test_create_validate_stream_scaled_copy),
+ KUNIT_CASE(dm_test_create_validate_stream_stale_copy),
KUNIT_CASE(dm_test_mode_valid_ignores_preferred_copy),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 8eaa5909068bc..b6b3964ce497d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3296,6 +3296,74 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
amdgpu_freesync_vid_mode = saved_vid_mode;
}
+/**
+ * dm_test_freesync_video_config_new_sink - Test the fixed config follows the new stream's sink
+ * @test: The KUnit test context
+ *
+ * The mode is a 50 Hz FreeSync video mode of a 1080p60 base. The fixed config
+ * is set only when the copies match the sink the new stream was built for,
+ * the sink create_stream_for_sink() checked when it picked the timing
+ */
+static void dm_test_freesync_video_config_new_sink(struct kunit *test)
+{
+ uint saved_vid_mode = amdgpu_freesync_vid_mode;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *base, *mode;
+ struct dm_crtc_state *crtc_state;
+ struct dc_stream_state *stream;
+ struct dc_sink *sink, *other;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ base = &aconnector->freesync_vid_base;
+ base->clock = 148500;
+ base->hdisplay = 1920;
+ base->hsync_start = 2008;
+ base->hsync_end = 2052;
+ base->htotal = 2200;
+ base->vdisplay = 1080;
+ base->vsync_start = 1084;
+ base->vsync_end = 1089;
+ base->vtotal = 1125;
+
+ mode = &crtc_state->base.mode;
+ drm_mode_copy(mode, base);
+ mode->vsync_start += 225;
+ mode->vsync_end += 225;
+ mode->vtotal += 225;
+
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ other->dc_edid = sink->dc_edid;
+ other->dc_edid.raw_edid[12]++;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ amdgpu_freesync_vid_mode = 1;
+
+ stream->sink = other;
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_UNSUPPORTED);
+
+ stream->sink = sink;
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+
+ amdgpu_freesync_vid_mode = saved_vid_mode;
+}
+
/**
* dm_test_update_crtc_state_unchanged - Test unchanged state needs no validation
* @test: The KUnit test context
@@ -5443,6 +5511,7 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_aquire_global_lock_no_commit),
KUNIT_CASE(dm_test_aquire_global_lock_waits_commit),
KUNIT_CASE(dm_test_freesync_video_config_no_old_stream),
+ KUNIT_CASE(dm_test_freesync_video_config_new_sink),
KUNIT_CASE(dm_test_update_crtc_state_unchanged),
KUNIT_CASE(dm_test_update_plane_state_detached),
KUNIT_CASE(dm_test_mod_power_update_streams_empty),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 8/8] drm/amd/display: Add KUnit tests for the mode copies
@ 2026-11-14 23:09 ` Hugo Sales
0 siblings, 0 replies; 19+ messages in thread
From: Hugo Sales @ 2026-11-14 23:09 UTC (permalink / raw)
To: Harry Wentland, Leo Li, Rodrigo Siqueira, Alex Deucher,
Christian König, David Airlie, Simona Vetter, Ray Wu,
Fangzhi Zuo, Roman Li, Alex Hung
Cc: amd-gfx, dri-devel, linux-kernel, Ville Syrjälä,
Aurabindo Pillai, Hugo Sales
Test that atomic_check reads the mode copies, not connector->modes, and
only for the sink they came from. The highest refresh rate lookup
returns nothing without a copy, even with modes in connector->modes, and
nothing for a sink with another EDID or none, while a replugged sink
with the same EDID keeps the copy. Scaling ignores a preferred mode copy
from another sink, and dm_set_freesync_video_config() sets the fixed
refresh config only when the copies match the new stream's sink
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 120 +++++++++++++++++++++
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 69 ++++++++++++
2 files changed, 189 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
index 71ad7e4ea5c9e..6fbf85e50872c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c
@@ -2534,6 +2534,95 @@ static void dm_test_highest_refresh_preferred_mode(struct kunit *test)
KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
}
+/**
+ * dm_test_highest_refresh_skips_modes - Test atomic_check callers never walk base.modes
+ * @test: The KUnit test context
+ *
+ * base.modes needs &drm_mode_config.mutex, which atomic_check doesn't hold.
+ * Without a copy the lookup returns NULL, even though base.modes has a mode
+ */
+static void dm_test_highest_refresh_skips_modes(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct dc_sink *sink;
+ struct drm_display_mode *mode;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.modes);
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+
+ mode->type = DRM_MODE_TYPE_PREFERRED;
+ mode->clock = 148500;
+ mode->hdisplay = 1920;
+ mode->vdisplay = 1080;
+ mode->htotal = 2200;
+ mode->vtotal = 1125;
+ list_add_tail(&mode->head, &aconnector->base.modes);
+
+ sink = dm_test_copies_sink(test, aconnector);
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, sink));
+ KUNIT_EXPECT_EQ(test, aconnector->freesync_vid_base.clock, 0);
+}
+
+/**
+ * dm_test_highest_refresh_other_sink - Test a copy from another sink is ignored
+ * @test: The KUnit test context
+ *
+ * After a monitor swap the copy describes the previous monitor until the next
+ * probe, so a sink with another EDID, or with none, gets no freesync base. A
+ * new sink with the same EDID, as after a replug, keeps it
+ */
+static void dm_test_highest_refresh_other_sink(struct kunit *test)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+ struct dc_sink *probed, *other, *replugged, *no_edid;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+ replugged = kunit_kzalloc(test, sizeof(*replugged), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, replugged);
+ no_edid = kunit_kzalloc(test, sizeof(*no_edid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, no_edid);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ INIT_LIST_HEAD(&aconnector->base.probed_modes);
+ aconnector->freesync_vid_base.clock = 148500;
+ aconnector->freesync_vid_base.hdisplay = 1920;
+ aconnector->freesync_vid_base.vdisplay = 1080;
+ aconnector->freesync_vid_base.htotal = 2200;
+ aconnector->freesync_vid_base.vtotal = 1125;
+ drm_mode_copy(mode, &aconnector->freesync_vid_base);
+
+ probed = dm_test_copies_sink(test, aconnector);
+ replugged->dc_edid = probed->dc_edid;
+ other->dc_edid = probed->dc_edid;
+ other->dc_edid.raw_edid[12]++;
+
+ KUNIT_EXPECT_PTR_EQ(test,
+ dm_test_highest_locked(test, aconnector, false, probed),
+ &aconnector->freesync_vid_base);
+ KUNIT_EXPECT_TRUE(test, dm_test_is_fs_video_locked(test, mode, aconnector, probed));
+ KUNIT_EXPECT_PTR_EQ(test,
+ dm_test_highest_locked(test, aconnector, false, replugged),
+ &aconnector->freesync_vid_base);
+
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, other));
+ KUNIT_EXPECT_FALSE(test, dm_test_is_fs_video_locked(test, mode, aconnector, other));
+ KUNIT_EXPECT_NULL(test,
+ dm_test_highest_locked(test, aconnector, false, no_edid));
+ KUNIT_EXPECT_NULL(test, dm_test_highest_locked(test, aconnector, false, NULL));
+}
+
/* Tests for amdgpu_dm_connector_update_mode_copies() */
static struct drm_display_mode *
@@ -8233,6 +8322,34 @@ static void dm_test_create_validate_stream_scaled_copy(struct kunit *test)
KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 3840);
}
+/**
+ * dm_test_create_validate_stream_stale_copy - Test atomic_check ignores a stale copy
+ * @test: The KUnit test context
+ *
+ * After a monitor swap the connector has a sink with another EDID, and the
+ * copy describes the previous monitor. Its timing must not be applied, even
+ * with scaling on
+ */
+static void dm_test_create_validate_stream_stale_copy(struct kunit *test)
+{
+ struct dm_test_cvs_dc *c = dm_test_cvs_dc_alloc(test);
+
+ /* drm_mode_convert_umode() fills in the CRTC timing of atomic modes */
+ drm_mode_set_crtcinfo(c->mode, CRTC_INTERLACE_HALVE_V);
+ c->dm_state->scaling = RMX_FULL;
+ dm_test_cvs_set_preferred_copy(test, c);
+ c->aconnector->dc_sink->dc_edid.raw_edid[12]++;
+ c->tgfuncs->validate_timing = dm_test_cvs_record_first_h_addressable;
+ dm_test_cvs_first_h_addressable = 0;
+
+ KUNIT_EXPECT_NULL(test,
+ dm_test_validate_locked(&c->aconnector->base,
+ c->mode,
+ c->dm_state,
+ NULL));
+ KUNIT_EXPECT_EQ(test, dm_test_cvs_first_h_addressable, 1920);
+}
+
/**
* dm_test_mode_valid_ignores_preferred_copy - Test mode_valid uses the mode's own timing
* @test: The KUnit test context
@@ -11271,6 +11388,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_highest_refresh_cached_base),
KUNIT_CASE(dm_test_highest_refresh_preferred_mode),
KUNIT_CASE(dm_test_highest_refresh_probed_skips_copy),
+ KUNIT_CASE(dm_test_highest_refresh_skips_modes),
+ KUNIT_CASE(dm_test_highest_refresh_other_sink),
/* amdgpu_dm_connector_update_mode_copies */
KUNIT_CASE(dm_test_update_mode_copies_highest_refresh),
KUNIT_CASE(dm_test_update_mode_copies_no_preferred),
@@ -11503,6 +11622,7 @@ static struct kunit_case amdgpu_dm_connector_tests[] = {
KUNIT_CASE(dm_test_create_validate_stream_prune_context),
KUNIT_CASE(dm_test_create_validate_stream_hdmi_rgb_first),
KUNIT_CASE(dm_test_create_validate_stream_scaled_copy),
+ KUNIT_CASE(dm_test_create_validate_stream_stale_copy),
KUNIT_CASE(dm_test_mode_valid_ignores_preferred_copy),
/* amdgpu_dm_update_connector_after_detect */
KUNIT_CASE(dm_test_update_after_detect_mst_noop),
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 8eaa5909068bc..b6b3964ce497d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -3296,6 +3296,74 @@ static void dm_test_freesync_video_config_no_old_stream(struct kunit *test)
amdgpu_freesync_vid_mode = saved_vid_mode;
}
+/**
+ * dm_test_freesync_video_config_new_sink - Test the fixed config follows the new stream's sink
+ * @test: The KUnit test context
+ *
+ * The mode is a 50 Hz FreeSync video mode of a 1080p60 base. The fixed config
+ * is set only when the copies match the sink the new stream was built for,
+ * the sink create_stream_for_sink() checked when it picked the timing
+ */
+static void dm_test_freesync_video_config_new_sink(struct kunit *test)
+{
+ uint saved_vid_mode = amdgpu_freesync_vid_mode;
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *base, *mode;
+ struct dm_crtc_state *crtc_state;
+ struct dc_stream_state *stream;
+ struct dc_sink *sink, *other;
+
+ aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, crtc_state);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, stream);
+ sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, sink);
+ other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other);
+
+ aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ base = &aconnector->freesync_vid_base;
+ base->clock = 148500;
+ base->hdisplay = 1920;
+ base->hsync_start = 2008;
+ base->hsync_end = 2052;
+ base->htotal = 2200;
+ base->vdisplay = 1080;
+ base->vsync_start = 1084;
+ base->vsync_end = 1089;
+ base->vtotal = 1125;
+
+ mode = &crtc_state->base.mode;
+ drm_mode_copy(mode, base);
+ mode->vsync_start += 225;
+ mode->vsync_end += 225;
+ mode->vtotal += 225;
+
+ memset(sink->dc_edid.raw_edid, 0x5a, 128);
+ sink->dc_edid.length = 128;
+ other->dc_edid = sink->dc_edid;
+ other->dc_edid.raw_edid[12]++;
+ aconnector->mode_copies_edid_size = 128;
+ memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid, 128);
+
+ amdgpu_freesync_vid_mode = 1;
+
+ stream->sink = other;
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_UNSUPPORTED);
+
+ stream->sink = sink;
+ dm_test_fs_video_config_locked(test, aconnector, crtc_state, stream);
+ KUNIT_EXPECT_EQ(test, (int)crtc_state->freesync_config.state,
+ (int)VRR_STATE_ACTIVE_FIXED);
+
+ amdgpu_freesync_vid_mode = saved_vid_mode;
+}
+
/**
* dm_test_update_crtc_state_unchanged - Test unchanged state needs no validation
* @test: The KUnit test context
@@ -5443,6 +5511,7 @@ static struct kunit_case amdgpu_dm_tests[] = {
KUNIT_CASE(dm_test_aquire_global_lock_no_commit),
KUNIT_CASE(dm_test_aquire_global_lock_waits_commit),
KUNIT_CASE(dm_test_freesync_video_config_no_old_stream),
+ KUNIT_CASE(dm_test_freesync_video_config_new_sink),
KUNIT_CASE(dm_test_update_crtc_state_unchanged),
KUNIT_CASE(dm_test_update_plane_state_detached),
KUNIT_CASE(dm_test_mod_power_update_streams_empty),
--
2.56.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH 4/8] drm/amd/display: Copy the preferred and highest refresh modes after probe
2026-11-14 23:09 ` Hugo Sales
(?)
@ 2026-11-14 23:40 ` sashiko-bot
-1 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-14 23:40 UTC (permalink / raw)
To: Hugo Sales; +Cc: amd-gfx, dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [High] drm/amd/display: dc_sink UAF in
amdgpu_dm_connector_update_mode_copies()
--
commit 1f5a443bde024bb59f9710748a6f559dc056a617
Author: Hugo Sales <hugo@hsal.es>
drm/amd/display: Copy the preferred and highest refresh modes after probe
This commit adds amdgpu_dm_connector_fill_modes() to copy the preferred mode,
highest refresh rate mode, and sink EDID into struct amdgpu_dm_connector
under connection_mutex after probing a connector.
Link: https://lore.kernel.org/r/auh8pUx_kXUYSdZ8@intel.com
> diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> index e477ef61ce109..491965266b786 100644
> --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
> +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
[ ... ]
> @@ -1223,6 +1268,87 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode,
> }
> EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode);
>
> +STATIC_IFN_KUNIT void
> +amdgpu_dm_connector_update_mode_copies(struct amdgpu_dm_connector *aconnector)
> +{
> + struct drm_mode_config *config = &aconnector->base.dev->mode_config;
> + struct list_head *modes = &aconnector->base.modes;
> + const struct drm_display_mode *preferred, *highest;
> + struct drm_display_mode native_buf;
> + const struct dc_sink *sink;
> +
> + lockdep_assert_held(&config->mutex);
> +
> + drm_modeset_lock(&config->connection_mutex, NULL);
> +
> + sink = aconnector->dc_sink;
> + aconnector->mode_copies_edid_size = amdgpu_dm_sink_edid_size(sink);
> + if (aconnector->mode_copies_edid_size)
> + memcpy(aconnector->mode_copies_edid, sink->dc_edid.raw_edid,
> + aconnector->mode_copies_edid_size);
[Severity: High]
Can amdgpu_dm_connector_update_mode_copies() read a freed aconnector->dc_sink
here?
In amdgpu_dm_connector_fill_modes(), drm_helper_probe_single_connector_modes()
acquires root->mst_mgr.base.lock during MST port detection and drops it on its
exit path before amdgpu_dm_connector_update_mode_copies() runs. Meanwhile,
amdgpu_dm_mst_connector_early_unregister() runs from
drm_dp_delayed_destroy_work under connector->mutex and
root->mst_mgr.base.lock, without holding mode_config.mutex or
connection_mutex, and frees dc_sink before clearing aconnector->dc_sink:
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c:amdgpu_dm_mst_connector_early_unregister() {
...
if (dc_sink) {
amdgpu_dm_With_dc_lock(adev,
dc_link_remove_remote_sink(dc_link, dc_sink));
dc_sink_release(dc_sink);
aconnector->dc_sink = NULL;
...
}
CPU0 CPU1 (kworker)
amdgpu_dm_connector_fill_modes()
drm_helper_probe_single_...()
...
drm_modeset_drop_locks(&ctx)
drm_dp_delayed_destroy_work()
...
amdgpu_dm_mst_connector_early_unregister()
lock(&root->mst_mgr.base.lock)
dc_sink_release(dc_sink) -> kfree()
amdgpu_dm_connector_update_mode_copies()
sink = aconnector->dc_sink;
amdgpu_dm_sink_edid_size(sink)
memcpy(..., sink->dc_edid.raw_edid, ...) <-- use-after-free
aconnector->dc_sink = NULL;
dm_resume() similarly releases and clears aconnector->dc_sink on SST
connectors under aconnector->hpd_lock without mode_config.mutex or
connection_mutex. This causes a slab use-after-free read of struct dc_sink
when copying sink->dc_edid.raw_edid.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114-amdgpu-modes-four-v1-0-f11e2a6cd407@hsal.es?part=4
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-11-14 23:41 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 23:09 [PATCH 0/8] drm/amd/display: Copy probed modes for atomic_check Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 1/8] drm/amd/display: Drop the unadvertised mode check Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 2/8] drm/amd/display: Don't match FreeSync video modes against the last probe Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 3/8] drm/amd/display: Validate modes with their own timing Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 4/8] drm/amd/display: Copy the preferred and highest refresh modes after probe Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:40 ` sashiko-bot
2026-11-14 23:09 ` [PATCH 5/8] drm/amd/display: Pass the sink to the FreeSync and stream timing helpers Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 6/8] drm/amd/display: Pick the FreeSync video config for the new stream's sink Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 7/8] drm/amd/display: Stop reading connector->modes from atomic_check Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
2026-11-14 23:09 ` [PATCH 8/8] drm/amd/display: Add KUnit tests for the mode copies Hugo Sales via B4 Relay
2026-11-14 23:09 ` Hugo Sales
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.