[PATCH v2 1/2] drm/client: Probe connectors with the device's mode size limits
From: Hugo Sales
Date: Sat Oct 24 2026 - 12:54:58 EST
Currently, drm_client_modeset_probe() passes the client's width and
height to ->fill_modes(), and the probe helper leaves every larger mode
out of connector->modes. drm_log, fbdev's initial setup and the
GETCONNECTOR ioctl already probe with the device's limits, but on a
hotplug fbdev passes its framebuffer size. After that, the list that
userspace and the driver share lacks every mode larger than the
framebuffer. That includes the panel's preferred mode when video= sets
a smaller one
Probe with the device's limits instead. Make the mode pickers skip
modes that don't fit width x height, using the drm_mode_validate_size()
check the probe helper applied before. The probe helper still runs
mode_valid on every mode, so only the size check moves
fbdev picks the same modes as before, with one exception. A DisplayPort
sink with no mode that fits used to end up with an empty list, so the
probe helper added its 640x480 fallback modes. Its list now keeps the
larger modes, and fbdev leaves that output off
That sink can then reach drm_client_firmware_config()'s last resort,
the CRTC's current mode, which may be larger than the framebuffer.
Check that mode's size too. When a lit connector ends up with no mode,
don't use the firmware config. Accepting it would also skip the
fallback, which may light up other connectors on that CRTC
Link: https://gitlab.freedesktop.org/drm/amd/-/issues/5983
Signed-off-by: Hugo Sales <hugo@xxxxxxx>
---
drivers/gpu/drm/drm_client_modeset.c | 72 ++++++++++++++-----
.../gpu/drm/tests/drm_client_modeset_test.c | 4 +-
2 files changed, 57 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 0080a8e9556a5..cb1bf67a577f0 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -121,11 +121,13 @@ drm_client_find_modeset(struct drm_client_dev *client, struct drm_crtc *crtc)
}
static const struct drm_display_mode *
-drm_connector_get_tiled_mode(struct drm_connector *connector)
+drm_connector_get_tiled_mode(struct drm_connector *connector, int width, int height)
{
const struct drm_display_mode *mode;
list_for_each_entry(mode, &connector->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) != MODE_OK)
+ continue;
if (mode->hdisplay == connector->tile_h_size &&
mode->vdisplay == connector->tile_v_size)
return mode;
@@ -134,11 +136,13 @@ drm_connector_get_tiled_mode(struct drm_connector *connector)
}
static const struct drm_display_mode *
-drm_connector_fallback_non_tiled_mode(struct drm_connector *connector)
+drm_connector_fallback_non_tiled_mode(struct drm_connector *connector, int width, int height)
{
const struct drm_display_mode *mode;
list_for_each_entry(mode, &connector->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) != MODE_OK)
+ continue;
if (mode->hdisplay == connector->tile_h_size &&
mode->vdisplay == connector->tile_v_size)
continue;
@@ -163,14 +167,19 @@ drm_connector_preferred_mode(struct drm_connector *connector, int width, int hei
}
static const struct drm_display_mode *
-drm_connector_first_mode(struct drm_connector *connector)
+drm_connector_first_mode(struct drm_connector *connector, int width, int height)
{
- return list_first_entry_or_null(&connector->modes,
- struct drm_display_mode, head);
+ const struct drm_display_mode *mode;
+
+ list_for_each_entry(mode, &connector->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) == MODE_OK)
+ return mode;
+ }
+ return NULL;
}
static const struct drm_display_mode *
-drm_connector_pick_cmdline_mode(struct drm_connector *connector)
+drm_connector_pick_cmdline_mode(struct drm_connector *connector, int width, int height)
{
const struct drm_cmdline_mode *cmdline_mode;
const struct drm_display_mode *mode;
@@ -183,6 +192,8 @@ drm_connector_pick_cmdline_mode(struct drm_connector *connector)
*/
list_for_each_entry(mode, &connector->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) != MODE_OK)
+ continue;
if (mode->type & DRM_MODE_TYPE_USERDEF)
return mode;
}
@@ -199,6 +210,8 @@ drm_connector_pick_cmdline_mode(struct drm_connector *connector)
prefer_non_interlace = !cmdline_mode->interlace;
again:
list_for_each_entry(mode, &connector->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) != MODE_OK)
+ continue;
/* check width/height */
if (mode->hdisplay != cmdline_mode->xres ||
mode->vdisplay != cmdline_mode->yres)
@@ -321,7 +334,7 @@ static bool drm_client_target_cloned(struct drm_device *dev,
continue;
mode_replace(dev, &modes[i],
- drm_connector_pick_cmdline_mode(connectors[i]));
+ drm_connector_pick_cmdline_mode(connectors[i], width, height));
if (!modes[i]) {
can_clone = false;
break;
@@ -357,6 +370,8 @@ static bool drm_client_target_cloned(struct drm_device *dev,
continue;
list_for_each_entry(mode, &connectors[i]->modes, head) {
+ if (drm_mode_validate_size(mode, width, height) != MODE_OK)
+ continue;
if (drm_mode_match(mode, dmt_mode,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
@@ -472,7 +487,7 @@ static bool drm_client_target_preferred(struct drm_device *dev,
mode_type = "cmdline";
mode_replace(dev, &modes[i],
- drm_connector_pick_cmdline_mode(connector));
+ drm_connector_pick_cmdline_mode(connector, width, height));
if (!modes[i]) {
mode_type = "preferred";
@@ -483,7 +498,7 @@ static bool drm_client_target_preferred(struct drm_device *dev,
if (!modes[i]) {
mode_type = "first";
mode_replace(dev, &modes[i],
- drm_connector_first_mode(connector));
+ drm_connector_first_mode(connector, width, height));
}
/*
@@ -499,14 +514,16 @@ static bool drm_client_target_preferred(struct drm_device *dev,
connector->num_h_tile * connector->num_v_tile ||
(connector->tile_h_loc == 0 &&
connector->tile_v_loc == 0 &&
- !drm_connector_get_tiled_mode(connector))) {
+ !drm_connector_get_tiled_mode(connector, width, height))) {
mode_type = "non tiled";
mode_replace(dev, &modes[i],
- drm_connector_fallback_non_tiled_mode(connector));
+ drm_connector_fallback_non_tiled_mode(connector,
+ width, height));
} else {
mode_type = "tiled";
mode_replace(dev, &modes[i],
- drm_connector_get_tiled_mode(connector));
+ drm_connector_get_tiled_mode(connector,
+ width, height));
}
}
@@ -721,7 +738,7 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
mode_type = "cmdline";
mode_replace(dev, &modes[i],
- drm_connector_pick_cmdline_mode(connector));
+ drm_connector_pick_cmdline_mode(connector, width, height));
if (!modes[i]) {
mode_type = "preferred";
@@ -732,11 +749,12 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
if (!modes[i]) {
mode_type = "first";
mode_replace(dev, &modes[i],
- drm_connector_first_mode(connector));
+ drm_connector_first_mode(connector, width, height));
}
/* last resort: use current mode */
- if (!modes[i]) {
+ if (!modes[i] &&
+ drm_mode_validate_size(&crtc->state->mode, width, height) == MODE_OK) {
mode_type = "current";
mode_replace(dev, &modes[i],
&crtc->state->mode);
@@ -750,8 +768,21 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
mode_type = "non tiled";
mode_replace(dev, &modes[i],
- drm_connector_fallback_non_tiled_mode(connector));
+ drm_connector_fallback_non_tiled_mode(connector,
+ width, height));
+ }
+
+ /*
+ * Accepting a config that leaves this connector dark would
+ * also skip the fallback, which may light up other connectors
+ * on this CRTC
+ */
+ if (!modes[i]) {
+ drm_dbg_kms(dev, "[CONNECTOR:%d:%s] no mode fits %dx%d\n",
+ connector->base.id, connector->name, width, height);
+ goto bail;
}
+
crtcs[i] = crtc;
drm_dbg_kms(dev, "[CONNECTOR::%d:%s] on [CRTC:%d:%s] using %s mode: %s\n",
@@ -865,8 +896,15 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
mutex_lock(&client->modeset_mutex);
mutex_lock(&dev->mode_config.mutex);
+ /*
+ * Probe with the device limits, not width x height. connector->modes is
+ * shared with userspace and the driver, so it must keep modes larger
+ * than this client's size. The mode pickers skip those instead.
+ */
for (i = 0; i < connector_count; i++)
- total_modes_count += connectors[i]->funcs->fill_modes(connectors[i], width, height);
+ total_modes_count += connectors[i]->funcs->fill_modes(connectors[i],
+ dev->mode_config.max_width,
+ dev->mode_config.max_height);
if (!total_modes_count)
drm_dbg_kms(dev, "No connectors reported connected with modes\n");
drm_client_connectors_enabled(connectors, connector_count, enabled);
diff --git a/drivers/gpu/drm/tests/drm_client_modeset_test.c b/drivers/gpu/drm/tests/drm_client_modeset_test.c
index ec58fe064d862..00f43fca97954 100644
--- a/drivers/gpu/drm/tests/drm_client_modeset_test.c
+++ b/drivers/gpu/drm/tests/drm_client_modeset_test.c
@@ -112,7 +112,7 @@ static void drm_test_pick_cmdline_res_1920_1080_60(struct kunit *test)
mutex_unlock(&drm->mode_config.mutex);
KUNIT_ASSERT_GT(test, ret, 0);
- mode = drm_connector_pick_cmdline_mode(connector);
+ mode = drm_connector_pick_cmdline_mode(connector, 1920, 1080);
KUNIT_ASSERT_NOT_NULL(test, mode);
KUNIT_EXPECT_TRUE(test, drm_mode_equal(expected_mode, mode));
@@ -151,7 +151,7 @@ static void drm_test_pick_cmdline_named(struct kunit *test)
mutex_unlock(&drm->mode_config.mutex);
KUNIT_ASSERT_GT(test, ret, 0);
- mode = drm_connector_pick_cmdline_mode(connector);
+ mode = drm_connector_pick_cmdline_mode(connector, 1920, 1080);
KUNIT_ASSERT_NOT_NULL(test, mode);
expected_mode = params->func(drm);
--
2.55.0