* [PATCH v5 24/38] drm/vkms: Properly render plane using their zpos
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Currently planes are rendered in creation order. This is not an issue, but
with the introduction of new zpos configuration, it is required to
properly render planes.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/vkms_crtc.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index 079abfba427d..b9428de2d4aa 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -142,14 +142,20 @@ static int vkms_crtc_atomic_check(struct drm_crtc *crtc,
return -ENOMEM;
vkms_state->num_active_planes = i;
- i = 0;
+ ret = drm_atomic_normalize_zpos(crtc->dev, state);
+ if (ret)
+ return ret;
+
drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) {
plane_state = drm_atomic_get_new_plane_state(crtc_state->state, plane);
if (!plane_state->visible)
continue;
- vkms_state->active_planes[i++] =
+ // Order planes according to their normalized_zpos
+ // After drm_atomic_normalize_zpos, the possible values are 0..n_planes-1
+ // so store them using this index
+ vkms_state->active_planes[plane_state->normalized_zpos] =
to_vkms_plane_state(plane_state);
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 25/38] drm/vkms: Introduce config for plane zpos property
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
VKMS can render plane in any order. Introduce the appropriate
configuration.
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 89 ++++++++++++++++
drivers/gpu/drm/vkms/vkms_config.c | 56 ++++++++++
drivers/gpu/drm/vkms/vkms_config.h | 143 ++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_plane.c | 11 ++
4 files changed, 299 insertions(+)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index a88d8b4a969e..4a5899b8ccfd 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -201,6 +201,9 @@ static void vkms_config_test_default_config(struct kunit *test)
KUNIT_EXPECT_EQ(test,
vkms_config_plane_get_default_color_encoding(plane_cfg),
DRM_COLOR_YCBCR_BT601);
+ KUNIT_EXPECT_EQ(test, vkms_config_plane_get_zpos_enabled(plane_cfg), false);
+ // No need to test the other zpos configurations as they are discarded if
+ // the zpos property is not created.
}
/* Encoders */
@@ -619,6 +622,91 @@ static void vkms_config_test_valid_plane_color_range(struct kunit *test)
vkms_config_destroy(config);
}
+static void vkms_config_test_valid_plane_zpos(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_plane *plane_cfg;
+
+ config = vkms_config_default_create(false, false, false, false);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ plane_cfg = get_first_plane(config);
+
+ /* Valid, zpos disabled */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, false);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, false);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 0);
+ vkms_config_plane_set_zpos_min(plane_cfg, 0);
+ vkms_config_plane_set_zpos_max(plane_cfg, 0);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid, zpos disabled, min/max are ignored */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, false);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, false);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 8);
+ vkms_config_plane_set_zpos_min(plane_cfg, 3);
+ vkms_config_plane_set_zpos_max(plane_cfg, 2);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid, zpos enabled but mutable disabled */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, false);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 1);
+ vkms_config_plane_set_zpos_min(plane_cfg, 0);
+ vkms_config_plane_set_zpos_max(plane_cfg, 0);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid, zpos enabled but mutable disabled */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, false);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 0);
+ vkms_config_plane_set_zpos_min(plane_cfg, 0);
+ vkms_config_plane_set_zpos_max(plane_cfg, 0);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Invalid, zpos enabled with min > max */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, true);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 0);
+ vkms_config_plane_set_zpos_min(plane_cfg, 1);
+ vkms_config_plane_set_zpos_max(plane_cfg, 0);
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ /* Valid, zpos enabled with min <= max */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, true);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 0);
+ vkms_config_plane_set_zpos_min(plane_cfg, 0);
+ vkms_config_plane_set_zpos_max(plane_cfg, 1);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Invalid, zpos enabled with initial < min */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, true);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 0);
+ vkms_config_plane_set_zpos_min(plane_cfg, 1);
+ vkms_config_plane_set_zpos_max(plane_cfg, 2);
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ /* Invalid, zpos enabled with initial > max */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, true);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 3);
+ vkms_config_plane_set_zpos_min(plane_cfg, 1);
+ vkms_config_plane_set_zpos_max(plane_cfg, 2);
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ /* Valid, zpos enabled with initial between min and max */
+ vkms_config_plane_set_zpos_enabled(plane_cfg, true);
+ vkms_config_plane_set_zpos_mutable(plane_cfg, true);
+ vkms_config_plane_set_zpos_initial(plane_cfg, 1);
+ vkms_config_plane_set_zpos_min(plane_cfg, 0);
+ vkms_config_plane_set_zpos_max(plane_cfg, 2);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
static void vkms_config_test_valid_plane_possible_crtcs(struct kunit *test)
{
struct vkms_config *config;
@@ -1144,6 +1232,7 @@ static struct kunit_case vkms_config_test_cases[] = {
KUNIT_CASE(vkms_config_test_valid_plane_rotations),
KUNIT_CASE(vkms_config_test_valid_plane_color_encoding),
KUNIT_CASE(vkms_config_test_valid_plane_color_range),
+ KUNIT_CASE(vkms_config_test_valid_plane_zpos),
KUNIT_CASE(vkms_config_test_valid_plane_possible_crtcs),
KUNIT_CASE(vkms_config_test_invalid_crtc_number),
KUNIT_CASE(vkms_config_test_invalid_encoder_number),
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index c7de3e307367..5bc8248f9935 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -91,6 +91,7 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
if (IS_ERR(plane_cfg))
goto err_alloc;
vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
+ vkms_config_plane_set_zpos_enabled(plane_cfg, false);
crtc_cfg = vkms_config_create_crtc(config);
if (IS_ERR(crtc_cfg))
@@ -110,6 +111,7 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
vkms_config_plane_set_type(plane_cfg,
DRM_PLANE_TYPE_OVERLAY);
vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline);
+ vkms_config_plane_set_zpos_enabled(plane_cfg, false);
if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg))
goto err_alloc;
@@ -123,6 +125,7 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR);
vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline);
+ vkms_config_plane_set_zpos_enabled(plane_cfg, false);
if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg))
goto err_alloc;
@@ -253,6 +256,33 @@ bool vkms_config_valid_plane_color_range(const struct vkms_config *config,
}
EXPORT_SYMBOL_IF_KUNIT(vkms_config_valid_plane_color_range);
+VISIBLE_IF_KUNIT
+bool vkms_config_valid_plane_zpos(const struct vkms_config *config,
+ const struct vkms_config_plane *plane_cfg)
+{
+ struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
+
+ if (!vkms_config_plane_get_zpos_enabled(plane_cfg) ||
+ !vkms_config_plane_get_zpos_mutable(plane_cfg))
+ return true;
+
+ if (vkms_config_plane_get_zpos_initial(plane_cfg) >
+ vkms_config_plane_get_zpos_max(plane_cfg)) {
+ drm_info(dev, "Configured initial zpos value bigger than zpos max\n");
+ return false;
+ }
+
+ if (vkms_config_plane_get_zpos_max(plane_cfg) <
+ vkms_config_plane_get_zpos_min(plane_cfg) ||
+ vkms_config_plane_get_zpos_initial(plane_cfg) <
+ vkms_config_plane_get_zpos_min(plane_cfg)) {
+ drm_info(dev, "Configured zpos value outside (zpos min; zpos max)\n");
+ return false;
+ }
+
+ return true;
+}
+
static bool valid_planes_for_crtc(const struct vkms_config *config,
struct vkms_config_crtc *crtc_cfg)
{
@@ -413,6 +443,9 @@ bool vkms_config_is_valid(const struct vkms_config *config)
struct vkms_config_crtc *crtc_cfg;
struct vkms_config_plane *plane_cfg;
+ bool has_zpos_enabled = false;
+ bool has_zpos_disabled = false;
+
vkms_config_for_each_plane(config, plane_cfg) {
if (!vkms_config_valid_plane_rotation(config, plane_cfg))
return false;
@@ -429,6 +462,19 @@ bool vkms_config_is_valid(const struct vkms_config *config)
"Both supported color encodings and color ranges must be set, or none\n");
return false;
}
+
+ if (!vkms_config_valid_plane_zpos(config, plane_cfg))
+ return false;
+
+ if (vkms_config_plane_get_zpos_enabled(plane_cfg))
+ has_zpos_enabled = true;
+ else
+ has_zpos_disabled = true;
+ }
+
+ if (has_zpos_enabled && has_zpos_disabled) {
+ drm_info(dev, "In the same device, all planes must have zpos enabled or none of them");
+ return false;
}
if (!valid_plane_number(config))
@@ -527,6 +573,16 @@ static int vkms_config_show(struct seq_file *m, void *data)
show_formats(m, vkms_config_plane_get_supported_formats(plane_cfg),
vkms_config_plane_get_supported_formats_count(plane_cfg));
seq_puts(m, "\n");
+ seq_printf(m, "\tzpos_enabled=%s\n",
+ str_true_false(vkms_config_plane_get_zpos_enabled(plane_cfg)));
+ seq_printf(m, "\tzpos_mutable=%s\n",
+ str_true_false(vkms_config_plane_get_zpos_mutable(plane_cfg)));
+ seq_printf(m, "\tzpos_min=%d\n",
+ vkms_config_plane_get_zpos_min(plane_cfg));
+ seq_printf(m, "\tzpos_initial=%d\n",
+ vkms_config_plane_get_zpos_initial(plane_cfg));
+ seq_printf(m, "\tzpos_max=%d\n",
+ vkms_config_plane_get_zpos_max(plane_cfg));
}
vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) {
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 3de4a92af9c5..7c0c24939a96 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -52,6 +52,11 @@ struct vkms_config {
* @supported_color_ranges: Color ranges that this plane will support
* @supported_formats: List of supported formats
* @supported_formats_count: Length of @supported_formats
+ * @zpos_enabled: Enable or disable the zpos property
+ * @zpos_mutable: Make the zpos property mutable or not (ignored if @zpos_enabled is false)
+ * @zpos_initial: Initial value for zpos property (ignored if @zpos_enabled is false)
+ * @zpos_min: Minimal value for zpos property (ignored if @zpos_enabled is false)
+ * @zpos_max: Maximal value for zpos property (ignored if @zpos_enabled is false)
*/
struct vkms_config_plane {
struct list_head link;
@@ -69,6 +74,11 @@ struct vkms_config_plane {
unsigned int supported_formats_count;
struct xarray possible_crtcs;
bool default_pipeline;
+ bool zpos_enabled;
+ bool zpos_mutable;
+ unsigned int zpos_initial;
+ unsigned int zpos_min;
+ unsigned int zpos_max;
/* Internal usage */
struct vkms_plane *plane;
@@ -557,6 +567,139 @@ vkms_config_plane_get_name(const struct vkms_config_plane *plane_cfg)
return plane_cfg->name;
}
+/**
+ * vkms_config_plane_set_zpos_enabled() - Enable or disable zpos property for a plane
+ * @plane_cfg: Plane configuration to modify
+ * @zpos_enabled: Whether to enable the zpos property
+ */
+static inline
+void vkms_config_plane_set_zpos_enabled(struct vkms_config_plane *plane_cfg,
+ bool zpos_enabled)
+{
+ plane_cfg->zpos_enabled = zpos_enabled;
+}
+
+/**
+ * vkms_config_plane_set_zpos_mutable() - Set whether zpos property is mutable
+ * @plane_cfg: Plane configuration to modify
+ * @zpos_mutable: Whether the zpos property should be mutable
+ */
+static inline
+void vkms_config_plane_set_zpos_mutable(struct vkms_config_plane *plane_cfg,
+ bool zpos_mutable)
+{
+ plane_cfg->zpos_mutable = zpos_mutable;
+}
+
+/**
+ * vkms_config_plane_set_zpos_initial() - Set the initial zpos value
+ * @plane_cfg: Plane configuration to modify
+ * @zpos_initial: Initial zpos value
+ */
+static inline
+void vkms_config_plane_set_zpos_initial(struct vkms_config_plane *plane_cfg,
+ unsigned int zpos_initial)
+{
+ plane_cfg->zpos_initial = zpos_initial;
+}
+
+/**
+ * vkms_config_plane_set_zpos_min() - Set the minimum zpos value
+ * @plane_cfg: Plane configuration to modify
+ * @zpos_min: Minimum zpos value
+ */
+static inline
+void vkms_config_plane_set_zpos_min(struct vkms_config_plane *plane_cfg,
+ unsigned int zpos_min)
+{
+ plane_cfg->zpos_min = zpos_min;
+}
+
+/**
+ * vkms_config_plane_set_zpos_max() - Set the maximum zpos value
+ * @plane_cfg: Plane configuration to modify
+ * @zpos_max: Maximum zpos value
+ */
+static inline
+void vkms_config_plane_set_zpos_max(struct vkms_config_plane *plane_cfg,
+ unsigned int zpos_max)
+{
+ plane_cfg->zpos_max = zpos_max;
+}
+
+/**
+ * vkms_config_plane_get_zpos_enabled() - Check if zpos property is enabled
+ * @plane_cfg: Plane configuration to check
+ *
+ * Returns:
+ * True if the zpos property is enabled for this plane, false otherwise.
+ */
+static inline
+bool vkms_config_plane_get_zpos_enabled(const struct vkms_config_plane *plane_cfg)
+{
+ return plane_cfg->zpos_enabled;
+}
+
+/**
+ * vkms_config_plane_get_zpos_mutable() - Check if zpos property is mutable
+ * @plane_cfg: Plane configuration to check
+ *
+ * Returns:
+ * True if the zpos property is mutable for this plane, false otherwise.
+ */
+static inline
+bool vkms_config_plane_get_zpos_mutable(const struct vkms_config_plane *plane_cfg)
+{
+ return plane_cfg->zpos_mutable;
+}
+
+/**
+ * vkms_config_plane_get_zpos_initial() - Get the initial zpos value
+ * @plane_cfg: Plane configuration to check
+ *
+ * Returns:
+ * The initial zpos value for this plane. The return value is undefined if
+ * zpos is disabled.
+ */
+static inline
+unsigned int vkms_config_plane_get_zpos_initial(const struct vkms_config_plane *plane_cfg)
+{
+ return plane_cfg->zpos_initial;
+}
+
+/**
+ * vkms_config_plane_get_zpos_min() - Get the minimum zpos value
+ * @plane_cfg: Plane configuration to check
+ *
+ * Returns:
+ * The minimum allowed zpos value for this plane. The return value is undefined
+ * if zpos is disabled.
+ */
+static inline
+unsigned int vkms_config_plane_get_zpos_min(const struct vkms_config_plane *plane_cfg)
+{
+ return plane_cfg->zpos_min;
+}
+
+/**
+ * vkms_config_plane_get_zpos_max() - Get the maximum zpos value
+ * @plane_cfg: Plane configuration to check
+ *
+ * Returns:
+ * The maximum allowed zpos value for this plane. The return value is undefined
+ * if zpos is disabled.
+ */
+static inline
+unsigned int vkms_config_plane_get_zpos_max(const struct vkms_config_plane *plane_cfg)
+{
+ return plane_cfg->zpos_max;
+}
+
+#if IS_ENABLED(CONFIG_KUNIT)
+bool vkms_config_valid_plane_zpos(const struct vkms_config *config,
+ const struct vkms_config_plane *plane_cfg);
+#endif
+
/**
* vkms_config_plane_attach_crtc - Attach a plane to a CRTC
* @plane_cfg: Plane to attach
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index 6e767cdf8194..d600a6167042 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -214,6 +214,17 @@ struct vkms_plane *vkms_plane_init(struct vkms_device *vkmsdev,
if (vkms_config_plane_get_default_pipeline(plane_cfg))
vkms_initialize_colorops(&plane->base);
+ if (vkms_config_plane_get_zpos_enabled(plane_cfg)) {
+ if (vkms_config_plane_get_zpos_mutable(plane_cfg)) {
+ drm_plane_create_zpos_property(&plane->base,
+ vkms_config_plane_get_zpos_initial(plane_cfg),
+ vkms_config_plane_get_zpos_min(plane_cfg),
+ vkms_config_plane_get_zpos_max(plane_cfg));
+ } else {
+ drm_plane_create_zpos_immutable_property(&plane->base,
+ vkms_config_plane_get_zpos_initial(plane_cfg));
+ }
+ }
return plane;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 26/38] drm/vkms: Introduce configfs for plane zpos property
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Modern compositor rely on zpos management to offload some processing to
deticated hardware. In order to test multiple configurations, add zpos
configuration to configFS.
Introduce multiple attributes to configure zpos:
- zpos_enabled - Create or not the zpos property. If not created, the zpos
is undefined.
- zpos_mutable - If the zpos property is created, allow or not the
userspace to modify it
- zpos_initial - Initial value for zpos property. Must be between zpos_min
and zpos_max
- zpos_min - Minimum zpos value for this plane. Must be <= zpos_max
- zpos_max - Maximum zpos value for this plane. Must be >= zpos_min
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
Documentation/ABI/testing/configfs-vkms | 36 +++++++
Documentation/gpu/vkms.rst | 9 +-
drivers/gpu/drm/vkms/vkms_configfs.c | 180 ++++++++++++++++++++++++++++++++
3 files changed, 224 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/configfs-vkms b/Documentation/ABI/testing/configfs-vkms
index de901ce36b2e..9bc51b1e682a 100644
--- a/Documentation/ABI/testing/configfs-vkms
+++ b/Documentation/ABI/testing/configfs-vkms
@@ -156,6 +156,42 @@ Description:
use '-' prefix. Use '+*' to add all formats, '-*' to
remove all.
+What: /sys/kernel/config/vkms/<device>/planes/<plane>/zpos_enabled
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Enable or disable the zpos property. Value: 1 - enabled,
+ 0 - disabled.
+
+What: /sys/kernel/config/vkms/<device>/planes/<plane>/zpos_mutable
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Create the zpos property as mutable or immutable.
+ Value: 1 - mutable, 0 - immutable. No effect if
+ zpos_enabled is not set.
+
+What: /sys/kernel/config/vkms/<device>/planes/<plane>/zpos_initial
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Set the initial zpos value. Must be between zpos_min
+ and zpos_max. No effect if zpos_enabled is not set.
+
+What: /sys/kernel/config/vkms/<device>/planes/<plane>/zpos_min
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Set the minimal zpos value. No effect if zpos_enabled
+ is not set.
+
+What: /sys/kernel/config/vkms/<device>/planes/<plane>/zpos_max
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Set the maximal zpos value. No effect if zpos_enabled
+ is not set.
+
What: /sys/kernel/config/vkms/<device>/planes/<plane>/possible_crtcs
Date: Nov 2025
Contact: dri-devel@lists.freedesktop.org
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index e5280a99eee8..b58e96a3c273 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -87,7 +87,7 @@ Start by creating one or more planes::
sudo mkdir /config/vkms/my-vkms/planes/plane0
-Planes have 8 configurable attributes:
+Planes have 13 configurable attributes:
- type: Plane type: 0 overlay, 1 primary, 2 cursor (same values as those
exposed by the "type" property of a plane)
@@ -113,6 +113,13 @@ Planes have 8 configurable attributes:
To remove a format, use a minus and its fourcc: -XR24
To add all formats use +*
To remove all formats, use -*
+- zpos_enabled: Enable or not the zpos property: 1 enable, 0 disable
+- zpos_mutable: Create the zpos property as a mutable or imutable property: 1 mutable,
+ 0 immutable. No effect if zpos_enabled is not set.
+- zpos_initial: Set the initial zpos value. Must be between zpos_min and zpos_max. No
+ effect if zpos_enabled is not set.
+- zpos_min: Set the minimum zpos value. No effect if zpos_enabled is not set.
+- zpos_max: Set the maximum zpos value. No effect if zpos_enabled is not set.
Continue by creating one or more CRTCs::
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index e6cb245b8582..95f4594fb683 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -687,6 +687,176 @@ static ssize_t plane_supported_formats_store(struct config_item *item,
return count;
}
+static ssize_t plane_zpos_enabled_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_plane *plane;
+ bool enabled;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ scoped_guard(mutex, &plane->dev->lock)
+ enabled = vkms_config_plane_get_zpos_enabled(plane->config);
+
+ return sprintf(page, "%d\n", enabled);
+}
+
+static ssize_t plane_zpos_enabled_store(struct config_item *item, const char *page,
+ size_t count)
+{
+ struct vkms_configfs_plane *plane;
+ bool enabled;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ if (kstrtobool(page, &enabled))
+ return -EINVAL;
+
+ scoped_guard(mutex, &plane->dev->lock) {
+ if (plane->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_plane_set_zpos_enabled(plane->config, enabled);
+ }
+
+ return (ssize_t)count;
+}
+
+static ssize_t plane_zpos_mutable_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_plane *plane;
+ bool mutable;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ scoped_guard(mutex, &plane->dev->lock)
+ mutable = vkms_config_plane_get_zpos_mutable(plane->config);
+
+ return sprintf(page, "%d\n", mutable);
+}
+
+static ssize_t plane_zpos_mutable_store(struct config_item *item, const char *page,
+ size_t count)
+{
+ struct vkms_configfs_plane *plane;
+ bool mutable;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ if (kstrtobool(page, &mutable))
+ return -EINVAL;
+
+ scoped_guard(mutex, &plane->dev->lock) {
+ if (plane->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_plane_set_zpos_mutable(plane->config, mutable);
+ }
+
+ return (ssize_t)count;
+}
+
+static ssize_t plane_zpos_initial_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int initial;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ scoped_guard(mutex, &plane->dev->lock)
+ initial = vkms_config_plane_get_zpos_initial(plane->config);
+
+ return sprintf(page, "%u\n", initial);
+}
+
+static ssize_t plane_zpos_initial_store(struct config_item *item, const char *page,
+ size_t count)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int initial;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ if (kstrtouint(page, 10, &initial))
+ return -EINVAL;
+
+ scoped_guard(mutex, &plane->dev->lock) {
+ if (plane->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_plane_set_zpos_initial(plane->config, initial);
+ }
+
+ return (ssize_t)count;
+}
+
+static ssize_t plane_zpos_min_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int min;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ scoped_guard(mutex, &plane->dev->lock)
+ min = vkms_config_plane_get_zpos_min(plane->config);
+
+ return sprintf(page, "%u\n", min);
+}
+
+static ssize_t plane_zpos_min_store(struct config_item *item, const char *page,
+ size_t count)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int min;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ if (kstrtouint(page, 10, &min))
+ return -EINVAL;
+
+ scoped_guard(mutex, &plane->dev->lock) {
+ if (plane->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_plane_set_zpos_min(plane->config, min);
+ }
+
+ return (ssize_t)count;
+}
+
+static ssize_t plane_zpos_max_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int max;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ scoped_guard(mutex, &plane->dev->lock)
+ max = vkms_config_plane_get_zpos_max(plane->config);
+
+ return sprintf(page, "%u\n", max);
+}
+
+static ssize_t plane_zpos_max_store(struct config_item *item, const char *page,
+ size_t count)
+{
+ struct vkms_configfs_plane *plane;
+ unsigned int max;
+
+ plane = plane_item_to_vkms_configfs_plane(item);
+
+ if (kstrtouint(page, 10, &max))
+ return -EINVAL;
+
+ scoped_guard(mutex, &plane->dev->lock) {
+ if (plane->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_plane_set_zpos_max(plane->config, max);
+ }
+
+ return (ssize_t)count;
+}
+
CONFIGFS_ATTR(plane_, type);
CONFIGFS_ATTR(plane_, supported_rotations);
CONFIGFS_ATTR(plane_, default_rotation);
@@ -695,6 +865,11 @@ CONFIGFS_ATTR(plane_, default_color_range);
CONFIGFS_ATTR(plane_, supported_color_encodings);
CONFIGFS_ATTR(plane_, default_color_encoding);
CONFIGFS_ATTR(plane_, supported_formats);
+CONFIGFS_ATTR(plane_, zpos_enabled);
+CONFIGFS_ATTR(plane_, zpos_mutable);
+CONFIGFS_ATTR(plane_, zpos_initial);
+CONFIGFS_ATTR(plane_, zpos_min);
+CONFIGFS_ATTR(plane_, zpos_max);
static struct configfs_attribute *plane_item_attrs[] = {
&plane_attr_type,
@@ -705,6 +880,11 @@ static struct configfs_attribute *plane_item_attrs[] = {
&plane_attr_supported_color_encodings,
&plane_attr_default_color_encoding,
&plane_attr_supported_formats,
+ &plane_attr_zpos_enabled,
+ &plane_attr_zpos_mutable,
+ &plane_attr_zpos_initial,
+ &plane_attr_zpos_min,
+ &plane_attr_zpos_max,
NULL,
};
--
2.54.0
^ permalink raw reply related
* [PATCH v5 27/38] drm/vkms: Introduce config for connector type
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
In order to emulate connector-specific behavior, add connector type
configuration.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 5 +++++
drivers/gpu/drm/vkms/vkms_config.c | 3 +++
drivers/gpu/drm/vkms/vkms_config.h | 24 ++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.c | 5 +++--
drivers/gpu/drm/vkms/vkms_connector.h | 4 +++-
drivers/gpu/drm/vkms/vkms_output.c | 2 +-
6 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index 4a5899b8ccfd..1900cc79db2b 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -137,6 +137,7 @@ static void vkms_config_test_default_config(struct kunit *test)
struct vkms_config *config;
struct vkms_config_plane *plane_cfg;
struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_connector *connector_cfg;
int n_primaries = 0;
int n_cursors = 0;
int n_overlays = 0;
@@ -211,6 +212,10 @@ static void vkms_config_test_default_config(struct kunit *test)
/* Connectors */
KUNIT_EXPECT_EQ(test, vkms_config_get_num_connectors(config), 1);
+ vkms_config_for_each_connector(config, connector_cfg) {
+ KUNIT_EXPECT_EQ(test, vkms_config_connector_get_type(connector_cfg),
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ }
KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index 5bc8248f9935..c407aea0bc6a 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -598,6 +598,8 @@ static int vkms_config_show(struct seq_file *m, void *data)
seq_puts(m, "connector:\n");
seq_printf(m, "\tstatus=%s\n",
drm_get_connector_status_name(vkms_config_connector_get_status(connector_cfg)));
+ seq_printf(m, "\ttype=%s\n",
+ drm_get_connector_type_name(vkms_config_connector_get_type(connector_cfg)));
}
return 0;
@@ -930,6 +932,7 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
connector_cfg->config = config;
connector_cfg->status = connector_status_connected;
+ vkms_config_connector_set_type(connector_cfg, DRM_MODE_CONNECTOR_VIRTUAL);
xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);
list_add_tail(&connector_cfg->link, &config->connectors);
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 7c0c24939a96..d6d185b2ccc0 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -130,6 +130,7 @@ struct vkms_config_encoder {
* struct vkms_config_connector
*
* @link: Link to the others connector in vkms_config
+ * @type: Store the type of connector using DRM_MODE_CONNECTOR_* values
* @config: The vkms_config this connector belongs to
* @status: Status (connected, disconnected...) of the connector
* @possible_encoders: Array of encoders that can be used with this connector
@@ -142,6 +143,7 @@ struct vkms_config_connector {
struct list_head link;
struct vkms_config *config;
+ int type;
enum drm_connector_status status;
struct xarray possible_encoders;
@@ -344,6 +346,28 @@ vkms_config_plane_set_default_pipeline(struct vkms_config_plane *plane_cfg,
}
/**
+ * vkms_config_connector_get_type() - Return the connector type
+ * @connector_cfg: Connector to get the type from
+ */
+static inline int
+vkms_config_connector_get_type(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->type;
+}
+
+/**
+ * vkms_config_connector_set_type() - Set the connector type
+ * @connector_cfg: Connector to set the type to
+ * @type: New connector type
+ */
+static inline void
+vkms_config_connector_set_type(struct vkms_config_connector *connector_cfg,
+ int type)
+{
+ connector_cfg->type = type;
+}
+
+/*
* vkms_config_plane_get_default_rotation() - Get the default rotation for a plane
* @plane_cfg: Plane to get the default rotation from
*
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index b0a6b212d3f4..5a87dc2d4c63 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -68,7 +68,8 @@ static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.best_encoder = vkms_conn_best_encoder,
};
-struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev)
+struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg)
{
struct drm_device *dev = &vkmsdev->drm;
struct vkms_connector *connector;
@@ -79,7 +80,7 @@ struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev)
return ERR_PTR(-ENOMEM);
ret = drmm_connector_init(dev, &connector->base, &vkms_connector_funcs,
- DRM_MODE_CONNECTOR_VIRTUAL, NULL);
+ vkms_config_connector_get_type(connector_cfg), NULL);
if (ret)
return ERR_PTR(ret);
diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h
index ed312f4eff3a..33253d29b1cc 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.h
+++ b/drivers/gpu/drm/vkms/vkms_connector.h
@@ -4,6 +4,7 @@
#define _VKMS_CONNECTOR_H_
#include "vkms_drv.h"
+#include "vkms_config.h"
#define drm_connector_to_vkms_connector(target) \
container_of(target, struct vkms_connector, base)
@@ -24,7 +25,8 @@ struct vkms_connector {
* Returns:
* The connector or an error on failure.
*/
-struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev);
+struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg);
/**
* vkms_trigger_connector_hotplug() - Update the device's connectors status
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 86ce07a617f5..1288c8551bc8 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -97,7 +97,7 @@ int vkms_output_init(struct vkms_device *vkmsdev)
struct vkms_config_encoder *possible_encoder;
unsigned long idx = 0;
- connector_cfg->connector = vkms_connector_init(vkmsdev);
+ connector_cfg->connector = vkms_connector_init(vkmsdev, connector_cfg);
if (IS_ERR(connector_cfg->connector)) {
DRM_ERROR("Failed to init connector\n");
return PTR_ERR(connector_cfg->connector);
--
2.54.0
^ permalink raw reply related
* [PATCH v5 28/38] drm/vkms: Introduce configfs for connector type
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Add a new attribute to connector to change its type. This is mostly
cosmetic and don't have direct effect in VKMS behavior.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
Documentation/ABI/testing/configfs-vkms | 7 ++++
Documentation/gpu/vkms.rst | 3 +-
drivers/gpu/drm/vkms/vkms_configfs.c | 65 +++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/configfs-vkms b/Documentation/ABI/testing/configfs-vkms
index 9bc51b1e682a..0e588a1145ff 100644
--- a/Documentation/ABI/testing/configfs-vkms
+++ b/Documentation/ABI/testing/configfs-vkms
@@ -33,6 +33,13 @@ Description:
Connection status of the connector. Possible values:
1 - connected, 2 - disconnected, 3 - unknown.
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/type
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Type of the connector. Possible values match those
+ exposed by the "type" field in drm_connector.
+
What: /sys/kernel/config/vkms/<device>/connectors/<connector>/possible_encoders
Date: Nov 2025
Contact: dri-devel@lists.freedesktop.org
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index b58e96a3c273..f2622b3fd8af 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -137,10 +137,11 @@ Last but not least, create one or more connectors::
sudo mkdir /config/vkms/my-vkms/connectors/connector0
-Connectors have 1 configurable attribute:
+Connectors have 2 configurable attribute:
- status: Connection status: 1 connected, 2 disconnected, 3 unknown (same values
as those exposed by the "status" property of a connector)
+- type: Type of connector. Same values as exposed by the "type" field in drm_connector.
To finish the configuration, link the different pipeline items::
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index 95f4594fb683..8b414f1950e8 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -1117,10 +1117,75 @@ static ssize_t connector_status_store(struct config_item *item,
return (ssize_t)count;
}
+static ssize_t connector_type_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ int type;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ type = vkms_config_connector_get_type(connector->config);
+ }
+
+ return sprintf(page, "%u", type);
+}
+
+static ssize_t connector_type_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ int val, ret;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ ret = kstrtouint(page, 10, &val);
+
+ if (ret)
+ return ret;
+
+ switch (val) {
+ case DRM_MODE_CONNECTOR_Unknown:
+ case DRM_MODE_CONNECTOR_VGA:
+ case DRM_MODE_CONNECTOR_DVII:
+ case DRM_MODE_CONNECTOR_DVID:
+ case DRM_MODE_CONNECTOR_DVIA:
+ case DRM_MODE_CONNECTOR_Composite:
+ case DRM_MODE_CONNECTOR_SVIDEO:
+ case DRM_MODE_CONNECTOR_LVDS:
+ case DRM_MODE_CONNECTOR_Component:
+ case DRM_MODE_CONNECTOR_9PinDIN:
+ case DRM_MODE_CONNECTOR_DisplayPort:
+ case DRM_MODE_CONNECTOR_HDMIA:
+ case DRM_MODE_CONNECTOR_HDMIB:
+ case DRM_MODE_CONNECTOR_TV:
+ case DRM_MODE_CONNECTOR_eDP:
+ case DRM_MODE_CONNECTOR_VIRTUAL:
+ case DRM_MODE_CONNECTOR_DSI:
+ case DRM_MODE_CONNECTOR_DPI:
+ case DRM_MODE_CONNECTOR_SPI:
+ case DRM_MODE_CONNECTOR_USB:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ if (connector->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_connector_set_type(connector->config, val);
+ }
+
+ return count;
+}
+
CONFIGFS_ATTR(connector_, status);
+CONFIGFS_ATTR(connector_, type);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
+ &connector_attr_type,
NULL,
};
--
2.54.0
^ permalink raw reply related
* [PATCH v5 29/38] drm/vkms: Rename vkms_connector_init to vkms_connector_init_static
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
With the introduction of dynamic connectors we will have two way of
initializing a VKMS connector. Rename the current function to clarify
the case.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/vkms_connector.c | 4 ++--
drivers/gpu/drm/vkms/vkms_connector.h | 6 +++---
drivers/gpu/drm/vkms/vkms_output.c | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index 5a87dc2d4c63..5b29dcade7e1 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -68,8 +68,8 @@ static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.best_encoder = vkms_conn_best_encoder,
};
-struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev,
- struct vkms_config_connector *connector_cfg)
+struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg)
{
struct drm_device *dev = &vkmsdev->drm;
struct vkms_connector *connector;
diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h
index 33253d29b1cc..85f9082c710e 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.h
+++ b/drivers/gpu/drm/vkms/vkms_connector.h
@@ -19,14 +19,14 @@ struct vkms_connector {
};
/**
- * vkms_connector_init() - Initialize a connector
+ * vkms_connector_init_static() - Initialize a connector
* @vkmsdev: VKMS device containing the connector
*
* Returns:
* The connector or an error on failure.
*/
-struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev,
- struct vkms_config_connector *connector_cfg);
+struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg);
/**
* vkms_trigger_connector_hotplug() - Update the device's connectors status
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 1288c8551bc8..406a22f1b963 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -97,7 +97,7 @@ int vkms_output_init(struct vkms_device *vkmsdev)
struct vkms_config_encoder *possible_encoder;
unsigned long idx = 0;
- connector_cfg->connector = vkms_connector_init(vkmsdev, connector_cfg);
+ connector_cfg->connector = vkms_connector_init_static(vkmsdev, connector_cfg);
if (IS_ERR(connector_cfg->connector)) {
DRM_ERROR("Failed to init connector\n");
return PTR_ERR(connector_cfg->connector);
--
2.54.0
^ permalink raw reply related
* [PATCH v5 30/38] drm/vkms: Introduce config for connector supported colorspace
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
To emulate some HDR features of displays, it is required to expose some
properties on HDMI, eDP and DP connectors.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 3 ++
drivers/gpu/drm/vkms/vkms_config.c | 5 +++
drivers/gpu/drm/vkms/vkms_config.h | 30 +++++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.c | 47 +++++++++++++++++++++++++++
4 files changed, 85 insertions(+)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index 1900cc79db2b..a31fd230c1d4 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -215,6 +215,9 @@ static void vkms_config_test_default_config(struct kunit *test)
vkms_config_for_each_connector(config, connector_cfg) {
KUNIT_EXPECT_EQ(test, vkms_config_connector_get_type(connector_cfg),
DRM_MODE_CONNECTOR_VIRTUAL);
+ KUNIT_EXPECT_EQ(test,
+ vkms_config_connector_get_supported_colorspaces(connector_cfg),
+ 0);
}
KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index c407aea0bc6a..ea9bc5d06be6 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -600,6 +600,10 @@ static int vkms_config_show(struct seq_file *m, void *data)
drm_get_connector_status_name(vkms_config_connector_get_status(connector_cfg)));
seq_printf(m, "\ttype=%s\n",
drm_get_connector_type_name(vkms_config_connector_get_type(connector_cfg)));
+ seq_puts(m, "\tsupported colorspaces=");
+ show_bitfield(m, vkms_config_connector_get_supported_colorspaces(connector_cfg),
+ drm_get_colorspace_name);
+ seq_puts(m, "\n");
}
return 0;
@@ -933,6 +937,7 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
connector_cfg->config = config;
connector_cfg->status = connector_status_connected;
vkms_config_connector_set_type(connector_cfg, DRM_MODE_CONNECTOR_VIRTUAL);
+ vkms_config_connector_set_supported_colorspaces(connector_cfg, 0);
xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);
list_add_tail(&connector_cfg->link, &config->connectors);
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index d6d185b2ccc0..80bbc13aad79 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -133,6 +133,10 @@ struct vkms_config_encoder {
* @type: Store the type of connector using DRM_MODE_CONNECTOR_* values
* @config: The vkms_config this connector belongs to
* @status: Status (connected, disconnected...) of the connector
+ * @supported_colorspaces: Bitmask of all the supported colorspaces. Values
+ * are the sames as ones accepted by
+ * drm_mode_create_hdmi_colorspace_property() and
+ * drm_mode_create_dp_colorspace_property()
* @possible_encoders: Array of encoders that can be used with this connector
* @connector: Internal usage. This pointer should never be considered as valid.
* It can be used to store a temporary reference to a VKMS connector
@@ -145,6 +149,7 @@ struct vkms_config_connector {
int type;
enum drm_connector_status status;
+ u32 supported_colorspaces;
struct xarray possible_encoders;
/* Internal usage */
@@ -244,6 +249,31 @@ struct vkms_config *vkms_config_default_create(bool enable_cursor,
*/
void vkms_config_destroy(struct vkms_config *config);
+/**
+ * vkms_config_connector_set_supported_colorspaces() - Set the supported colorspaces for a connector
+ * @connector_cfg: Connector configuration to modify
+ * @supported_colorspaces: Bitmask of supported colorspaces (DRM_MODE_COLORIMETRY_*)
+ */
+static inline void
+vkms_config_connector_set_supported_colorspaces(struct vkms_config_connector *connector_cfg,
+ u32 supported_colorspaces)
+{
+ connector_cfg->supported_colorspaces = supported_colorspaces;
+}
+
+/**
+ * vkms_config_connector_get_supported_colorspaces() - Get the supported colorspaces for a connector
+ * @connector_cfg: Connector configuration to query
+ *
+ * Returns:
+ * Bitmask of supported colorspaces (DRM_MODE_COLORIMETRY_*)
+ */
+static inline u32
+vkms_config_connector_get_supported_colorspaces(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->supported_colorspaces;
+}
+
/**
* vkms_config_get_device_name() - Return the name of the device
* @config: Configuration to get the device name from
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index 5b29dcade7e1..a1f08167def1 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -68,6 +68,49 @@ static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.best_encoder = vkms_conn_best_encoder,
};
+
+/**
+ * vkms_connector_init - Common initialization for all vkms connectors
+ *
+ * @connector - Already allocated connector
+ * @connector_cfg - Configuration to apply
+ *
+ * Returns: 0 on success, errno on error;
+ */
+static int __must_check vkms_connector_init(struct vkms_connector *connector,
+ struct vkms_config_connector *connector_cfg)
+{
+ int ret = 0;
+
+ if (vkms_config_connector_get_supported_colorspaces(connector_cfg)) {
+ if (connector_cfg->type == DRM_MODE_CONNECTOR_HDMIA) {
+ ret = drm_mode_create_hdmi_colorspace_property(&connector->base,
+ vkms_config_connector_get_supported_colorspaces(connector_cfg));
+ if (ret)
+ return ret;
+ } else if (connector_cfg->type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_cfg->type == DRM_MODE_CONNECTOR_eDP) {
+ ret = drm_mode_create_dp_colorspace_property(&connector->base,
+ vkms_config_connector_get_supported_colorspaces(connector_cfg));
+ if (ret)
+ return ret;
+ }
+
+ if (connector_cfg->type == DRM_MODE_CONNECTOR_HDMIA ||
+ connector_cfg->type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_cfg->type == DRM_MODE_CONNECTOR_eDP) {
+ ret = drm_connector_attach_colorspace_property(&connector->base);
+ if (ret) {
+ drm_property_destroy(connector->base.dev, connector->base.colorspace_property);
+ return ret;
+ }
+ drm_connector_attach_hdr_output_metadata_property(&connector->base);
+ }
+ }
+
+ return 0;
+}
+
struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
struct vkms_config_connector *connector_cfg)
{
@@ -84,6 +127,10 @@ struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
if (ret)
return ERR_PTR(ret);
+ ret = vkms_connector_init(connector, connector_cfg);
+ if (ret)
+ return ERR_PTR(ret);
+
drm_connector_helper_add(&connector->base, &vkms_conn_helper_funcs);
return connector;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 31/38] drm/vkms: Introduce configfs for connector supported colorspace
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Add the attribute supported_colorspaces to configure the supported
colorspace of a connector. It will allows emulating some HDR features of
displays. This feature is only supported for HDMI, DP and eDP connectors.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
Documentation/ABI/testing/configfs-vkms | 8 +++++++
Documentation/gpu/vkms.rst | 7 +++++-
drivers/gpu/drm/vkms/vkms_configfs.c | 40 +++++++++++++++++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/configfs-vkms b/Documentation/ABI/testing/configfs-vkms
index 0e588a1145ff..86f5f0beec8d 100644
--- a/Documentation/ABI/testing/configfs-vkms
+++ b/Documentation/ABI/testing/configfs-vkms
@@ -40,6 +40,14 @@ Description:
Type of the connector. Possible values match those
exposed by the "type" field in drm_connector.
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/supported_colorspaces
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Supported colorspaces for HDMI, DP, and eDP connectors.
+ Value is a bitfield, where 0x1 = NO_DATA, 0x2 = SMPTE_170M_YCC,
+ etc. See enum drm_colorspace for full list.
+
What: /sys/kernel/config/vkms/<device>/connectors/<connector>/possible_encoders
Date: Nov 2025
Contact: dri-devel@lists.freedesktop.org
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index f2622b3fd8af..11058b6e6250 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -137,11 +137,16 @@ Last but not least, create one or more connectors::
sudo mkdir /config/vkms/my-vkms/connectors/connector0
-Connectors have 2 configurable attribute:
+Connectors have 3 configurable attribute:
- status: Connection status: 1 connected, 2 disconnected, 3 unknown (same values
as those exposed by the "status" property of a connector)
- type: Type of connector. Same values as exposed by the "type" field in drm_connector.
+- supported_colorspaces: Supported colorspaces values for HDMI, DP and eDP connectors.
+ If supported_colorspaces is not 0, the HDR_OUTPUT_METADATA will also be created.
+ Value is a bitfield, 0x1 = NO_DATA, 0x2 = SMPTE_170M_YCC... see enum drm_colorspace
+ for full list.
+
To finish the configuration, link the different pipeline items::
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index 8b414f1950e8..e1f6e7817f97 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -1180,12 +1180,52 @@ static ssize_t connector_type_store(struct config_item *item,
return count;
}
+static ssize_t connector_supported_colorspaces_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ int colorspaces;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ colorspaces = vkms_config_connector_get_supported_colorspaces(connector->config);
+
+ return sprintf(page, "%u", colorspaces);
+}
+
+static ssize_t connector_supported_colorspaces_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ int val, ret;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ ret = kstrtouint(page, 10, &val);
+ if (ret)
+ return ret;
+
+ if ((val & -BIT(DRM_MODE_COLORIMETRY_COUNT)) != 0)
+ return -EINVAL;
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ if (connector->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_connector_set_supported_colorspaces(connector->config, val);
+ }
+
+ return count;
+}
+
CONFIGFS_ATTR(connector_, status);
CONFIGFS_ATTR(connector_, type);
+CONFIGFS_ATTR(connector_, supported_colorspaces);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
&connector_attr_type,
+ &connector_attr_supported_colorspaces,
NULL,
};
--
2.54.0
^ permalink raw reply related
* [PATCH v5 32/38] drm/vkms: Introduce config for connector EDID
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Allows configuration of EDID for each connector.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 2 +
drivers/gpu/drm/vkms/vkms_config.c | 6 +++
drivers/gpu/drm/vkms/vkms_config.h | 75 +++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.c | 50 ++++++++++++++++--
4 files changed, 129 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index a31fd230c1d4..2b49745ee026 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -218,6 +218,8 @@ static void vkms_config_test_default_config(struct kunit *test)
KUNIT_EXPECT_EQ(test,
vkms_config_connector_get_supported_colorspaces(connector_cfg),
0);
+ KUNIT_EXPECT_EQ(test, vkms_config_connector_get_edid_enabled(connector_cfg),
+ false);
}
KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index ea9bc5d06be6..1a110b8aa114 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -538,6 +538,7 @@ static int vkms_config_show(struct seq_file *m, void *data)
struct drm_device *dev = entry->dev;
struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
const char *dev_name;
+ int edid_len;
struct vkms_config_plane *plane_cfg;
struct vkms_config_crtc *crtc_cfg;
struct vkms_config_encoder *encoder_cfg;
@@ -604,6 +605,11 @@ static int vkms_config_show(struct seq_file *m, void *data)
show_bitfield(m, vkms_config_connector_get_supported_colorspaces(connector_cfg),
drm_get_colorspace_name);
seq_puts(m, "\n");
+ vkms_config_connector_get_edid(connector_cfg, &edid_len);
+ seq_printf(m, "\tEDID=%s (len=%d)\n",
+ str_enabled_disabled(vkms_config_connector_get_edid_enabled(connector_cfg)),
+ edid_len
+ );
}
return 0;
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 80bbc13aad79..7e361b6e45b0 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -137,6 +137,9 @@ struct vkms_config_encoder {
* are the sames as ones accepted by
* drm_mode_create_hdmi_colorspace_property() and
* drm_mode_create_dp_colorspace_property()
+ * @edid_enabled: If true, create the EDID property
+ * @edid: Stores the current EDID. The value will be ignored if @edid_enabled is false
+ * @edid_len: Current EDID length. The value will be ignored if @edid_enabled is false
* @possible_encoders: Array of encoders that can be used with this connector
* @connector: Internal usage. This pointer should never be considered as valid.
* It can be used to store a temporary reference to a VKMS connector
@@ -150,6 +153,9 @@ struct vkms_config_connector {
int type;
enum drm_connector_status status;
u32 supported_colorspaces;
+ bool edid_enabled;
+ u8 *edid;
+ unsigned int edid_len;
struct xarray possible_encoders;
/* Internal usage */
@@ -274,6 +280,75 @@ vkms_config_connector_get_supported_colorspaces(const struct vkms_config_connect
return connector_cfg->supported_colorspaces;
}
+/**
+ * vkms_config_connector_get_edid_enabled() - Check if EDID is enabled for a connector
+ * @connector_cfg: Connector configuration to check
+ *
+ * Returns:
+ * True if EDID is enabled for this connector, false otherwise.
+ */
+static inline bool
+vkms_config_connector_get_edid_enabled(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->edid_enabled;
+}
+
+/**
+ * vkms_config_connector_set_edid_enabled() - Enable or disable EDID for a connector
+ * @connector_cfg: Connector configuration to modify
+ * @enabled: Whether to enable EDID for this connector
+ */
+static inline void
+vkms_config_connector_set_edid_enabled(struct vkms_config_connector *connector_cfg,
+ bool enabled)
+{
+ connector_cfg->edid_enabled = enabled;
+}
+
+/**
+ * vkms_config_connector_get_edid() - Get the EDID data for a connector
+ * @connector_cfg: Connector configuration to get the EDID from
+ * @len: Pointer to store the length of the EDID data
+ *
+ * Returns:
+ * Pointer to the EDID data buffer, or NULL if no EDID is set.
+ * The length of the EDID data is stored in @len.
+ */
+static inline const u8 *
+vkms_config_connector_get_edid(const struct vkms_config_connector *connector_cfg, int *len)
+{
+ *len = connector_cfg->edid_len;
+ return connector_cfg->edid;
+}
+
+/**
+ * vkms_config_connector_set_edid() - Set the EDID data for a connector
+ * @connector_cfg: Connector configuration to modify
+ * @edid: Pointer to the EDID data buffer
+ * @len: Length of the EDID data
+ *
+ * If @len is 0, the EDID data will be cleared and @edid ignored. If
+ * memory allocation fails, the existing EDID data will be preserved.
+ */
+static inline void
+vkms_config_connector_set_edid(struct vkms_config_connector *connector_cfg,
+ const u8 *edid, unsigned int len)
+{
+ if (len) {
+ void *edid_tmp = krealloc(connector_cfg->edid, len, GFP_KERNEL);
+
+ if (edid_tmp) {
+ connector_cfg->edid = edid_tmp;
+ memcpy(connector_cfg->edid, edid, len);
+ connector_cfg->edid_len = len;
+ }
+ } else {
+ kfree(connector_cfg->edid);
+ connector_cfg->edid = NULL;
+ connector_cfg->edid_len = len;
+ }
+}
+
/**
* vkms_config_get_device_name() - Return the name of the device
* @config: Configuration to get the device name from
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index a1f08167def1..fe42ab2ba7b1 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -42,13 +42,55 @@ static const struct drm_connector_funcs vkms_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
+static int vkms_connector_read_block(void *context, u8 *buf, unsigned int block, size_t len)
+{
+ struct vkms_config_connector *config = context;
+ unsigned int edid_len;
+ const u8 *edid = vkms_config_connector_get_edid(config, &edid_len);
+
+ if (block * len + len > edid_len)
+ return 1;
+ memcpy(buf, &edid[block * len], len);
+ return 0;
+}
+
static int vkms_conn_get_modes(struct drm_connector *connector)
{
- int count;
+ struct vkms_connector *vkms_connector = drm_connector_to_vkms_connector(connector);
+ const struct drm_edid *drm_edid = NULL;
+ int count = 0;
+ struct vkms_config_connector *context = NULL;
+ struct drm_device *dev = connector->dev;
+ struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
+ struct vkms_config_connector *connector_cfg;
- /* Use the default modes list from DRM */
- count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX);
- drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF);
+ vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
+ if (connector_cfg->connector == vkms_connector) {
+ context = connector_cfg;
+ break;
+ }
+ }
+ if (context) {
+ if (vkms_config_connector_get_edid_enabled(context)) {
+ drm_edid = drm_edid_read_custom(connector,
+ vkms_connector_read_block, context);
+
+ /*
+ * Unconditionally update the connector. If the EDID was read
+ * successfully, fill in the connector information derived from the
+ * EDID. Otherwise, if the EDID is NULL, clear the connector
+ * information.
+ */
+ drm_edid_connector_update(connector, drm_edid);
+
+ count = drm_edid_connector_add_modes(connector);
+
+ drm_edid_free(drm_edid);
+ } else {
+ count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX);
+ drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF);
+ }
+ }
return count;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 33/38] drm/vkms: Introduce configfs for connector EDID
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Introduce new attributes to configure EDID of a connector:
- edid_enable - chose if the connector will have an EDID or not
- edid - raw edid content
Due to limitation of ConfigFS, the max len of EDID is PAGE_SIZE (4kB on
x86), it should be sufficient for many tests. One possible evolution is
using a ConfigFS blob to allow bigger EDID.
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
Documentation/ABI/testing/configfs-vkms | 14 ++++++
Documentation/gpu/vkms.rst | 5 ++-
drivers/gpu/drm/vkms/vkms_configfs.c | 80 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.c | 3 ++
4 files changed, 101 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/configfs-vkms b/Documentation/ABI/testing/configfs-vkms
index 86f5f0beec8d..04e2f89e3129 100644
--- a/Documentation/ABI/testing/configfs-vkms
+++ b/Documentation/ABI/testing/configfs-vkms
@@ -48,6 +48,20 @@ Description:
Value is a bitfield, where 0x1 = NO_DATA, 0x2 = SMPTE_170M_YCC,
etc. See enum drm_colorspace for full list.
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/edid_enabled
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Enable or disable EDID for this connector. Value:
+ 1 - enabled, 0 - disabled.
+
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/edid
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Content of the EDID for this connector. Ignored if
+ edid_enabled is not set.
+
What: /sys/kernel/config/vkms/<device>/connectors/<connector>/possible_encoders
Date: Nov 2025
Contact: dri-devel@lists.freedesktop.org
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index 11058b6e6250..2a3e175bf67b 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -137,7 +137,7 @@ Last but not least, create one or more connectors::
sudo mkdir /config/vkms/my-vkms/connectors/connector0
-Connectors have 3 configurable attribute:
+Connectors have 5 configurable attribute:
- status: Connection status: 1 connected, 2 disconnected, 3 unknown (same values
as those exposed by the "status" property of a connector)
@@ -146,6 +146,9 @@ Connectors have 3 configurable attribute:
If supported_colorspaces is not 0, the HDR_OUTPUT_METADATA will also be created.
Value is a bitfield, 0x1 = NO_DATA, 0x2 = SMPTE_170M_YCC... see enum drm_colorspace
for full list.
+- edid_enabled: Enable or not EDID for this connector. Some connectors may not have an
+ EDID but just a list of modes, this attribute allows to disable EDID property.
+- edid: Content of the EDID. Ignored if edid_enabled is not set
To finish the configuration, link the different pipeline items::
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index e1f6e7817f97..f79a796c2017 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -1218,14 +1218,94 @@ static ssize_t connector_supported_colorspaces_store(struct config_item *item,
return count;
}
+static ssize_t connector_edid_enabled_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ bool enabled;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ enabled = vkms_config_connector_get_edid_enabled(connector->config);
+
+ return sprintf(page, "%d\n", enabled);
+}
+
+static ssize_t connector_edid_enabled_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
+ bool enabled;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+ connector_cfg = connector->config;
+
+ if (kstrtobool(page, &enabled))
+ return -EINVAL;
+
+ scoped_guard(mutex, &connector->dev->lock)
+ {
+ vkms_config_connector_set_edid_enabled(connector_cfg, enabled);
+
+ if (connector->dev->enabled &&
+ vkms_config_connector_get_status(connector_cfg) !=
+ connector_status_disconnected)
+ vkms_trigger_connector_hotplug(connector->dev->config->dev);
+ }
+ return count;
+}
+
+static ssize_t connector_edid_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ {
+ unsigned int len = 0;
+ const u8 *edid = vkms_config_connector_get_edid(connector->config, &len);
+
+ memcpy(page, edid, min(len, PAGE_SIZE));
+ return min(len, PAGE_SIZE);
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t connector_edid_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ {
+ vkms_config_connector_set_edid(connector->config, page, count);
+
+ if (connector->dev->enabled &&
+ vkms_config_connector_get_status(connector->config) !=
+ connector_status_disconnected)
+ vkms_trigger_connector_hotplug(connector->dev->config->dev);
+ }
+
+ return count;
+}
+
CONFIGFS_ATTR(connector_, status);
CONFIGFS_ATTR(connector_, type);
CONFIGFS_ATTR(connector_, supported_colorspaces);
+CONFIGFS_ATTR(connector_, edid_enabled);
+CONFIGFS_ATTR(connector_, edid);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
&connector_attr_type,
&connector_attr_supported_colorspaces,
+ &connector_attr_edid_enabled,
+ &connector_attr_edid,
NULL,
};
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index fe42ab2ba7b1..f726c7995524 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -175,6 +175,9 @@ struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
drm_connector_helper_add(&connector->base, &vkms_conn_helper_funcs);
+ if (vkms_config_connector_get_edid_enabled(connector_cfg))
+ drm_connector_attach_edid_property(&connector->base);
+
return connector;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 34/38] drm/vkms: Store the enabled/disabled status for connector
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
In order to prepare for dynamic connector configuration, we need to store
if a connector is dynamic and if it is enabled.
The two new vkms_config_connector fields will helps for that.
Co-developed-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
.clang-format | 2 +
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 4 ++
drivers/gpu/drm/vkms/vkms_config.c | 6 +++
drivers/gpu/drm/vkms/vkms_config.h | 66 +++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_output.c | 2 +-
5 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/.clang-format b/.clang-format
index 1cc151e2adcc..0d985d30efb2 100644
--- a/.clang-format
+++ b/.clang-format
@@ -724,6 +724,8 @@ ForEachMacros:
- 'v4l2_m2m_for_each_src_buf_safe'
- 'virtio_device_for_each_vq'
- 'vkms_config_for_each_connector'
+ - 'vkms_config_for_each_connector_static'
+ - 'vkms_config_for_each_connector_dynamic'
- 'vkms_config_for_each_crtc'
- 'vkms_config_for_each_encoder'
- 'vkms_config_for_each_plane'
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index 2b49745ee026..fda23f05a0a5 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -220,6 +220,10 @@ static void vkms_config_test_default_config(struct kunit *test)
0);
KUNIT_EXPECT_EQ(test, vkms_config_connector_get_edid_enabled(connector_cfg),
false);
+ KUNIT_EXPECT_EQ(test, vkms_config_connector_is_enabled(connector_cfg),
+ true);
+ KUNIT_EXPECT_EQ(test, vkms_config_connector_is_dynamic(connector_cfg),
+ false);
}
KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index 1a110b8aa114..98e40c6a2c5b 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -597,6 +597,8 @@ static int vkms_config_show(struct seq_file *m, void *data)
vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
seq_puts(m, "connector:\n");
+ seq_printf(m, "\t%s\n",
+ str_enabled_disabled(vkms_config_connector_is_enabled(connector_cfg)));
seq_printf(m, "\tstatus=%s\n",
drm_get_connector_status_name(vkms_config_connector_get_status(connector_cfg)));
seq_printf(m, "\ttype=%s\n",
@@ -610,6 +612,8 @@ static int vkms_config_show(struct seq_file *m, void *data)
str_enabled_disabled(vkms_config_connector_get_edid_enabled(connector_cfg)),
edid_len
);
+ seq_printf(m, "\tdynamic=%s\n",
+ str_true_false(vkms_config_connector_is_dynamic(connector_cfg)));
}
return 0;
@@ -944,6 +948,8 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
connector_cfg->status = connector_status_connected;
vkms_config_connector_set_type(connector_cfg, DRM_MODE_CONNECTOR_VIRTUAL);
vkms_config_connector_set_supported_colorspaces(connector_cfg, 0);
+ vkms_config_connector_set_dynamic(connector_cfg, false);
+ vkms_config_connector_set_enabled(connector_cfg, true);
xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);
list_add_tail(&connector_cfg->link, &config->connectors);
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 7e361b6e45b0..071887a65380 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -132,6 +132,8 @@ struct vkms_config_encoder {
* @link: Link to the others connector in vkms_config
* @type: Store the type of connector using DRM_MODE_CONNECTOR_* values
* @config: The vkms_config this connector belongs to
+ * @dynamic: Store if a connector should be created with drm_connector_dynamic_init
+ * @enabled: If @dynamic, this means that the connector is currently registered in drm
* @status: Status (connected, disconnected...) of the connector
* @supported_colorspaces: Bitmask of all the supported colorspaces. Values
* are the sames as ones accepted by
@@ -151,6 +153,8 @@ struct vkms_config_connector {
struct vkms_config *config;
int type;
+ bool enabled;
+ bool dynamic;
enum drm_connector_status status;
u32 supported_colorspaces;
bool edid_enabled;
@@ -194,6 +198,24 @@ struct vkms_config_connector {
#define vkms_config_for_each_connector(config, connector_cfg) \
list_for_each_entry((connector_cfg), &(config)->connectors, link)
+/**
+ * vkms_config_for_each_connector_static - Iterate over the static vkms_config connectors
+ * @config: &struct vkms_config pointer
+ * @connector_cfg: &struct vkms_config_connector pointer used as cursor
+ */
+#define vkms_config_for_each_connector_static(config, connector_cfg) \
+ vkms_config_for_each_connector((config), (connector_cfg)) \
+ if (!(connector_cfg)->dynamic)
+
+/**
+ * vkms_config_for_each_connector_dynamic - Iterate over the dynamic vkms_config connectors
+ * @config: &struct vkms_config pointer
+ * @connector_cfg: &struct vkms_config_connector pointer used as cursor
+ */
+#define vkms_config_for_each_connector_dynamic(config, connector_cfg) \
+ vkms_config_for_each_connector((config), (connector_cfg)) \
+ if ((connector_cfg)->dynamic)
+
/**
* vkms_config_plane_for_each_possible_crtc - Iterate over the vkms_config_plane
* possible CRTCs
@@ -472,6 +494,50 @@ vkms_config_connector_set_type(struct vkms_config_connector *connector_cfg,
connector_cfg->type = type;
}
+/**
+ * vkms_config_connector_set_enabled() - If the connector is part of the device
+ * @crtc_cfg: Target connector
+ * @enabled: Add or remove the connector
+ */
+static inline void
+vkms_config_connector_set_enabled(struct vkms_config_connector *connector_cfg,
+ bool enabled)
+{
+ connector_cfg->enabled = enabled;
+}
+
+/**
+ * vkms_config_connector_is_enabled() - If the connector is part of the device
+ * @connector_cfg: The connector
+ */
+static inline bool
+vkms_config_connector_is_enabled(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->enabled;
+}
+
+/**
+ * vkms_config_connector_set_dynamic() - If the connector is dynamic
+ * @crtc_cfg: Target connector
+ * @enabled: Enable or disable the dynamic status
+ */
+static inline void
+vkms_config_connector_set_dynamic(struct vkms_config_connector *connector_cfg,
+ bool dynamic)
+{
+ connector_cfg->dynamic = dynamic;
+}
+
+/**
+ * vkms_config_connector_is_enabled() - If the connector is dynamic
+ * @connector_cfg: The connector
+ */
+static inline bool
+vkms_config_connector_is_dynamic(struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->dynamic;
+}
+
/*
* vkms_config_plane_get_default_rotation() - Get the default rotation for a plane
* @plane_cfg: Plane to get the default rotation from
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 406a22f1b963..13c4ca5fd39d 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -93,7 +93,7 @@ int vkms_output_init(struct vkms_device *vkmsdev)
}
}
- vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
+ vkms_config_for_each_connector_static(vkmsdev->config, connector_cfg) {
struct vkms_config_encoder *possible_encoder;
unsigned long idx = 0;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 35/38] drm/vkms: Allow to hot-add connectors
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
In order to allow creating dynamic connector, add the required
infrastructure in vkms_connector.
[Louis Chauvet: use drm_atomic_helper_connector_reset instead of
drm_mode_config_reset because connector is not yet registered]
Co-developed-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/vkms_connector.c | 89 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/vkms/vkms_connector.h | 33 +++++++++++++
drivers/gpu/drm/vkms/vkms_output.c | 9 ++++
3 files changed, 131 insertions(+)
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index f726c7995524..a013b53bd915 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -181,9 +181,98 @@ struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
return connector;
}
+static void vkms_connector_dynamic_destroy(struct drm_connector *connector)
+{
+ struct vkms_connector *vkms_connector;
+
+ drm_connector_cleanup(connector);
+
+ vkms_connector = drm_connector_to_vkms_connector(connector);
+ kfree(vkms_connector);
+}
+
+static const struct drm_connector_funcs vkms_dynamic_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .destroy = vkms_connector_dynamic_destroy,
+ .detect = vkms_connector_detect,
+};
+
void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
drm_kms_helper_hotplug_event(dev);
}
+
+struct vkms_connector *vkms_connector_hot_add(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg)
+{
+ struct vkms_config_encoder *encoder_cfg;
+ struct vkms_connector __free(kfree) * connector = NULL;
+ int ret;
+ unsigned long idx = 0;
+
+ connector = kzalloc_obj(*connector, GFP_KERNEL);
+ if (IS_ERR(connector))
+ return connector;
+ ret = drm_connector_dynamic_init(&vkmsdev->drm,
+ &connector->base,
+ &vkms_dynamic_connector_funcs,
+ connector_cfg->type,
+ NULL);
+ if (ret)
+ return ERR_PTR(ret);
+ drm_connector_helper_add(&connector->base, &vkms_conn_helper_funcs);
+
+ vkms_config_connector_for_each_possible_encoder(connector_cfg, idx, encoder_cfg) {
+ ret = drm_connector_attach_encoder(&connector->base,
+ encoder_cfg->encoder);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ drm_atomic_helper_connector_reset(&connector->base);
+
+ ret = vkms_connector_init(connector, connector_cfg);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = drm_connector_dynamic_register(&connector->base);
+ if (ret) {
+ if (connector_cfg->type == DRM_MODE_CONNECTOR_HDMIA ||
+ connector_cfg->type == DRM_MODE_CONNECTOR_DisplayPort ||
+ connector_cfg->type == DRM_MODE_CONNECTOR_eDP) {
+ drm_property_destroy(connector->base.dev,
+ connector->base.colorspace_property);
+ }
+ return ERR_PTR(ret);
+ }
+
+ return_ptr(connector);
+}
+
+void vkms_connector_hot_remove(struct vkms_device *vkmsdev,
+ struct vkms_connector *connector)
+{
+ drm_connector_unregister(&connector->base);
+ drm_mode_config_reset(&vkmsdev->drm);
+ drm_connector_put(&connector->base);
+}
+
+int vkms_connector_hot_attach_encoder(struct vkms_device *vkmsdev,
+ struct vkms_connector *connector,
+ struct drm_encoder *encoder)
+{
+ int ret;
+
+ ret = drm_connector_attach_encoder(&connector->base, encoder);
+ if (ret)
+ return ret;
+
+ drm_mode_config_reset(&vkmsdev->drm);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h
index 85f9082c710e..a235a518d5a0 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.h
+++ b/drivers/gpu/drm/vkms/vkms_connector.h
@@ -34,4 +34,37 @@ struct vkms_connector *vkms_connector_init_static(struct vkms_device *vkmsdev,
*/
void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev);
+/**
+ * vkms_connector_hot_add() - Create a connector after the device is created
+ * @vkmsdev: Device to hot-add the connector to
+ * @connector_cfg: Connector's configuration
+ *
+ * Returns:
+ * A pointer to the newly created connector or a PTR_ERR on failure.
+ */
+struct vkms_connector *vkms_connector_hot_add(struct vkms_device *vkmsdev,
+ struct vkms_config_connector *connector_cfg);
+
+/**
+ * vkms_connector_hot_remove() - Remove a connector after a device is created
+ * @vkmsdev: Device to containing the connector to be removed
+ * @connector: The connector to hot-remove
+ */
+void vkms_connector_hot_remove(struct vkms_device *vkmsdev,
+ struct vkms_connector *connector);
+
+/**
+ * vkms_connector_hot_attach_encoder() - Attach a connector to a encoder after
+ * the device is created.
+ * @vkmsdev: Device containing the connector and the encoder
+ * @connector: Connector to attach to @encoder
+ * @encoder: Target encoder
+ *
+ * Returns:
+ * 0 on success or an error on failure.
+ */
+int vkms_connector_hot_attach_encoder(struct vkms_device *vkmsdev,
+ struct vkms_connector *connector,
+ struct drm_encoder *encoder);
+
#endif /* _VKMS_CONNECTOR_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 13c4ca5fd39d..50f7d88dee8b 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -117,5 +117,14 @@ int vkms_output_init(struct vkms_device *vkmsdev)
drm_mode_config_reset(dev);
+ vkms_config_for_each_connector_dynamic(vkmsdev->config, connector_cfg) {
+ if (connector_cfg->enabled) {
+ connector_cfg->connector = vkms_connector_hot_add(vkmsdev, connector_cfg);
+
+ if (IS_ERR(connector_cfg->connector))
+ return PTR_ERR(connector_cfg->connector);
+ }
+ }
+
return 0;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 36/38] drm/vkms: Introduce configfs for dynamic connector creation
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
DRM allows the connector to be created after the device. To allows
emulating this, add two configfs attributes to connector to allows this.
Using the dynamic attribute you can set if a connector will be dynamic or
not.
Using the enabled attribute, you can set at runtime if a dynamic connector
is present or not.
Co-developed-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
Documentation/ABI/testing/configfs-vkms | 14 ++++
Documentation/gpu/vkms.rst | 6 +-
drivers/gpu/drm/vkms/vkms_configfs.c | 144 ++++++++++++++++++++++++++++++--
3 files changed, 155 insertions(+), 9 deletions(-)
diff --git a/Documentation/ABI/testing/configfs-vkms b/Documentation/ABI/testing/configfs-vkms
index 04e2f89e3129..8a5899c52e78 100644
--- a/Documentation/ABI/testing/configfs-vkms
+++ b/Documentation/ABI/testing/configfs-vkms
@@ -62,6 +62,20 @@ Description:
Content of the EDID for this connector. Ignored if
edid_enabled is not set.
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/dynamic
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ Set to 1 to create a dynamic connector (emulates DP MST).
+ Value: 1 - dynamic, 0 - static.
+
+What: /sys/kernel/config/vkms/<device>/connectors/<connector>/enabled
+Date: Nov 2025
+Contact: dri-devel@lists.freedesktop.org
+Description:
+ For dynamic connectors, set to 1 to create the connector,
+ 0 to remove it. Value: 1 - enabled, 0 - disabled.
+
What: /sys/kernel/config/vkms/<device>/connectors/<connector>/possible_encoders
Date: Nov 2025
Contact: dri-devel@lists.freedesktop.org
diff --git a/Documentation/gpu/vkms.rst b/Documentation/gpu/vkms.rst
index 2a3e175bf67b..31569421d8f5 100644
--- a/Documentation/gpu/vkms.rst
+++ b/Documentation/gpu/vkms.rst
@@ -137,7 +137,7 @@ Last but not least, create one or more connectors::
sudo mkdir /config/vkms/my-vkms/connectors/connector0
-Connectors have 5 configurable attribute:
+Connectors have 7 configurable attribute:
- status: Connection status: 1 connected, 2 disconnected, 3 unknown (same values
as those exposed by the "status" property of a connector)
@@ -149,7 +149,9 @@ Connectors have 5 configurable attribute:
- edid_enabled: Enable or not EDID for this connector. Some connectors may not have an
EDID but just a list of modes, this attribute allows to disable EDID property.
- edid: Content of the EDID. Ignored if edid_enabled is not set
-
+- dynamic: Set to 1 while configuring the device to create a dynamic connector. A dynamic
+ connector can be used to emulate DP MST connectors.
+- enabled: For dynamic connector, set it to 1 to create the connector, 0 to remove it.
To finish the configuration, link the different pipeline items::
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index f79a796c2017..f92b172e75cf 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -1091,6 +1091,12 @@ static ssize_t connector_status_show(struct config_item *item, char *page)
return sprintf(page, "%u", status);
}
+static bool connector_is_enabled(struct vkms_config_connector *connector_cfg)
+{
+ return !connector_cfg->dynamic ||
+ (connector_cfg->dynamic && connector_cfg->enabled);
+}
+
static ssize_t connector_status_store(struct config_item *item,
const char *page, size_t count)
{
@@ -1110,7 +1116,7 @@ static ssize_t connector_status_store(struct config_item *item,
scoped_guard(mutex, &connector->dev->lock) {
vkms_config_connector_set_status(connector->config, status);
- if (connector->dev->enabled)
+ if (connector->dev->enabled && connector_is_enabled(connector->config))
vkms_trigger_connector_hotplug(connector->dev->config->dev);
}
@@ -1171,7 +1177,7 @@ static ssize_t connector_type_store(struct config_item *item,
}
scoped_guard(mutex, &connector->dev->lock) {
- if (connector->dev->enabled)
+ if (connector->dev->enabled && connector_is_enabled(connector->config))
return -EBUSY;
vkms_config_connector_set_type(connector->config, val);
@@ -1290,6 +1296,107 @@ static ssize_t connector_edid_store(struct config_item *item,
connector_status_disconnected)
vkms_trigger_connector_hotplug(connector->dev->config->dev);
}
+ return count;
+}
+
+static ssize_t connector_enabled_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ bool enabled;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ enabled = vkms_config_connector_is_enabled(connector->config);
+
+ return sprintf(page, "%d\n", enabled);
+}
+
+static ssize_t connector_enabled_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
+ bool enabled, was_enabled;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+ connector_cfg = connector->config;
+
+ if (kstrtobool(page, &enabled))
+ return -EINVAL;
+ scoped_guard(mutex, &connector->dev->lock) {
+ if (!connector->dev->enabled) {
+ vkms_config_connector_set_enabled(connector_cfg, enabled);
+ } else {
+ // Only dynamic connector can be enabled/disabled at runtime
+ if (!connector_cfg->dynamic)
+ return -EBUSY;
+
+ was_enabled = vkms_config_connector_is_enabled(connector_cfg);
+ vkms_config_connector_set_enabled(connector_cfg, enabled);
+
+ // Resulting configuration is invalid (missing encoder for example)
+ // Early return to avoid drm core issue
+ if (!vkms_config_is_valid(connector->dev->config)) {
+ count = -EINVAL;
+ goto rollback;
+ }
+
+ if (!was_enabled && enabled) {
+ // Adding the connector
+ connector_cfg->connector = vkms_connector_hot_add(connector->dev->config->dev,
+ connector_cfg);
+ if (IS_ERR(connector_cfg->connector)) {
+ count = PTR_ERR(connector_cfg->connector);
+ goto rollback;
+ }
+ } else if (was_enabled && !enabled) {
+ vkms_connector_hot_remove(connector->dev->config->dev,
+ connector_cfg->connector);
+ }
+ }
+ }
+ return count;
+
+rollback:
+ vkms_config_connector_set_enabled(connector_cfg, was_enabled);
+ return count;
+}
+
+static ssize_t connector_dynamic_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ bool enabled;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ enabled = vkms_config_connector_is_dynamic(connector->config);
+ }
+
+ return sprintf(page, "%d\n", enabled);
+}
+
+static ssize_t connector_dynamic_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
+ bool dynamic;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+ connector_cfg = connector->config;
+
+ if (kstrtobool(page, &dynamic))
+ return -EINVAL;
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ // Can't change the dynamic status when the device is activated
+ if (connector->dev->enabled)
+ return -EBUSY;
+
+ vkms_config_connector_set_dynamic(connector_cfg, dynamic);
+ }
return count;
}
@@ -1299,6 +1406,8 @@ CONFIGFS_ATTR(connector_, type);
CONFIGFS_ATTR(connector_, supported_colorspaces);
CONFIGFS_ATTR(connector_, edid_enabled);
CONFIGFS_ATTR(connector_, edid);
+CONFIGFS_ATTR(connector_, dynamic);
+CONFIGFS_ATTR(connector_, enabled);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
@@ -1306,19 +1415,28 @@ static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_supported_colorspaces,
&connector_attr_edid_enabled,
&connector_attr_edid,
+ &connector_attr_dynamic,
+ &connector_attr_enabled,
NULL,
};
static void connector_release(struct config_item *item)
{
struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
struct mutex *lock;
connector = connector_item_to_vkms_configfs_connector(item);
+ connector_cfg = connector->config;
lock = &connector->dev->lock;
scoped_guard(mutex, lock) {
+ if (connector->dev->enabled && connector_cfg->dynamic && connector_cfg->enabled)
+ vkms_connector_hot_remove(connector->dev->config->dev,
+ connector_cfg->connector);
+
vkms_config_destroy_connector(connector->config);
+
kfree(connector);
}
}
@@ -1337,6 +1455,7 @@ static int connector_possible_encoders_allow_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
struct vkms_configfs_encoder *encoder;
int ret;
@@ -1344,14 +1463,25 @@ static int connector_possible_encoders_allow_link(struct config_item *src,
return -EINVAL;
connector = connector_possible_encoders_item_to_vkms_configfs_connector(src);
+ connector_cfg = connector->config;
encoder = encoder_item_to_vkms_configfs_encoder(target);
scoped_guard(mutex, &connector->dev->lock) {
- if (connector->dev->enabled)
- return -EBUSY;
+ if (connector->dev->enabled && connector_cfg->enabled) {
+ if (!connector_cfg->dynamic)
+ return -EBUSY;
+
+ ret = vkms_connector_hot_attach_encoder(connector->dev->config->dev,
+ connector->config->connector,
+ encoder->config->encoder);
+ if (ret)
+ return ret;
+ }
ret = vkms_config_connector_attach_encoder(connector->config,
encoder->config);
+ if (ret)
+ return ret;
}
return ret;
@@ -1392,9 +1522,6 @@ static struct config_group *make_connector_group(struct config_group *group,
dev = child_group_to_vkms_configfs_device(group);
scoped_guard(mutex, &dev->lock) {
- if (dev->enabled)
- return ERR_PTR(-EBUSY);
-
connector = kzalloc(sizeof(*connector), GFP_KERNEL);
if (!connector)
return ERR_PTR(-ENOMEM);
@@ -1408,6 +1535,9 @@ static struct config_group *make_connector_group(struct config_group *group,
return ERR_PTR(ret);
}
+ vkms_config_connector_set_dynamic(connector->config, connector->dev->enabled);
+ vkms_config_connector_set_enabled(connector->config, !connector->dev->enabled);
+
config_group_init_type_name(&connector->group, name,
&connector_item_type);
--
2.54.0
^ permalink raw reply related
* [PATCH v5 37/38] drm/vkms: Add connector parent configuration in vkms_config
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
In order to fully emulate MST connector from the userspace point of view,
add a way to configure the PATH property.
Add two things:
- port_id, to configure the MST port id (used to generate the last
-<port-id> in the PATH property)
- parent, to configure the parent connector (used to
generate <connector-id>- or -<port-id>- in the PATH property)
In order to properly populate the PATH property, it should be done after
all the connector initialization to ensure that the <connector-id> is
known.
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/tests/vkms_config_test.c | 269 +++++++++++++++++++++++++-
drivers/gpu/drm/vkms/vkms_config.c | 50 ++++-
drivers/gpu/drm/vkms/vkms_config.h | 60 +++++-
drivers/gpu/drm/vkms/vkms_configfs.c | 2 +-
drivers/gpu/drm/vkms/vkms_connector.c | 74 +++++++
drivers/gpu/drm/vkms/vkms_connector.h | 9 +
drivers/gpu/drm/vkms/vkms_output.c | 2 +
7 files changed, 460 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_config_test.c b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
index fda23f05a0a5..a9700e265d42 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_config_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_config_test.c
@@ -396,7 +396,7 @@ static void vkms_config_test_get_connectors(struct kunit *test)
KUNIT_ASSERT_EQ(test, n_connectors, 2);
n_connectors = 0;
- vkms_config_destroy_connector(connector_cfg2);
+ vkms_config_destroy_connector(config, connector_cfg2);
vkms_config_for_each_connector(config, connector_cfg) {
n_connectors++;
if (connector_cfg != connector_cfg1)
@@ -845,7 +845,7 @@ static void vkms_config_test_invalid_connector_number(struct kunit *test)
/* Invalid: No connectors */
connector_cfg = get_first_connector(config);
- vkms_config_destroy_connector(connector_cfg);
+ vkms_config_destroy_connector(config, connector_cfg);
KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
/* Invalid: Too many connectors */
@@ -1233,6 +1233,267 @@ static void vkms_config_test_connector_status(struct kunit *test)
vkms_config_destroy(config);
}
+static void vkms_config_test_connector_dynamic_status(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_connector *connector_cfg;
+ struct vkms_config_encoder *encoder_cfg;
+ struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_plane *plane_cfg;
+ enum drm_connector_status status;
+ int err;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ /* Create a complete pipeline */
+ crtc_cfg = vkms_config_create_crtc(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg);
+
+ encoder_cfg = vkms_config_create_encoder(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg);
+
+ connector_cfg = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg);
+
+ plane_cfg = vkms_config_create_plane(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg);
+
+ vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
+ err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_connector_attach_encoder(connector_cfg, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ /* Test dynamic status changes */
+ status = vkms_config_connector_get_status(connector_cfg);
+ KUNIT_EXPECT_EQ(test, status, connector_status_connected);
+
+ vkms_config_connector_set_status(connector_cfg, connector_status_disconnected);
+ status = vkms_config_connector_get_status(connector_cfg);
+ KUNIT_EXPECT_EQ(test, status, connector_status_disconnected);
+
+ /* Configuration should still be valid regardless of connector status */
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ vkms_config_connector_set_status(connector_cfg, connector_status_connected);
+ status = vkms_config_connector_get_status(connector_cfg);
+ KUNIT_EXPECT_EQ(test, status, connector_status_connected);
+
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Test with unknown status */
+ vkms_config_connector_set_status(connector_cfg, connector_status_unknown);
+ status = vkms_config_connector_get_status(connector_cfg);
+ KUNIT_EXPECT_EQ(test, status, connector_status_unknown);
+
+ /* Configuration should still be valid */
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
+static void vkms_config_test_dynamic_connector_validity(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_connector *connector_cfg1, *connector_cfg2;
+ struct vkms_config_encoder *encoder_cfg;
+ struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_plane *plane_cfg;
+ int err;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ /* Create a complete pipeline */
+ crtc_cfg = vkms_config_create_crtc(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg);
+
+ encoder_cfg = vkms_config_create_encoder(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg);
+
+ connector_cfg1 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1);
+
+ plane_cfg = vkms_config_create_plane(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg);
+
+ vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
+ err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ /* Valid: Non-dynamic connector */
+ vkms_config_connector_set_dynamic(connector_cfg1, false);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Dynamic connector */
+ vkms_config_connector_set_dynamic(connector_cfg1, true);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Multiple dynamic connectors */
+ connector_cfg2 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2);
+ vkms_config_connector_set_dynamic(connector_cfg2, true);
+ err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
+static void vkms_config_test_dynamic_connector_parent_validity(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_connector *connector_cfg1, *connector_cfg2, *connector_cfg3;
+ struct vkms_config_encoder *encoder_cfg;
+ struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_plane *plane_cfg;
+ int err;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ /* Create a complete pipeline */
+ crtc_cfg = vkms_config_create_crtc(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg);
+
+ encoder_cfg = vkms_config_create_encoder(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg);
+
+ connector_cfg1 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1);
+
+ plane_cfg = vkms_config_create_plane(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg);
+
+ vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
+ err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ /* Valid: Non-dynamic connector with no parent */
+ vkms_config_connector_set_dynamic(connector_cfg1, false);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Dynamic connector with no parent */
+ vkms_config_connector_set_dynamic(connector_cfg1, true);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Dynamic connector with dynamic parent */
+ connector_cfg2 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2);
+ vkms_config_connector_set_dynamic(connector_cfg2, true);
+ err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ vkms_config_connector_attach_parent(connector_cfg2, connector_cfg1);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Dynamic connector with non-dynamic parent */
+ vkms_config_connector_set_dynamic(connector_cfg1, false);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Non-dynamic connector with no parent */
+ vkms_config_connector_attach_parent(connector_cfg2, NULL);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Multiple levels of dynamic parent-child relationships */
+ vkms_config_connector_set_dynamic(connector_cfg1, true);
+ vkms_config_connector_attach_parent(connector_cfg2, connector_cfg1);
+ connector_cfg3 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg3);
+ vkms_config_connector_set_dynamic(connector_cfg3, true);
+ err = vkms_config_connector_attach_encoder(connector_cfg3, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ vkms_config_connector_attach_parent(connector_cfg3, connector_cfg2);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
+static void vkms_config_test_dynamic_connector_parent_loop(struct kunit *test)
+{
+ struct vkms_config *config;
+ struct vkms_config_connector *connector_cfg1, *connector_cfg2, *connector_cfg3;
+ struct vkms_config_encoder *encoder_cfg;
+ struct vkms_config_crtc *crtc_cfg;
+ struct vkms_config_plane *plane_cfg;
+ int err;
+
+ config = vkms_config_create("test");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
+
+ /* Create a complete pipeline */
+ crtc_cfg = vkms_config_create_crtc(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_cfg);
+
+ encoder_cfg = vkms_config_create_encoder(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder_cfg);
+
+ connector_cfg1 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg1);
+
+ plane_cfg = vkms_config_create_plane(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_cfg);
+
+ vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
+ err = vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ err = vkms_config_connector_attach_encoder(connector_cfg1, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ /* Valid: Single dynamic connector with no parent */
+ vkms_config_connector_set_dynamic(connector_cfg1, true);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Valid: Two dynamic connectors in a chain */
+ connector_cfg2 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg2);
+ vkms_config_connector_set_dynamic(connector_cfg2, true);
+ err = vkms_config_connector_attach_encoder(connector_cfg2, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ vkms_config_connector_attach_parent(connector_cfg2, connector_cfg1);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Invalid: Create a loop - connector2 -> connector1 -> connector2 */
+ vkms_config_connector_attach_parent(connector_cfg1, connector_cfg2);
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ /* Fix the loop */
+ vkms_config_connector_attach_parent(connector_cfg1, NULL);
+ KUNIT_EXPECT_TRUE(test, vkms_config_is_valid(config));
+
+ /* Invalid: Create a longer loop - connector1 -> connector2 -> connector3 -> connector1 */
+ connector_cfg3 = vkms_config_create_connector(config);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg3);
+ vkms_config_connector_set_dynamic(connector_cfg3, true);
+ err = vkms_config_connector_attach_encoder(connector_cfg3, encoder_cfg);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ vkms_config_connector_attach_parent(connector_cfg3, connector_cfg2);
+ vkms_config_connector_attach_parent(connector_cfg1, connector_cfg3);
+ KUNIT_EXPECT_FALSE(test, vkms_config_is_valid(config));
+
+ vkms_config_destroy(config);
+}
+
static struct kunit_case vkms_config_test_cases[] = {
KUNIT_CASE(vkms_config_test_empty_config),
KUNIT_CASE_PARAM(vkms_config_test_default_config,
@@ -1259,6 +1520,10 @@ static struct kunit_case vkms_config_test_cases[] = {
KUNIT_CASE(vkms_config_test_encoder_get_possible_crtcs),
KUNIT_CASE(vkms_config_test_connector_get_possible_encoders),
KUNIT_CASE(vkms_config_test_connector_status),
+ KUNIT_CASE(vkms_config_test_connector_dynamic_status),
+ KUNIT_CASE(vkms_config_test_dynamic_connector_validity),
+ KUNIT_CASE(vkms_config_test_dynamic_connector_parent_validity),
+ KUNIT_CASE(vkms_config_test_dynamic_connector_parent_loop),
{}
};
diff --git a/drivers/gpu/drm/vkms/vkms_config.c b/drivers/gpu/drm/vkms/vkms_config.c
index 98e40c6a2c5b..b9d5a2e44fd9 100644
--- a/drivers/gpu/drm/vkms/vkms_config.c
+++ b/drivers/gpu/drm/vkms/vkms_config.c
@@ -170,7 +170,7 @@ void vkms_config_destroy(struct vkms_config *config)
vkms_config_destroy_encoder(config, encoder_cfg);
list_for_each_entry_safe(connector_cfg, connector_tmp, &config->connectors, link)
- vkms_config_destroy_connector(connector_cfg);
+ vkms_config_destroy_connector(config, connector_cfg);
kfree_const(config->dev_name);
kfree(config);
@@ -437,6 +437,41 @@ static bool valid_connector_possible_encoders(const struct vkms_config *config)
return true;
}
+static bool valid_connector_parents(const struct vkms_config *config)
+{
+ struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
+
+ struct vkms_config_connector *connector_cfg;
+ struct vkms_config_connector *connector_cfg_parent;
+
+ vkms_config_for_each_connector(config, connector_cfg) {
+ bool expected_status = connector_cfg->enabled;
+
+ vkms_config_for_each_connector(config, connector_cfg_parent)
+ connector_cfg_parent->visited = false;
+
+ connector_cfg_parent = connector_cfg;
+ while (connector_cfg_parent) {
+ if (connector_cfg_parent->visited) {
+ drm_info(dev, "Parents of connector should not form a loop\n");
+ return false;
+ }
+
+ if (expected_status && connector_cfg_parent->enabled != expected_status) {
+ drm_info(dev, "All parents of an enabled connector must be enabled\n");
+ return false;
+ }
+
+
+
+ connector_cfg_parent->visited = true;
+ connector_cfg_parent = connector_cfg_parent->parent;
+ }
+ }
+
+ return true;
+}
+
bool vkms_config_is_valid(const struct vkms_config *config)
{
struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
@@ -503,6 +538,9 @@ bool vkms_config_is_valid(const struct vkms_config *config)
if (!valid_connector_possible_encoders(config))
return false;
+ if (!valid_connector_parents(config))
+ return false;
+
return true;
}
EXPORT_SYMBOL_IF_KUNIT(vkms_config_is_valid);
@@ -958,8 +996,16 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
}
EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_connector);
-void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg)
+void vkms_config_destroy_connector(struct vkms_config *config,
+ struct vkms_config_connector *connector_cfg)
{
+ struct vkms_config_connector *connector_cfg_tmp;
+
+ vkms_config_for_each_connector(config, connector_cfg_tmp) {
+ if (connector_cfg_tmp->parent == connector_cfg)
+ connector_cfg_tmp->parent = NULL;
+ }
+
xa_destroy(&connector_cfg->possible_encoders);
list_del(&connector_cfg->link);
kfree(connector_cfg);
diff --git a/drivers/gpu/drm/vkms/vkms_config.h b/drivers/gpu/drm/vkms/vkms_config.h
index 071887a65380..2e6ddd2922c2 100644
--- a/drivers/gpu/drm/vkms/vkms_config.h
+++ b/drivers/gpu/drm/vkms/vkms_config.h
@@ -143,10 +143,14 @@ struct vkms_config_encoder {
* @edid: Stores the current EDID. The value will be ignored if @edid_enabled is false
* @edid_len: Current EDID length. The value will be ignored if @edid_enabled is false
* @possible_encoders: Array of encoders that can be used with this connector
+ * @parent: To emulate MST connector, this will contains a pointer to the parent
+ * @port_id: To emulate MST connector, this contains the physical port id. Used to
+ * generate the PATH property.
* @connector: Internal usage. This pointer should never be considered as valid.
* It can be used to store a temporary reference to a VKMS connector
* during device creation. This pointer is not managed by the
* configuration and must be managed by other means.
+ * @visited: Internal usage. This boolean is used to do some verification on the connectors.
*/
struct vkms_config_connector {
struct list_head link;
@@ -161,9 +165,12 @@ struct vkms_config_connector {
u8 *edid;
unsigned int edid_len;
struct xarray possible_encoders;
+ struct vkms_config_connector *parent;
+ u8 port_id;
/* Internal usage */
struct vkms_connector *connector;
+ bool visited;
};
/**
@@ -370,6 +377,56 @@ vkms_config_connector_set_edid(struct vkms_config_connector *connector_cfg,
connector_cfg->edid_len = len;
}
}
+/**
+ * vkms_config_connector_attach_parent - Attach a connector to a parent connector
+ * @connector_cfg: Connector to attach
+ * @parent: Parent connector to attach @connector_cfg to. Can be NULL to detach the parent.
+ */
+static inline void
+vkms_config_connector_attach_parent(struct vkms_config_connector *connector_cfg,
+ struct vkms_config_connector *parent)
+{
+ connector_cfg->parent = parent;
+}
+
+/**
+ * vkms_config_connector_get_parent - Get the parent connector of a connector
+ * @connector_cfg: Connector to get the parent from
+ *
+ * Returns:
+ * The parent connector of @connector_cfg or NULL if none is assigned yet.
+ */
+static inline struct vkms_config_connector*
+vkms_config_connector_get_parent(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->parent;
+}
+
+/**
+ * vkms_config_connector_get_port_id() - Get the port ID for a connector
+ * @connector_cfg: Connector configuration to query
+ *
+ * Returns:
+ * The port ID for this connector.
+ */
+static inline u8
+vkms_config_connector_get_port_id(const struct vkms_config_connector *connector_cfg)
+{
+ return connector_cfg->port_id;
+}
+
+/**
+ * vkms_config_connector_set_port_id() - Set the port ID for a connector
+ * @connector_cfg: Connector configuration to modify
+ * @port_id: New port ID for this connector
+ */
+static inline void
+vkms_config_connector_set_port_id(struct vkms_config_connector *connector_cfg,
+ u8 port_id)
+{
+ connector_cfg->port_id = port_id;
+}
+
/**
* vkms_config_get_device_name() - Return the name of the device
@@ -1027,7 +1084,8 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
* vkms_config_destroy_connector() - Remove and free a connector configuration
* @connector_cfg: Connector configuration to destroy
*/
-void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg);
+void vkms_config_destroy_connector(struct vkms_config *config,
+ struct vkms_config_connector *connector_cfg);
/**
* vkms_config_connector_attach_encoder - Attach a connector to an encoder
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index f92b172e75cf..dd2a9e450150 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -1435,7 +1435,7 @@ static void connector_release(struct config_item *item)
vkms_connector_hot_remove(connector->dev->config->dev,
connector_cfg->connector);
- vkms_config_destroy_connector(connector->config);
+ vkms_config_destroy_connector(connector->dev->config, connector->config);
kfree(connector);
}
diff --git a/drivers/gpu/drm/vkms/vkms_connector.c b/drivers/gpu/drm/vkms/vkms_connector.c
index a013b53bd915..08e1828a0fb0 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.c
+++ b/drivers/gpu/drm/vkms/vkms_connector.c
@@ -8,6 +8,76 @@
#include "vkms_config.h"
#include "vkms_connector.h"
+/**
+ * vkms_connector_build_path_property() - Build the PATH property string for MST connectors
+ * @connector: The connector to build the PATH property for
+ * @connector_cfg: The connector configuration
+ *
+ * The PATH property format is:
+ * mst:<drm object ID of root connector>-<dash-separated list of port_id>
+ * For nested MST connectors, this builds the full path like mst:45-2-3-4-2
+ */
+static void vkms_connector_build_path_property(struct vkms_connector *connector,
+ const struct vkms_config_connector *connector_cfg)
+{
+ const struct vkms_config_connector *current_cfg = connector_cfg;
+ const struct vkms_config_connector *root_cfg = NULL;
+ struct vkms_connector *root_connector = NULL;
+ char path[128]; /* Increased size for nested MST paths */
+ int len = 0;
+ u8 port_ids[16]; /* Max 16 levels of nesting */
+ int port_count = 0;
+ int i;
+
+ if (!vkms_config_connector_get_parent(connector_cfg))
+ return;
+
+ while (current_cfg) {
+ if (port_count < ARRAY_SIZE(port_ids))
+ port_ids[port_count++] = current_cfg->port_id;
+
+ if (!vkms_config_connector_get_parent(current_cfg)) {
+ root_cfg = current_cfg;
+ break;
+ }
+
+ current_cfg = vkms_config_connector_get_parent(current_cfg);
+ }
+
+ if (!root_cfg || !root_cfg->connector)
+ return;
+
+ root_connector = root_cfg->connector;
+
+ len = snprintf(path, sizeof(path), "mst:%d", root_connector->base.base.id);
+
+ for (i = port_count - 2; i >= 0; i--) {
+ int added = snprintf(path + len, sizeof(path) - len,
+ "-%u", port_ids[i]);
+ if (added < 0 || len + added >= sizeof(path))
+ return;
+ len += added;
+ }
+
+ drm_connector_set_path_property(&connector->base, path);
+}
+
+/**
+ * vkms_connector_update_path_properties() - Update PATH properties for all connectors
+ * @vkmsdev: VKMS device
+ *
+ * This should be called after all connectors are created to ensure parent connectors
+ * have valid DRM object IDs.
+ */
+void vkms_connector_update_path_properties(struct vkms_device *vkmsdev)
+{
+ struct vkms_config_connector *connector_cfg;
+
+ vkms_config_for_each_connector(vkmsdev->config, connector_cfg)
+ if (connector_cfg->connector)
+ vkms_connector_build_path_property(connector_cfg->connector, connector_cfg);
+}
+
static enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
bool force)
{
@@ -150,6 +220,8 @@ static int __must_check vkms_connector_init(struct vkms_connector *connector,
}
}
+ drm_object_attach_property(&connector->base.base, connector->base.dev->mode_config.path_property, 0);
+
return 0;
}
@@ -240,6 +312,8 @@ struct vkms_connector *vkms_connector_hot_add(struct vkms_device *vkmsdev,
if (ret)
return ERR_PTR(ret);
+ vkms_connector_build_path_property(connector, connector_cfg);
+
ret = drm_connector_dynamic_register(&connector->base);
if (ret) {
if (connector_cfg->type == DRM_MODE_CONNECTOR_HDMIA ||
diff --git a/drivers/gpu/drm/vkms/vkms_connector.h b/drivers/gpu/drm/vkms/vkms_connector.h
index a235a518d5a0..f8219830aaa5 100644
--- a/drivers/gpu/drm/vkms/vkms_connector.h
+++ b/drivers/gpu/drm/vkms/vkms_connector.h
@@ -67,4 +67,13 @@ int vkms_connector_hot_attach_encoder(struct vkms_device *vkmsdev,
struct vkms_connector *connector,
struct drm_encoder *encoder);
+/**
+ * vkms_connector_update_path_properties() - Update PATH properties for all connectors
+ * @vkmsdev: VKMS device
+ *
+ * This should be called after all connectors are created to ensure parent connectors
+ * have valid DRM object IDs.
+ */
+void vkms_connector_update_path_properties(struct vkms_device *vkmsdev);
+
#endif /* _VKMS_CONNECTOR_H_ */
diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c
index 50f7d88dee8b..ad901340ba7c 100644
--- a/drivers/gpu/drm/vkms/vkms_output.c
+++ b/drivers/gpu/drm/vkms/vkms_output.c
@@ -126,5 +126,7 @@ int vkms_output_init(struct vkms_device *vkmsdev)
}
}
+ vkms_connector_update_path_properties(vkmsdev);
+
return 0;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 38/38] drm/vkms: Add ConfigFS interface for connector parent and port_id
From: Louis Chauvet @ 2026-06-27 3:30 UTC (permalink / raw)
To: Haneen Mohammed, Simona Vetter, Melissa Wen, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, jose.exposito89,
Jonathan Corbet, Shuah Khan, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Luca Ceresoli, Kory Maincent, victoria, sebastian.wick, victoria,
airlied, thomas.petazzoni, dri-devel, linux-kernel, linux-doc,
Louis Chauvet, llvm
In-Reply-To: <20260627-vkms-all-config-v5-0-854aa0840926@bootlin.com>
Add the parent and port_id configuration for a connector.
- port_id can contains any u8 value
- parent can contain at most one symlink to an other connector.
Signed-off-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
drivers/gpu/drm/vkms/vkms_configfs.c | 104 +++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c
index dd2a9e450150..4f4ec9944804 100644
--- a/drivers/gpu/drm/vkms/vkms_configfs.c
+++ b/drivers/gpu/drm/vkms/vkms_configfs.c
@@ -114,6 +114,7 @@ struct vkms_configfs_encoder {
struct vkms_configfs_connector {
struct config_group group;
struct config_group possible_encoders_group;
+ struct config_group parent_group;
struct vkms_configfs_device *dev;
struct vkms_config_connector *config;
};
@@ -151,6 +152,10 @@ struct vkms_configfs_connector {
container_of(to_config_group((item)), struct vkms_configfs_connector, \
possible_encoders_group)
+#define connector_parent_item_to_vkms_configfs_connector(item) \
+ container_of(to_config_group((item)), struct vkms_configfs_connector, \
+ parent_group)
+
static ssize_t crtc_writeback_show(struct config_item *item, char *page)
{
struct vkms_configfs_crtc *crtc;
@@ -1401,6 +1406,42 @@ static ssize_t connector_dynamic_store(struct config_item *item,
return count;
}
+static ssize_t connector_port_id_show(struct config_item *item, char *page)
+{
+ struct vkms_configfs_connector *connector;
+ u8 port_id;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ scoped_guard(mutex, &connector->dev->lock)
+ port_id = vkms_config_connector_get_port_id(connector->config);
+
+ return sprintf(page, "%u\n", port_id);
+}
+
+static ssize_t connector_port_id_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct vkms_configfs_connector *connector;
+ u8 port_id;
+ int ret;
+
+ connector = connector_item_to_vkms_configfs_connector(item);
+
+ ret = kstrtou8(page, 10, &port_id);
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ if (connector->dev->enabled && connector_is_enabled(connector->config))
+ return -EBUSY;
+
+ vkms_config_connector_set_port_id(connector->config, port_id);
+ }
+
+ return count;
+}
+
CONFIGFS_ATTR(connector_, status);
CONFIGFS_ATTR(connector_, type);
CONFIGFS_ATTR(connector_, supported_colorspaces);
@@ -1408,6 +1449,7 @@ CONFIGFS_ATTR(connector_, edid_enabled);
CONFIGFS_ATTR(connector_, edid);
CONFIGFS_ATTR(connector_, dynamic);
CONFIGFS_ATTR(connector_, enabled);
+CONFIGFS_ATTR(connector_, port_id);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
@@ -1417,6 +1459,7 @@ static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_edid,
&connector_attr_dynamic,
&connector_attr_enabled,
+ &connector_attr_port_id,
NULL,
};
@@ -1512,6 +1555,63 @@ static const struct config_item_type connector_possible_encoders_group_type = {
.ct_owner = THIS_MODULE,
};
+static int connector_parent_connector_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
+ struct vkms_configfs_connector *parent;
+ struct vkms_config_connector *parent_cfg;
+ int ret;
+
+ if (target->ci_type != &connector_item_type)
+ return -EINVAL;
+
+ connector = connector_parent_item_to_vkms_configfs_connector(src);
+ connector_cfg = connector->config;
+ parent = connector_item_to_vkms_configfs_connector(target);
+ parent_cfg = parent->config;
+
+ if (connector->dev != parent->dev)
+ return -EINVAL;
+
+ scoped_guard(mutex, &connector->dev->lock) {
+ if (vkms_config_connector_get_parent(connector_cfg))
+ return -EMLINK;
+
+ if (connector->dev->enabled && connector_cfg->enabled)
+ return -EBUSY;
+
+ vkms_config_connector_attach_parent(connector_cfg,
+ parent_cfg);
+ }
+
+ return ret;
+}
+
+static void connector_parent_connector_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct vkms_configfs_connector *connector;
+ struct vkms_config_connector *connector_cfg;
+
+ connector = connector_parent_item_to_vkms_configfs_connector(src);
+ connector_cfg = connector->config;
+
+ scoped_guard(mutex, &connector->dev->lock)
+ vkms_config_connector_attach_parent(connector_cfg, NULL);
+}
+
+static struct configfs_item_operations connector_parent_item_operations = {
+ .allow_link = connector_parent_connector_allow_link,
+ .drop_link = connector_parent_connector_drop_link,
+};
+
+static const struct config_item_type connector_parent_group_type = {
+ .ct_item_ops = &connector_parent_item_operations,
+ .ct_owner = THIS_MODULE,
+};
+
static struct config_group *make_connector_group(struct config_group *group,
const char *name)
{
@@ -1544,6 +1644,10 @@ static struct config_group *make_connector_group(struct config_group *group,
config_group_init_type_name(&connector->possible_encoders_group,
"possible_encoders",
&connector_possible_encoders_group_type);
+ config_group_init_type_name(&connector->parent_group,
+ "parent",
+ &connector_parent_group_type);
+ configfs_add_default_group(&connector->parent_group, &connector->group);
configfs_add_default_group(&connector->possible_encoders_group,
&connector->group);
}
--
2.54.0
^ permalink raw reply related
* Re: [PATCH net-next] Documentation: networking: Add a test plan for ethtool pause validation
From: Maxime Chevallier @ 2026-06-27 5:34 UTC (permalink / raw)
To: Jakub Kicinski, Andrew Lunn
Cc: davem, Eric Dumazet, Paolo Abeni, Simon Horman, Russell King,
Heiner Kallweit, Jonathan Corbet, Shuah Khan, Oleksij Rempel,
Vladimir Oltean, Florian Fainelli, thomas.petazzoni, netdev,
linux-kernel, linux-doc
In-Reply-To: <20260626173352.7dc8f106@kernel.org>
Hi Jakub,
On 6/27/26 02:33, Jakub Kicinski wrote:
> On Fri, 26 Jun 2026 14:39:57 +0200 Andrew Lunn wrote:
>> On Fri, Jun 26, 2026 at 10:33:50AM +0200, Maxime Chevallier wrote:
>>>
>>>> Sphinx follows pythons object orientate structure. So you could have a
>>>> class test_ethtool_pause_advertising, with class documentation. And
>>>> then methods within the class which are individual tests. The
>>>> commented out section would then be method documentation.
>>>
>>> Good point, so maybe something along these lines :
>>>
>>> - A class for the test
>>> - methods for indivitual tests
>>> - For readability, I've written what the internal test helper would look
>>> like (_adv_test), and how a test would look like without the helper in
>>> adv_rx_on_tx_on().
>>>
>>> I'm already diving into coding, but it helps me a bit in the definition of the
>>> "description" format :)
>>>
>>> this is what the class would look like :
>>
>> I like this :-)
>
> This is very far from what existing python tests do in netdev.
We can probably drop the class, as it is with this discussion, it's merely a way
to regroup doc common to similar tests. The rest really is the usual set of
ksft funcs you can feed to the run function, with a set of ksft_ethtool_*
annotators for generic checks.
>
> I would prefer to stick to the "bash on steroids" use of Python.
Maxime
^ permalink raw reply
* Re: [PATCH v10 6/6] selftests/mm: add hwpoison-panic destructive test
From: Mike Rapoport @ 2026-06-27 7:52 UTC (permalink / raw)
To: Breno Leitao
Cc: Miaohe Lin, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Naoya Horiguchi, Jonathan Corbet, Shuah Khan, Liam R. Howlett,
lance.yang, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
linux-mm, linux-kernel, linux-doc, linux-kselftest,
linux-trace-kernel, kernel-team
In-Reply-To: <20260626-ecc_panic-v10-6-6dacb8ad024d@debian.org>
Hi Breno,
On Fri, Jun 26, 2026 at 08:33:20AM -0700, Breno Leitao wrote:
> Add a destructive selftest that verifies
> vm.panic_on_unrecoverable_memory_failure actually panics when a
> hwpoison error hits a kernel-owned page.
> +ksft_skip=4
...
> +ksft_print() { echo "# $*"; }
> +ksft_exit_skip() { ksft_print "$*"; exit "$ksft_skip"; }
> +ksft_exit_fail() { echo "not ok 1 $*"; exit 1; }
There is tools/testing/selftests/kselftest/ktap_helpers.sh that already
implements this :)
--
Sincerely yours,
Mike.
^ permalink raw reply
* [PATCH 1/2] Documentation: kunit: Test Kconfig entries shouldn't select other configs
From: David Gow @ 2026-06-27 8:29 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Jonathan Corbet, Shuah Khan
Cc: David Gow, linux-kselftest, kunit-dev, linux-doc, linux-kernel,
Geert Uytterhoeven
Add a note to the Kconfig section of style.rst to use 'depends on' rather
than 'selects' for dependencies, as this can cause users of
CONFIG_KUNIT_ALL_TESTS to suddenly grow unexpected dependencies.
Signed-off-by: David Gow <david@davidgow.net>
---
Documentation/dev-tools/kunit/style.rst | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/Documentation/dev-tools/kunit/style.rst b/Documentation/dev-tools/kunit/style.rst
index eac81a714a29..449f9f816fc7 100644
--- a/Documentation/dev-tools/kunit/style.rst
+++ b/Documentation/dev-tools/kunit/style.rst
@@ -164,9 +164,10 @@ This Kconfig entry must:
* be visible only if ``CONFIG_KUNIT_ALL_TESTS`` is not enabled.
* have a default value of ``CONFIG_KUNIT_ALL_TESTS``.
* have a brief description of KUnit in the help text.
-
-If we are not able to meet above conditions (for example, the test is unable to
-be built as a module), Kconfig entries for tests should be tristate.
+* depend on the feature being tested, rather than selecting it (so that
+ enabling ``CONFIG_KUNIT_ALL_TESTS`` does not enable unrelated functionality).
+* be ``tristate``, unless there is a specific reason that the test cannot be
+ built as a module.
For example, a Kconfig entry might look like:
--
2.54.0
^ permalink raw reply related
* [PATCH 2/2] Documentation: kunit: Fix outdated FAQ entries
From: David Gow @ 2026-06-27 8:29 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Jonathan Corbet, Shuah Khan
Cc: David Gow, linux-kselftest, kunit-dev, linux-doc, linux-kernel,
Geert Uytterhoeven
In-Reply-To: <20260627082921.1709181-1-david@davidgow.net>
The KUnit FAQ was written when KUnit in general, and kunit.py in
particular, were very heavily focused on UML. While they were updated
slightly when qemu support was added, they've not really kept pace with
changes to KUnit or the structure of the rest of the documentation.
Update them to describe how to run kunit.py with non-UML architectures, and
to point to the run_manual.rst page for further detail on how to run KUnit
without kunit.py, as it's the authoratative documentation on that subject.
Signed-off-by: David Gow <david@davidgow.net>
---
Documentation/dev-tools/kunit/faq.rst | 43 +++++++++++++++------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/Documentation/dev-tools/kunit/faq.rst b/Documentation/dev-tools/kunit/faq.rst
index fae426f2634a..b1341c1a62d9 100644
--- a/Documentation/dev-tools/kunit/faq.rst
+++ b/Documentation/dev-tools/kunit/faq.rst
@@ -25,19 +25,21 @@ disqualifying any of them from being considered unit testing frameworks.
Does KUnit support running on architectures other than UML?
===========================================================
-Yes, mostly.
+Yes. KUnit can run on any architecture, though the kunit.py tool can only
+build and run kernels for some architectures (of which UML is the default).
-For the most part, the KUnit core framework (what we use to write the tests)
-can compile to any architecture. It compiles like just another part of the
-kernel and runs when the kernel boots, or when built as a module, when the
-module is loaded. However, there is infrastructure, like the KUnit Wrapper
-(``tools/testing/kunit/kunit.py``) that might not support some architectures
-(see :ref:`kunit-on-qemu`).
+You can build and run tests without kunit.py at all on any architecture by
+enabling ``CONFIG_KUNIT=y`` and booting the kernel.
+See Documentation/dev-tools/kunit/run_manual.rst for more details.
-In short, yes, you can run KUnit on other architectures, but it might require
-more work than using KUnit on UML.
+Alternatively, kunit.py supports many common architectures using
+cross-compilers and the qemu emulator. This can be done using the ``--arch``
+parameter when running the tests, and the ``--cross_compile`` parameter
+when building (if the architecture is not supported by the host compiler).
+See :ref:`kunit-on-qemu` for more details.
-For more information, see :ref:`kunit-on-non-uml`.
+When writing tests targeting other architectures, it's worth keeping the tips
+on the :ref:`kunit-on-non-uml` page in mind.
.. _kinds-of-tests:
@@ -78,27 +80,30 @@ things to try.
down where an issue is occurring. (If you think the parser is at fault, you
can run it manually against ``stdin`` or a file with ``kunit.py parse``.)
3. Running the UML kernel directly can often reveal issues or error messages,
- ``kunit_tool`` ignores. This should be as simple as running ``./vmlinux``
- after building the UML kernel (for example, by using ``kunit.py build``).
+ ``kunit_tool`` ignores. This should be as simple as runningi the ``vmlinux``
+ binary in the output directory (by default ``./.kunit/vmlinux``) after
+ building the UML kernel (for example, by using ``kunit.py build``).
Note that UML has some unusual requirements (such as the host having a tmpfs
filesystem mounted), and has had issues in the past when built statically and
the host has KASLR enabled. (On older host kernels, you may need to run
``setarch `uname -m` -R ./vmlinux`` to disable KASLR.)
-4. Make sure the kernel .config has ``CONFIG_KUNIT=y`` and at least one test
+4. Try running KUnit on a different architecture by using the ``--arch``
+ option. On an x86_64 host, using ``--arch=x86_64`` is a good first step.
+5. Make sure the kernel .config has ``CONFIG_KUNIT=y`` and at least one test
(e.g. ``CONFIG_KUNIT_EXAMPLE_TEST=y``). kunit_tool will keep its .config
around, so you can see what config was used after running ``kunit.py run``.
It also preserves any config changes you might make, so you can
enable/disable things with ``make ARCH=um menuconfig`` or similar, and then
re-run kunit_tool.
-5. Try to run ``make ARCH=um defconfig`` before running ``kunit.py run``. This
+6. Try to run ``make ARCH=um defconfig`` before running ``kunit.py run``. This
may help clean up any residual config items which could be causing problems.
-6. Finally, try running KUnit outside UML. KUnit and KUnit tests can be
- built into any kernel, or can be built as a module and loaded at runtime.
- Doing so should allow you to determine if UML is causing the issue you're
- seeing. When tests are built-in, they will execute when the kernel boots, and
+7. Finally, try running KUnit manually, instead of via ``kunit.py``. KUnit can
+ be built into any kernel, or can be built as a module and loaded at runtime.
+ When tests are built-in, they will execute when the kernel boots, and
modules will automatically execute associated tests when loaded. Test results
can be collected from ``/sys/kernel/debug/kunit/<test suite>/results``, and
- can be parsed with ``kunit.py parse``. For more details, see :ref:`kunit-on-qemu`.
+ can be parsed with ``kunit.py parse``. For more details, see
+ Documentation/dev-tools/kunit/run_manual.rst
If none of the above tricks help, you are always welcome to email any issues to
kunit-dev@googlegroups.com.
--
2.54.0
^ permalink raw reply related
* [PATCH] Documentation: RCU: adopt new coding style of type-aware kmalloc-family - part 2/2
From: Manuel Ebner @ 2026-06-27 9:06 UTC (permalink / raw)
To: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION, open list,
Kees Cook
Cc: Manuel Ebner
Update Documentation/RCU/* to reflect new type-aware kmalloc-family
as suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
and family")
p = kmalloc(...);
-> p = kmalloc_obj(...);
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
A similar patch sent by me has been applied. Back then I wasn't familiar
enough to convert all *alloc(*. That's why this is " - part 2/2".
Part 1: f61bf5fdf77d ("Documentation: RCU: adopt new coding style of
type-aware kmalloc-family")
---
Documentation/RCU/rcu_dereference.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/RCU/rcu_dereference.rst b/Documentation/RCU/rcu_dereference.rst
index 2524dcdadde2..5bc3785ebfc2 100644
--- a/Documentation/RCU/rcu_dereference.rst
+++ b/Documentation/RCU/rcu_dereference.rst
@@ -236,7 +236,7 @@ precautions. To see this, consider the following code fragment::
{
struct foo *p;
- p = kmalloc(...);
+ p = kmalloc_obj(*p);
if (p == NULL)
deal_with_it();
p->a = 42; /* Each field in its own cache line. */
@@ -293,7 +293,7 @@ Then one approach is to use locking, for example, as follows::
{
struct foo *p;
- p = kmalloc(...);
+ p = kmalloc_obj(*p);
if (p == NULL)
deal_with_it();
spin_lock(&p->lock);
--
2.54.0
^ permalink raw reply related
* Re: [External] [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: yunhui cui @ 2026-06-27 9:11 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-3-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:40 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the srmcfg CSR defined in the Ssqosid ISA extension.
> The CSR contains two fields:
>
> - Resource Control ID (RCID) for resource allocation
> - Monitoring Counter ID (MCID) for tracking resource usage
>
> Requests from a hart to shared resources are tagged with these IDs,
> allowing resource usage to be associated with the running task.
>
> Add a srmcfg field to thread_struct with the same format as the CSR so
> the scheduler can set the RCID and MCID for each task on context
> switch. A per-cpu cpu_srmcfg variable mirrors the CSR state to avoid
> redundant writes. L1D-hot memory access is faster than a CSR read and
> avoids traps under virtualization.
>
> A per-cpu cpu_srmcfg_default holds the default srmcfg for each CPU as
> set by resctrl CPU group assignment. On context switch, RCID and MCID
> inherit from the CPU default independently: a task whose thread RCID
> field is zero takes the CPU default's RCID, and likewise for MCID.
>
> Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 8 ++++
> arch/riscv/Kconfig | 18 +++++++
> arch/riscv/include/asm/csr.h | 5 ++
> arch/riscv/include/asm/processor.h | 3 ++
> arch/riscv/include/asm/qos.h | 83 ++++++++++++++++++++++++++++++++
> arch/riscv/include/asm/switch_to.h | 3 ++
> arch/riscv/kernel/Makefile | 2 +
> arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++
> 8 files changed, 220 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0b9d7c8276acbafdb28a0ea5e81aa853ebee50b9..07109e1a8f8470377916c98074ab68fec51dfdc6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23293,6 +23293,14 @@ F: drivers/perf/riscv_pmu.c
> F: drivers/perf/riscv_pmu_legacy.c
> F: drivers/perf/riscv_pmu_sbi.c
>
> +RISC-V QOS RESCTRL SUPPORT
> +M: Drew Fustini <fustini@kernel.org>
> +R: yunhui cui <cuiyunhui@bytedance.com>
> +L: linux-riscv@lists.infradead.org
> +S: Supported
> +F: arch/riscv/include/asm/qos.h
> +F: arch/riscv/kernel/qos.c
> +
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> M: Anup Patel <anup@brainfault.org>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 3f0a647218e407f72890e83722ba8472858c1a59..ee586925f97227668c228b5481c05a2f914d928c 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -590,6 +590,24 @@ config RISCV_ISA_SVNAPOT
>
> If you don't know what to do here, say Y.
>
> +config RISCV_ISA_SSQOSID
> + bool "Ssqosid extension support for supervisor mode Quality of Service ID"
> + depends on 64BIT
> + default n
> + help
> + Adds support for the Ssqosid ISA extension (Supervisor-mode
> + Quality of Service ID).
> +
> + Ssqosid defines the srmcfg CSR which allows the system to tag the
> + running process with an RCID (Resource Control ID) and MCID
> + (Monitoring Counter ID). The RCID is used to determine resource
> + allocation. The MCID is used to track resource usage in event
> + counters.
> +
> + For example, a cache controller may use the RCID to apply a
> + cache partitioning scheme and use the MCID to track how much
> + cache a process, or a group of processes, is using.
> +
> config RISCV_ISA_SVPBMT
> bool "Svpbmt extension support for supervisor mode page-based memory types"
> depends on 64BIT && MMU
> diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
> index 31b8988f4488daa89b854ccc97c4efe1c82bcc3e..7bce928e5daa09bd62f0917279b04cfad30f46f5 100644
> --- a/arch/riscv/include/asm/csr.h
> +++ b/arch/riscv/include/asm/csr.h
> @@ -84,6 +84,10 @@
> #define SATP_ASID_MASK _AC(0xFFFF, UL)
> #endif
>
> +/* SRMCFG fields */
> +#define SRMCFG_RCID_MASK GENMASK(11, 0)
> +#define SRMCFG_MCID_MASK GENMASK(27, 16)
> +
> /* Exception cause high bit - is an interrupt if set */
> #define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
>
> @@ -328,6 +332,7 @@
> #define CSR_STVAL 0x143
> #define CSR_SIP 0x144
> #define CSR_SATP 0x180
> +#define CSR_SRMCFG 0x181
>
> #define CSR_STIMECMP 0x14D
> #define CSR_STIMECMPH 0x15D
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 812517b2cec1350f741849c1c56a35027321ef50..49a386d74cd3f0603a3ff919059d077a7e4d513c 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -123,6 +123,9 @@ struct thread_struct {
> /* A forced icache flush is not needed if migrating to the previous cpu. */
> unsigned int prev_cpu;
> #endif
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> + u32 srmcfg;
> +#endif
> };
>
> /* Whitelist the fstate from the task_struct for hardened usercopy */
> diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e9e1d69f3797be5f89785a9b3aa7d9d51c476a8a
> --- /dev/null
> +++ b/arch/riscv/include/asm/qos.h
> @@ -0,0 +1,83 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_RISCV_QOS_H
> +#define _ASM_RISCV_QOS_H
> +
> +#include <linux/percpu-defs.h>
> +
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> +
> +#include <linux/bitfield.h>
> +#include <linux/cpufeature.h>
> +#include <linux/sched.h>
> +
> +#include <asm/csr.h>
> +#include <asm/fence.h>
> +#include <asm/hwcap.h>
> +
> +/* cached value of srmcfg csr for each cpu */
> +DECLARE_PER_CPU(u32, cpu_srmcfg);
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DECLARE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +static inline void __switch_to_srmcfg(struct task_struct *next)
> +{
> + u32 thread_srmcfg, default_srmcfg;
> +
> + thread_srmcfg = READ_ONCE(next->thread.srmcfg);
> + default_srmcfg = __this_cpu_read(cpu_srmcfg_default);
> +
> + /*
> + * RCID and MCID inherit from cpu_srmcfg_default independently.
> + * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are both 0, so a
> + * zero field means "unassigned" and takes the CPU default.
> + */
> + if (thread_srmcfg == 0) {
> + thread_srmcfg = default_srmcfg;
> + } else {
> + u32 rcid = FIELD_GET(SRMCFG_RCID_MASK, thread_srmcfg);
> + u32 mcid = FIELD_GET(SRMCFG_MCID_MASK, thread_srmcfg);
> +
> + if (rcid == 0 || mcid == 0) {
> + if (rcid == 0)
> + rcid = FIELD_GET(SRMCFG_RCID_MASK, default_srmcfg);
> + if (mcid == 0)
> + mcid = FIELD_GET(SRMCFG_MCID_MASK, default_srmcfg);
> + thread_srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, rcid) |
> + FIELD_PREP(SRMCFG_MCID_MASK, mcid);
> + }
> + }
> +
> + if (thread_srmcfg != __this_cpu_read(cpu_srmcfg)) {
> + /*
> + * Drain stores from the outgoing task before the CSR write
> + * so they retain the previous RCID/MCID tag at the cache
> + * interconnect.
> + */
> + RISCV_FENCE(rw, o);
> +
> + __this_cpu_write(cpu_srmcfg, thread_srmcfg);
> + csr_write(CSR_SRMCFG, thread_srmcfg);
> + /*
> + * Order the csrw before the new task's loads/stores so they
> + * pick up the new tag. Zicsr 6.1.1 makes CSR writes weakly
> + * ordered (device-output) vs memory ops. Ssqosid v1.0 is
> + * silent so honor the general CSR rule.
> + */
> + RISCV_FENCE(o, rw);
This is in the context-switch path and may be expensive in practice. Even if
the target workload is pinned and grouped, unpinned/default-group tasks or
kworkers may still run on those CPUs, causing frequent SRMCFG transitions and
paying two fences each time.
Is this strict ordering required by the Ssqosid spec or known hardware? If
not, can we make this a trade-off and avoid the fences by default, accepting a
small QoS-tagging inaccuracy around the context-switch boundary?
> + }
> +}
> +
> +static __always_inline bool has_srmcfg(void)
> +{
> + return riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID);
> +}
> +
> +#else /* ! CONFIG_RISCV_ISA_SSQOSID */
> +
> +struct task_struct;
> +static __always_inline bool has_srmcfg(void) { return false; }
> +static inline void __switch_to_srmcfg(struct task_struct *next) { }
> +
> +#endif /* CONFIG_RISCV_ISA_SSQOSID */
> +#endif /* _ASM_RISCV_QOS_H */
> diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h
> index 0e71eb82f920cac2f14bb626879bb219a2f247cc..1c7ea53ec012adeaf03bf7c5d549ab21849768b5 100644
> --- a/arch/riscv/include/asm/switch_to.h
> +++ b/arch/riscv/include/asm/switch_to.h
> @@ -14,6 +14,7 @@
> #include <asm/processor.h>
> #include <asm/ptrace.h>
> #include <asm/csr.h>
> +#include <asm/qos.h>
>
> #ifdef CONFIG_FPU
> extern void __fstate_save(struct task_struct *save_to);
> @@ -119,6 +120,8 @@ do { \
> __switch_to_fpu(__prev, __next); \
> if (has_vector() || has_xtheadvector()) \
> __switch_to_vector(__prev, __next); \
> + if (has_srmcfg()) \
> + __switch_to_srmcfg(__next); \
> if (switch_to_should_flush_icache(__next)) \
> local_flush_icache_all(); \
> __switch_to_envcfg(__next); \
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index cabb99cadfb6d1e1284d6b4e9ae76044d36949f5..ebe1c3588177b4b825a52af9ca17e17b5561427c 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -128,3 +128,5 @@ obj-$(CONFIG_ACPI_NUMA) += acpi_numa.o
>
> obj-$(CONFIG_GENERIC_CPU_VULNERABILITIES) += bugs.o
> obj-$(CONFIG_RISCV_USER_CFI) += usercfi.o
> +
> +obj-$(CONFIG_RISCV_ISA_SSQOSID) += qos.o
> diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ea33201a43f61534bf28b9c02b62801f30f62154
> --- /dev/null
> +++ b/arch/riscv/kernel/qos.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/cpu.h>
> +#include <linux/cpu_pm.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/notifier.h>
> +#include <linux/percpu-defs.h>
> +#include <linux/types.h>
> +
> +#include <asm/cpufeature-macros.h>
> +#include <asm/hwcap.h>
> +#include <asm/qos.h>
> +
> +/*
> + * Cached value of srmcfg csr for each cpu. Seeded to U32_MAX so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR. The encoding
> + * MCID << 16 | RCID with both fields well under 16 bits can never
> + * produce this sentinel. This covers early-boot context switches that
> + * happen before riscv_srmcfg_init() runs as an arch_initcall.
> + */
> +DEFINE_PER_CPU(u32, cpu_srmcfg) = U32_MAX;
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DEFINE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +/*
> + * Invalidate the per-CPU srmcfg cache, used as both the cpuhp startup and
> + * teardown callback. The sentinel is a value no real srmcfg encoding can
> + * produce (MCID << 16 | RCID, both fields well under 16 bits) so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR.
> + *
> + * Ssqosid v1.0 leaves CSR state across hart stop/start implementation-
> + * defined, so the cached value cannot be trusted after online. Invalidating
> + * on offline as well means the sentinel persists across the offline period:
> + * a CPU brought back online finds the cache already invalidated before it is
> + * schedulable, closing the window where a task scheduled before the startup
> + * callback runs could match a stale cache and skip the CSR write while the
> + * hardware CSR was reset across hart stop/start.
> + */
> +static int riscv_srmcfg_reset_cache(unsigned int cpu)
> +{
> + per_cpu(cpu_srmcfg, cpu) = U32_MAX;
> + return 0;
> +}
> +
> +/*
> + * CPU PM notifier: invalidate the cached srmcfg on resume from a deep
> + * idle / suspend. Ssqosid v1.0 leaves CSR_SRMCFG state across low-power
> + * transitions implementation-defined, and the boot CPU never goes
> + * through the cpuhp online callback during system suspend, so without
> + * this hook __switch_to_srmcfg() would skip the CSR write when the
> + * outgoing task happens to share its srmcfg with the pre-suspend cache.
> + */
> +static int riscv_srmcfg_pm_notify(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + switch (action) {
> + case CPU_PM_EXIT:
> + case CPU_PM_ENTER_FAILED:
> + /*
> + * The CSR is implementation-defined across the low-power
> + * transition. Invalidate the cache and eagerly rewrite the
> + * CSR for the current task so it does not run mis-tagged
> + * until the next context switch.
> + */
> + __this_cpu_write(cpu_srmcfg, U32_MAX);
> + __switch_to_srmcfg(current);
> + break;
> + }
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block riscv_srmcfg_pm_nb = {
> + .notifier_call = riscv_srmcfg_pm_notify,
> +};
> +
> +static int __init riscv_srmcfg_init(void)
> +{
> + int err;
> +
> + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID))
> + return 0;
> +
> + /*
> + * cpuhp_setup_state() invokes the startup callback locally on every
> + * already-online CPU, so no separate seed loop is needed here.
> + */
> + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online",
> + riscv_srmcfg_reset_cache, riscv_srmcfg_reset_cache);
> + if (err < 0) {
> + pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n",
> + err);
> + return err;
> + }
> +
> + cpu_pm_register_notifier(&riscv_srmcfg_pm_nb);
> + return 0;
> +}
> +arch_initcall(riscv_srmcfg_init);
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
* [PATCH] Documentation: device-mapper: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-06-27 9:15 UTC (permalink / raw)
To: Alasdair Kergon, Mike Snitzer, Mikulas Patocka,
Benjamin Marzinski, Jonathan Corbet, Shuah Khan, Kees Cook,
open list:DEVICE-MAPPER (LVM), open list:DOCUMENTATION,
open list
Cc: Manuel Ebner
Change the Documentation to reflect this commit 69050f8d6d07 ("treewide: Replace
kmalloc with kmalloc_obj for non-scalar types")
kmalloc -> kmalloc_objs
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/admin-guide/device-mapper/statistics.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/device-mapper/statistics.rst b/Documentation/admin-guide/device-mapper/statistics.rst
index 41ded0bc5933..2190b90118f8 100644
--- a/Documentation/admin-guide/device-mapper/statistics.rst
+++ b/Documentation/admin-guide/device-mapper/statistics.rst
@@ -30,7 +30,7 @@ region, etc. Unique region_ids enable multiple userspace programs to
request and process statistics for the same DM device without stepping
on each other's data.
-The creation of DM statistics will allocate memory via kmalloc or
+The creation of DM statistics will allocate memory via kmalloc_objs or
fallback to using vmalloc space. At most, 1/4 of the overall system
memory may be allocated by DM statistics. The admin can see how much
memory is used by reading:
--
2.54.0
^ permalink raw reply related
* [PATCH] docs: kernel-hacking: fix typo
From: Manuel Ebner @ 2026-06-27 9:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION, open list,
Randy Dunlap
Cc: Manuel Ebner
GP_KERNEL -> GFP_KERNEL
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/kernel-hacking/locking.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/kernel-hacking/locking.rst b/Documentation/kernel-hacking/locking.rst
index c969c76ef7cb..9c6c76d3a723 100644
--- a/Documentation/kernel-hacking/locking.rst
+++ b/Documentation/kernel-hacking/locking.rst
@@ -1317,7 +1317,7 @@ from user context, and can sleep.
- put_user()
-- kmalloc(GP_KERNEL) <kmalloc>`
+- kmalloc(GFP_KERNEL) <kmalloc>`
- mutex_lock_interruptible() and
mutex_lock()
--
2.54.0
^ permalink raw reply related
* [PATCH] docs: watchdog: Fix brackets
From: Manuel Ebner @ 2026-06-27 9:17 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Jonathan Corbet, Shuah Khan,
open list:WATCHDOG DEVICE DRIVERS, open list:DOCUMENTATION,
open list, Randy Dunlap
Cc: Manuel Ebner
Add missing brackets ')'.
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/watchdog/watchdog-parameters.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst
index 2359aa32e25d..8e2df5b7d241 100644
--- a/Documentation/watchdog/watchdog-parameters.rst
+++ b/Documentation/watchdog/watchdog-parameters.rst
@@ -59,7 +59,7 @@ advantechwdt:
alim1535_wdt:
timeout:
- Watchdog timeout in seconds. (0 < timeout < 18000, default=60
+ Watchdog timeout in seconds. (0 < timeout < 18000, default=60)
nowayout:
Watchdog cannot be stopped once started
(default=kernel config parameter)
@@ -68,7 +68,7 @@ alim1535_wdt:
alim7101_wdt:
timeout:
- Watchdog timeout in seconds. (1<=timeout<=3600, default=30
+ Watchdog timeout in seconds. (1<=timeout<=3600, default=30)
use_gpio:
Use the gpio watchdog (required by old cobalt boards).
default=0/off/no
--
2.54.0
^ permalink raw reply related
* Re: [External] [PATCH v2 4/8] riscv_cbqri: Add capacity controller probe and allocation device ops
From: yunhui cui @ 2026-06-27 9:19 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-4-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:41 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the RISC-V CBQRI capacity controller. A platform driver
> passes a cbqri_controller_info descriptor together with the cache level
> to riscv_cbqri_register_cc_dt(), which probes the controller and adds it
> to the controller list.
>
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 3 +
> drivers/resctrl/Kconfig | 13 +
> drivers/resctrl/Makefile | 3 +
> drivers/resctrl/cbqri_devices.c | 520 +++++++++++++++++++++++++++++++++++++++
> drivers/resctrl/cbqri_internal.h | 107 ++++++++
> include/linux/riscv_cbqri.h | 47 ++++
> 6 files changed, 693 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 07109e1a8f8470377916c98074ab68fec51dfdc6..811c0c9b1fac806945cad0229c5330654420a835 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23300,6 +23300,9 @@ L: linux-riscv@lists.infradead.org
> S: Supported
> F: arch/riscv/include/asm/qos.h
> F: arch/riscv/kernel/qos.c
> +F: drivers/resctrl/cbqri_devices.c
> +F: drivers/resctrl/cbqri_internal.h
> +F: include/linux/riscv_cbqri.h
>
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
> index 672abea3b03ccbeb4532832dd18e5cb80e90ff5b..92b9c82cf9f397437d28006e79d40c2d9b384eb0 100644
> --- a/drivers/resctrl/Kconfig
> +++ b/drivers/resctrl/Kconfig
> @@ -29,3 +29,16 @@ config ARM64_MPAM_RESCTRL_FS
> default y if ARM64_MPAM_DRIVER && RESCTRL_FS
> select RESCTRL_RMID_DEPENDS_ON_CLOSID
> select RESCTRL_ASSIGN_FIXED
> +
> +menuconfig RISCV_CBQRI
> + bool "RISC-V CBQRI support"
> + depends on RISCV && RISCV_ISA_SSQOSID
> + help
> + Capacity and Bandwidth QoS Register Interface (CBQRI) support for
> + RISC-V cache QoS resources. CBQRI exposes cache capacity
> + allocation through the resctrl filesystem at /sys/fs/resctrl when
> + RESCTRL_FS is also enabled.
> +
> +if RISCV_CBQRI
> +
> +endif
> diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
> index 4f6d0e81f9b8f34bd8842fdb13be69da72515b79..4d8a2c4b5627144a651da007174d4d9df6171330 100644
> --- a/drivers/resctrl/Makefile
> +++ b/drivers/resctrl/Makefile
> @@ -3,3 +3,6 @@ mpam-y += mpam_devices.o
> mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o
>
> ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
> +
> +obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
> +cbqri-y += cbqri_devices.o
> diff --git a/drivers/resctrl/cbqri_devices.c b/drivers/resctrl/cbqri_devices.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..8ad9df404f65d5d82722cf8b78f02936c489ca6d
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_devices.c
> @@ -0,0 +1,520 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/ioport.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/printk.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +#include <asm/csr.h>
> +
> +#include "cbqri_internal.h"
> +
> +LIST_HEAD(cbqri_controllers);
> +
> +/*
> + * Serializes cbqri_controllers mutations against a concurrent insert under
> + * asynchronous driver probing, and against the boot-time walk in the resctrl
> + * glue. Runtime cpuhp walks happen after registration has settled.
> + */
> +DEFINE_MUTEX(cbqri_controllers_lock);
> +
> +/* Set capacity block mask (cc_block_mask) */
> +static void cbqri_set_cbm(struct cbqri_controller *ctrl, u64 cbm)
> +{
> + iowrite64(cbm, ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> +}
> +
> +static int cbqri_wait_busy_flag(struct cbqri_controller *ctrl, int reg_offset,
> + u64 *regp)
> +{
> + u64 reg;
> + int ret;
> +
> + /*
> + * Sleeping poll: caller holds ctrl->lock as a sleeping mutex, so
> + * 10us/1ms is safe under PREEMPT_RT.
> + */
> + ret = readq_poll_timeout(ctrl->base + reg_offset, reg,
> + !FIELD_GET(CBQRI_CONTROL_REGISTERS_BUSY_MASK, reg),
> + 10, 1000);
> + if (ret)
> + return ret;
> + if (regp)
> + *regp = reg;
> + return 0;
> +}
> +
> +/*
> + * Perform capacity allocation control operation on capacity controller.
> + * Caller must hold ctrl->lock.
> + */
> +static int cbqri_cc_alloc_op(struct cbqri_controller *ctrl, int operation,
> + int rcid, u32 at)
> +{
> + int reg_offset = CBQRI_CC_ALLOC_CTL_OFF;
> + int status;
> + u64 reg;
> +
> + lockdep_assert_held(&ctrl->lock);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout before starting operation\n");
> + return -EIO;
> + }
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_OP_MASK, ®, operation);
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_RCID_MASK, ®, rcid);
> +
> + /*
> + * CBQRI Table 1: AT 0=Data, 1=Code. Program AT on controllers
> + * that report supports_alloc_at_code. On controllers that don't,
> + * AT is reserved-zero and the op acts on both halves.
> + */
> + reg &= ~CBQRI_CONTROL_REGISTERS_AT_MASK;
> + if (ctrl->cc.supports_alloc_at_code)
> + reg |= FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK, at);
> +
> + iowrite64(reg, ctrl->base + reg_offset);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> + if (status != CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS) {
> + pr_err_ratelimited("operation %d failed: status=%d\n", operation, status);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Apply a capacity block mask and verify via CONFIG_LIMIT + READ_LIMIT.
> + *
> + * AT-capable controllers with CDP off need a second CONFIG_LIMIT on the
> + * other AT half (the spec encodes AT only as 0=Data / 1=Code, there is
> + * no "both halves" value). CDP-on issues separate per-type writes from
> + * resctrl, so a single CONFIG_LIMIT per call is correct.
> + */
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg)
> +{
> + bool need_at_mirror;
> + u64 saved_cbm = 0;
> + int err = 0;
> + u64 reg;
> +
> + mutex_lock(&ctrl->lock);
> +
> + need_at_mirror = ctrl->cc.supports_alloc_at_code && !cfg->cdp_enabled;
> +
> + /*
> + * Capture the cfg->at half CBM before any write so a partial
> + * AT-mirror failure can revert and keep the two halves consistent.
> + * Pre-clear cc_block_mask so a silent firmware no-op (status
> + * SUCCESS but staging not updated) shows as a zero readback
> + * rather than carrying stale data from a prior op.
> + */
> + if (need_at_mirror) {
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> + saved_cbm = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + }
> +
> + /* Set capacity block mask (cc_block_mask) */
> + cbqri_set_cbm(ctrl, cfg->cbm);
> +
> + /* Capacity config limit operation for the AT half implied by cfg->at */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
When CUNITS=1, CONFIG_LIMIT also consumes cc_cunits. If resctrl does not
expose unit limits, the driver should still write cc_cunits=0 before
CONFIG_LIMIT to avoid a hidden stale/implementation-defined unit limit.
Should we handle cc_cunits here?
> +
> + /*
> + * CDP-off mirror: on AT-capable controllers, also program the
> + * other AT half with the same mask so the two halves stay in sync.
> + */
> + if (need_at_mirror) {
> + u32 other = (cfg->at == CBQRI_CONTROL_REGISTERS_AT_CODE) ?
> + CBQRI_CONTROL_REGISTERS_AT_DATA :
> + CBQRI_CONTROL_REGISTERS_AT_CODE;
> +
> + cbqri_set_cbm(ctrl, cfg->cbm);
> + err = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, other);
> + if (err < 0) {
> + int rerr;
> +
> + /*
> + * Best-effort revert of the cfg->at half so the two
> + * halves stay in sync. A schemata read sees only one
> + * half, so silent divergence would otherwise report
> + * the new value as if the write had succeeded.
> + */
> + cbqri_set_cbm(ctrl, saved_cbm);
> + rerr = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (rerr < 0)
> + pr_err_ratelimited("AT-mirror revert failed (err=%d), AT halves diverged\n",
> + rerr);
> + goto out;
> + }
> + }
> +
> + /* Clear cc_block_mask before read limit to verify op works */
> + cbqri_set_cbm(ctrl, 0);
> +
> + /* Perform a capacity read limit operation to verify blockmask */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> +
> + /*
> + * Read capacity blockmask and narrow to u32 to match resctrl's CBM
> + * width. cbqri_probe_cc() rejects ncblks > 32 so the upper bits are
> + * reserved zero.
> + */
> + reg = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + if (lower_32_bits(reg) != cfg->cbm) {
> + pr_err_ratelimited("CBM verify mismatch (reg=%llx != cbm=%llx)\n",
> + reg, cfg->cbm);
> + err = -EIO;
> + }
> +
> +out:
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +/*
> + * Read the configured CBM for closid on the at half via READ_LIMIT.
> + * Pre-clears cc_block_mask before the op so a silent firmware no-op
> + * (status SUCCESS but staging not updated) is detectable in cbm_out.
> + */
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out)
> +{
> + int err;
> +
> + mutex_lock(&ctrl->lock);
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, closid, at);
> + if (err == 0) {
> + /*
> + * cc_block_mask is a 64-bit MMIO register. resctrl exposes the
> + * CBM as a u32. cbqri_probe_cc() rejects ncblks > 32 so the
> + * upper 32 bits are reserved zero by the spec. Narrow
> + * explicitly via lower_32_bits() so the assumption is visible
> + * at the read site.
> + */
> + *cbm_out = lower_32_bits(ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF));
> + }
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset,
> + int operation, int *status, bool *access_type_supported)
> +{
> + const u64 active_mask = CBQRI_CONTROL_REGISTERS_OP_MASK |
> + CBQRI_CONTROL_REGISTERS_AT_MASK |
> + CBQRI_CONTROL_REGISTERS_RCID_MASK;
> + u64 reg, saved_reg;
> + int at;
> +
> + /*
> + * Default the output to false so the status==0 (feature not
> + * implemented) path returns a deterministic value to the caller
> + * rather than leaving an uninitialized bool.
> + */
> + *access_type_supported = false;
> +
> + /* Keep the initial register value to preserve the WPRI fields */
> + reg = ioread64(ctrl->base + reg_offset);
> + saved_reg = reg;
> +
> + /* Drain any in-flight firmware op before issuing our own write. */
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, &saved_reg) < 0) {
> + pr_err("BUSY timeout before probe operation\n");
> + return -EIO;
> + }
> +
> + /*
> + * Execute the requested operation with all active fields
> + * (OP/AT/RCID) zeroed except OP itself. Every bit not in
> + * active_mask is WPRI and gets carried over from saved_reg.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + /* Get the operation status */
> + *status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> +
> + /*
> + * Check for the AT support if the register is implemented
> + * (if not, the status value will remain 0)
> + */
> + if (*status != 0) {
> + /*
> + * Re-issue operation with AT=CODE so the controller
> + * latches AT=CODE on supported hardware (or resets it to 0
> + * on hardware that doesn't). OP must be a defined CBQRI op
> + * here. OP=0 is a no-op and would silently disable CDP.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK,
> + CBQRI_CONTROL_REGISTERS_AT_CODE);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err("BUSY timeout setting AT field\n");
> + return -EIO;
> + }
> +
> + /*
> + * If the AT field value has been reset to zero,
> + * then the AT support is not present
> + */
> + at = FIELD_GET(CBQRI_CONTROL_REGISTERS_AT_MASK, reg);
> + if (at == CBQRI_CONTROL_REGISTERS_AT_CODE)
> + *access_type_supported = true;
> + }
> +
> + /*
> + * Restore the original register value.
> + * Clear OP to avoid re-triggering the probe op.
> + */
> + saved_reg &= ~CBQRI_CONTROL_REGISTERS_OP_MASK;
> + iowrite64(saved_reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, NULL) < 0) {
> + pr_err("BUSY timeout restoring register value\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_cc(struct cbqri_controller *ctrl)
> +{
> + int err, status;
> + int ver_major, ver_minor;
> + u64 reg;
> +
> + reg = ioread64(ctrl->base + CBQRI_CC_CAPABILITIES_OFF);
> + if (reg == 0)
> + return -ENODEV;
> +
> + ver_minor = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MINOR_MASK, reg);
> + ver_major = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK, reg);
> + ctrl->cc.ncblks = FIELD_GET(CBQRI_CC_CAPABILITIES_NCBLKS_MASK, reg);
> +
> + pr_debug("version=%d.%d ncblks=%d cache_level=%d\n",
> + ver_major, ver_minor,
> + ctrl->cc.ncblks, ctrl->cache.cache_level);
> +
> + /*
> + * NCBLKS == 0 would divide-by-zero in the schemata math while
> + * ctrl->lock is held.
> + */
> + if (!ctrl->cc.ncblks) {
> + pr_warn("CC at %pa has 0 capacity blocks, skipping\n",
> + &ctrl->addr);
> + return -ENODEV;
> + }
> +
> + if (ctrl->cc.ncblks > 32) {
> + pr_warn("CC at %pa has ncblks=%u > 32 (resctrl CBM is u32), skipping\n",
> + &ctrl->addr, ctrl->cc.ncblks);
> + return -ENODEV;
> + }
> +
> + /* Probe allocation features */
> + err = cbqri_probe_feature(ctrl, CBQRI_CC_ALLOC_CTL_OFF,
> + CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + &status, &ctrl->cc.supports_alloc_at_code);
> + if (err)
> + return err;
> +
> + if (status == CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS)
> + ctrl->alloc_capable = true;
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_controller(struct cbqri_controller *ctrl)
> +{
> + int err;
> +
> + pr_debug("controller info: type=%d addr=%pa size=%pa max-rcid=%u\n",
> + ctrl->type, &ctrl->addr, &ctrl->size, ctrl->rcid_count);
> +
> + if (!ctrl->addr) {
> + pr_warn("controller has invalid addr=0x0, skipping\n");
> + return -EINVAL;
> + }
> +
> + if (ctrl->size < CBQRI_CTRL_MIN_REG_SPAN) {
> + pr_warn("controller at %pa: size %pa < minimum 0x%x, skipping\n",
> + &ctrl->addr, &ctrl->size, CBQRI_CTRL_MIN_REG_SPAN);
> + return -EINVAL;
> + }
> +
> + if (!request_mem_region(ctrl->addr, ctrl->size, "cbqri_controller")) {
> + pr_err("request_mem_region failed for %pa\n", &ctrl->addr);
> + return -EBUSY;
> + }
> +
> + ctrl->base = ioremap(ctrl->addr, ctrl->size);
> + if (!ctrl->base) {
> + pr_err("ioremap failed for %pa\n", &ctrl->addr);
> + err = -ENOMEM;
> + goto err_release;
> + }
> +
> + switch (ctrl->type) {
> + case CBQRI_CONTROLLER_TYPE_CAPACITY:
> + err = cbqri_probe_cc(ctrl);
> + break;
> + default:
> + pr_err("unknown controller type %d\n", ctrl->type);
> + err = -ENODEV;
> + break;
> + }
> +
> + if (err)
> + goto err_iounmap;
> +
> + return 0;
> +
> +err_iounmap:
> + iounmap(ctrl->base);
> + ctrl->base = NULL;
> +err_release:
> + release_mem_region(ctrl->addr, ctrl->size);
> + return err;
> +}
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl)
> +{
> + /*
> + * cbqri_probe_controller() clears ctrl->base on its error paths and
> + * releases the mem region itself, so reach into both only when
> + * destroy is rolling back a successful probe.
> + */
> + if (ctrl->base) {
> + iounmap(ctrl->base);
> + release_mem_region(ctrl->addr, ctrl->size);
> + }
> + kfree(ctrl);
> +}
> +
> +/**
> + * riscv_cbqri_register_cc_dt() - register a DT-described capacity controller
> + * @info: registration descriptor. info->cache_id is used as the
> + * resctrl domain id. info->type must be CAPACITY.
> + * @cache_level: cache level (2 or 3) the controller backs, mapped to the
> + * resctrl L2/L3 resource by the resctrl glue.
> + * @cpu_mask: CPUs that share this cache.
> + *
> + * The cache topology is supplied directly by the caller. A device-tree
> + * platform driver that already knows which CPUs share the cache and at what
> + * level passes that in. There is no firmware table to resolve it from.
> + *
> + * Return: 0 on success, or a negative errno on failure.
> + */
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + struct cbqri_controller *ctrl;
> + int err;
> +
> + if (!info->addr) {
> + pr_warn("skipping controller with invalid addr=0x0\n");
> + return -EINVAL;
> + }
> +
> + if (info->type != CBQRI_CONTROLLER_TYPE_CAPACITY) {
> + pr_warn("register_cc_dt called with non-capacity type %u\n",
> + info->type);
> + return -EINVAL;
> + }
> +
> + if (!cpu_mask || cpumask_empty(cpu_mask)) {
> + pr_warn("register_cc_dt called with empty cpu_mask\n");
> + return -EINVAL;
> + }
> +
> + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
> + if (!ctrl)
> + return -ENOMEM;
> +
> + mutex_init(&ctrl->lock);
> +
> + ctrl->addr = info->addr;
> + ctrl->size = info->size;
> + ctrl->type = info->type;
> + ctrl->rcid_count = info->rcid_count;
> +
> + /*
> + * SRMCFG encodes RCID in 12 bits. Reject an out-of-range count rather
> + * than silently truncating in every FIELD_PREP(SRMCFG_RCID_MASK, closid)
> + * on the schedule-in fast path.
> + */
> + if (ctrl->rcid_count > FIELD_MAX(SRMCFG_RCID_MASK) + 1) {
> + pr_warn("CC at %pa has RCID count %u beyond the 12-bit SRMCFG field, skipping\n",
> + &ctrl->addr, ctrl->rcid_count);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + ctrl->cache.cache_id = info->cache_id;
> + ctrl->cache.cache_level = cache_level;
> + cpumask_copy(&ctrl->cache.cpu_mask, cpu_mask);
> +
> + err = cbqri_probe_controller(ctrl);
> + if (err) {
> + cbqri_controller_destroy(ctrl);
> + return err;
> + }
> +
> + /*
> + * Allocation capability comes from the capabilities register probed
> + * above, not from device tree. rcid_count only bounds the RCID range,
> + * so a controller the hardware reports as alloc-capable but described
> + * with no RCID count cannot be driven. Reject that inconsistency. A
> + * monitoring-only controller (not alloc_capable) needs no RCID count.
> + */
> + if (ctrl->alloc_capable && !ctrl->rcid_count) {
> + pr_warn("CC at %pa is alloc-capable but has no RCID count, skipping\n",
> + &ctrl->addr);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + mutex_lock(&cbqri_controllers_lock);
> + list_add_tail(&ctrl->list, &cbqri_controllers);
> + mutex_unlock(&cbqri_controllers_lock);
> + return 0;
> +}
> diff --git a/drivers/resctrl/cbqri_internal.h b/drivers/resctrl/cbqri_internal.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..2192a3831bcde7cd4fddf03f2ce1103902e8815c
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_internal.h
> @@ -0,0 +1,107 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +#define _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +
> +/* Capacity Controller (CC) MMIO register offsets. */
> +#define CBQRI_CC_CAPABILITIES_OFF 0
> +#define CBQRI_CC_ALLOC_CTL_OFF 24
> +#define CBQRI_CC_BLOCK_MASK_OFF 32
> +
> +/*
> + * Highest defined register offset (0x20) plus the 8-byte register width.
> + * cbqri_probe_controller() rejects smaller mappings so a u64 access at
> + * BLOCK_MASK stays in bounds.
> + */
> +#define CBQRI_CTRL_MIN_REG_SPAN 0x28u
> +
> +#define CBQRI_CC_CAPABILITIES_VER_MINOR_MASK GENMASK_ULL(3, 0)
> +#define CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK GENMASK_ULL(7, 4)
> +#define CBQRI_CC_CAPABILITIES_NCBLKS_MASK GENMASK_ULL(23, 8)
> +
> +/*
> + * CC control registers are 64-bit. Keep every field mask GENMASK_ULL so
> + * FIELD_MODIFY() or ~mask on a u64 register never zero-extends a 32-bit
> + * mask and clobbers STATUS/BUSY/WPRI in bits 63:32 if RV32 support is
> + * added in the future.
> + */
> +#define CBQRI_CONTROL_REGISTERS_OP_MASK GENMASK_ULL(4, 0)
> +#define CBQRI_CONTROL_REGISTERS_AT_MASK GENMASK_ULL(7, 5)
> +/* AT field values (CBQRI Table 1): data vs code half for CDP */
> +#define CBQRI_CONTROL_REGISTERS_AT_DATA 0
> +#define CBQRI_CONTROL_REGISTERS_AT_CODE 1
> +#define CBQRI_CONTROL_REGISTERS_RCID_MASK GENMASK_ULL(19, 8)
> +#define CBQRI_CONTROL_REGISTERS_STATUS_MASK GENMASK_ULL(38, 32)
> +#define CBQRI_CONTROL_REGISTERS_BUSY_MASK GENMASK_ULL(39, 39)
> +
> +#define CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT 1
> +#define CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT 2
> +#define CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS 1
> +
> +/* Capacity Controller hardware capabilities */
> +struct riscv_cbqri_capacity_caps {
> + u16 ncblks;
> + bool supports_alloc_at_code;
> +};
> +
> +/**
> + * struct cbqri_cc_config - desired capacity allocation state for one rcid
> + * @cbm: capacity block mask
> + * @at: AT half the @cbm applies to (CBQRI_CONTROL_REGISTERS_AT_DATA
> + * or CBQRI_CONTROL_REGISTERS_AT_CODE)
> + * @cdp_enabled: when false and the controller supports AT, mirror @cbm
> + * into the other AT half so both stay in sync
> + */
> +struct cbqri_cc_config {
> + u64 cbm;
> + u32 at;
> + bool cdp_enabled;
> +};
> +
> +struct cbqri_controller {
> + void __iomem *base;
> + /*
> + * Serializes the write-then-poll-busy MMIO sequences on this
> + * controller. Each CBQRI op may busy-wait up to 1 ms on slow
> + * firmware, so use a sleeping mutex to keep preemption enabled.
> + * All resctrl-arch entry points run in process context.
> + */
> + struct mutex lock;
> +
> + struct riscv_cbqri_capacity_caps cc;
> +
> + bool alloc_capable;
> +
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> +
> + struct list_head list;
> +
> + struct cache_controller {
> + u32 cache_level;
> + struct cpumask cpu_mask;
> + /* Cache id used as the resctrl domain id */
> + u32 cache_id;
> + } cache;
> +};
> +
> +extern struct list_head cbqri_controllers;
> +extern struct mutex cbqri_controllers_lock;
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl);
> +
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg);
> +
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out);
> +
> +#endif /* _DRIVERS_RESCTRL_CBQRI_INTERNAL_H */
> diff --git a/include/linux/riscv_cbqri.h b/include/linux/riscv_cbqri.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..58737224d2f2c67a443f78f9fea7f368c7d16044
> --- /dev/null
> +++ b/include/linux/riscv_cbqri.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Public registration API for the RISC-V Capacity and Bandwidth QoS
> + * Register Interface (CBQRI) core. Discovery layers (device tree
> + * platform drivers) call riscv_cbqri_register_cc_dt() to hand a capacity
> + * controller descriptor to the core, which owns all subsequent state.
> + */
> +#ifndef _LINUX_RISCV_CBQRI_H
> +#define _LINUX_RISCV_CBQRI_H
> +
> +#include <linux/types.h>
> +
> +struct cpumask;
> +
> +enum cbqri_controller_type {
> + CBQRI_CONTROLLER_TYPE_CAPACITY,
> +};
> +
> +/**
> + * struct cbqri_controller_info - registration descriptor
> + * @addr: MMIO base address of the controller's register interface
> + * @size: size of the MMIO region
> + * @type: controller type (capacity)
> + * @rcid_count: number of supported RCIDs
> + * @cache_id: cache id used as the resctrl domain id
> + */
> +struct cbqri_controller_info {
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> + u32 cache_id;
> +};
> +
> +#if IS_ENABLED(CONFIG_RISCV_CBQRI)
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask);
> +#else
> +static inline int
> +riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + return -ENODEV;
> +}
> +#endif
> +
> +#endif /* _LINUX_RISCV_CBQRI_H */
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox