From 9480dd7ec3fcadd4217503e4bb32c16e19e4b21f Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 26 May 2026 11:50:58 -0700 Subject: [PATCH 001/167] accel/amdxdna: Disable device buffer exporting Device buffers are never intended to be exported. Disable exporting support explicitly. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260526185058.1780869-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 00efa8abfeea..77e5fad08ae5 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -728,9 +728,15 @@ static int amdxdna_gem_dev_obj_vmap(struct drm_gem_object *obj, struct iosys_map return 0; } +static struct dma_buf *amdxdna_gem_dev_obj_export(struct drm_gem_object *gobj, int flags) +{ + return ERR_PTR(-EOPNOTSUPP); +} + static const struct drm_gem_object_funcs amdxdna_gem_dev_obj_funcs = { .free = amdxdna_gem_dev_obj_free, .vmap = amdxdna_gem_dev_obj_vmap, + .export = amdxdna_gem_dev_obj_export, }; static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { From 38b4ce17ef3421fb0e5e6dbdab1974282bde1165 Mon Sep 17 00:00:00 2001 From: Yicong Hui Date: Thu, 9 Apr 2026 16:48:26 +0100 Subject: [PATCH 002/167] drm: Remove unused header in drm_dumb_buffers.c Remove the header #include "drm_internal.h" from drm_dumb_buffers.c, which is included but not used. Header was introduced in commit 47f10854ca89 ("drm: Don't export the drm_gem_dumb_destroy() function") when moving functions, but was not removed in commit 96a7b60f6ddb ("drm: remove dumb_destroy callback") when the drm_gem_dumb_destroy function was removed. Compiles successfully with DRM enabled, pass kunit tests and IGT-tests in a vng virtual machine. Fixes: 96a7b60f6ddb ("drm: remove dumb_destroy callback") Signed-off-by: Yicong Hui Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260409154826.8955-1-yiconghui@gmail.com --- drivers/gpu/drm/drm_dumb_buffers.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/drm_dumb_buffers.c b/drivers/gpu/drm/drm_dumb_buffers.c index e2b62e5fb891..61bf3c9b7182 100644 --- a/drivers/gpu/drm/drm_dumb_buffers.c +++ b/drivers/gpu/drm/drm_dumb_buffers.c @@ -32,7 +32,6 @@ #include #include "drm_crtc_internal.h" -#include "drm_internal.h" /** * DOC: overview From 645abe00a34588f27c856ca2b532642a8ef7691b Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 30 Apr 2026 11:15:32 +0200 Subject: [PATCH 003/167] drm/gm12u320: Replace system_long_wq with system_dfl_long_wq Currently the code enqueue work items using {queue|mod}_delayed_work(), using system_long_wq. This workqueue should be used when long works are expected, but it is a per-cpu workqueue. This is important because queue_delayed_work() queue the work using: queue_delayed_work_on(WORK_CPU_UNBOUND, ...); Note that WORK_CPU_UNBOUND = NR_CPUS. This would end up calling __queue_delayed_work() that does: if (housekeeping_enabled(HK_TYPE_TIMER)) { // [....] } else { if (likely(cpu == WORK_CPU_UNBOUND)) add_timer_global(timer); else add_timer_on(timer, cpu); } So when cpu == WORK_CPU_UNBOUND the timer is global and is not using a specific CPU. Later, when __queue_work() is called: if (req_cpu == WORK_CPU_UNBOUND) { if (wq->flags & WQ_UNBOUND) cpu = wq_select_unbound_cpu(raw_smp_processor_id()); else cpu = raw_smp_processor_id(); } Because the wq is not unbound, it takes the CPU where the timer fired and enqueue the work on that CPU. The consequence of all of this is that the work can run anywhere, depending on where the timer fired. Recently, a new unbound workqueue specific for long running work has been added in commit c116737e972e ("workqueue: Add system_dfl_long_wq for long unbound works"). So change system_long_wq with system_dfl_long_wq so that the work may benefit from scheduler task placement. Signed-off-by: Marco Crivellari Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260430091532.115846-1-marco.crivellari@suse.com --- drivers/gpu/drm/tiny/gm12u320.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tiny/gm12u320.c b/drivers/gpu/drm/tiny/gm12u320.c index d73dfebb4353..4ad074337af0 100644 --- a/drivers/gpu/drm/tiny/gm12u320.c +++ b/drivers/gpu/drm/tiny/gm12u320.c @@ -388,7 +388,7 @@ static void gm12u320_fb_update_work(struct work_struct *work) * We must draw a frame every 2s otherwise the projector * switches back to showing its logo. */ - queue_delayed_work(system_long_wq, &gm12u320->fb_update.work, + queue_delayed_work(system_dfl_long_wq, &gm12u320->fb_update.work, msecs_to_jiffies(IDLE_TIMEOUT)); return; @@ -427,7 +427,8 @@ static void gm12u320_fb_mark_dirty(struct drm_framebuffer *fb, mutex_unlock(&gm12u320->fb_update.lock); if (wakeup) - mod_delayed_work(system_long_wq, &gm12u320->fb_update.work, 0); + mod_delayed_work(system_dfl_long_wq, + &gm12u320->fb_update.work, 0); if (old_fb) drm_framebuffer_put(old_fb); From 2b0fb8a0aa50cd36c94d414f3d4b02766cfdfec7 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:13 +0200 Subject: [PATCH 004/167] drm/atomic: Document atomic commit lifetime How drm_atomic_commit and the various entity structures are allocated and freed isn't really trivial. Document it. Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-1-852346394200@kernel.org Signed-off-by: Maxime Ripard --- Documentation/gpu/drm-kms.rst | 6 +++ drivers/gpu/drm/drm_atomic.c | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst index fa69a5450f96..e4f7153543b3 100644 --- a/Documentation/gpu/drm-kms.rst +++ b/Documentation/gpu/drm-kms.rst @@ -287,6 +287,12 @@ structure, ordering of committing state changes to hardware is sequenced using Read on in this chapter, and also in :ref:`drm_atomic_helper` for more detailed coverage of specific topics. +Atomic State Lifetime +--------------------- + +.. kernel-doc:: drivers/gpu/drm/drm_atomic.c + :doc: state lifetime + Handling Driver Private State ----------------------------- diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 170de30c28ae..a1bcaaf71eea 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -47,6 +47,78 @@ #include "drm_crtc_internal.h" #include "drm_internal.h" +/** + * DOC: state lifetime + * + * &drm_atomic_commit represents an update to modeset pipeline state. + * It's a transient object that holds a state update as a collection of + * pointers to individual objects' states. &drm_atomic_commit has a much + * shorter lifetime than the objects' states, since it's only allocated + * while preparing, checking or committing the update, while object + * states are allocated when preparing the update and kept alive as long + * as they are active in the device. + * + * Their respective lifetimes are: + * + * - at reset time, the object reset implementation allocates a new + * default state and stores it in the object state pointer. + * + * - whenever a new update is needed: + * + * + drm_atomic_commit_alloc() allocates a new &drm_atomic_commit + * instance. + * + * + The code triggering the commit (ioctl, client modeset, + * drm_atomic_helper_reset_crtc(), etc.) copies the current active + * state of all entities affected by the update into this new + * &drm_atomic_commit using drm_atomic_get_plane_state(), + * drm_atomic_get_crtc_state(), drm_atomic_get_connector_state(), or + * drm_atomic_get_private_obj_state(). This new state can then be + * modified. + * + * At that point, &drm_atomic_commit stores three state pointers for + * any affected entity: the "old" and "new" states, and + * state_to_destroy. The old state is the state currently active in + * the hardware, which is either the one initialized by reset() or a + * newer one if a commit has been made. The new state is the state + * we just allocated and we might eventually commit to the hardware. + * The state_to_destroy points to the state we'll eventually have to + * free when the drm_atomic_commit will be destroyed, and points to + * the new state for now since the old state is still the active + * state. + * + * + After the calling code populated the commit with the entities + * states, it updates the new states with the new values we need to + * commit. The new commit instance is now ready. + * + * + Then we have two branches depending on the calling code intent: + * + * - If the calling code only wants to check that the commit would + * work (for example because of the DRM_MODE_ATOMIC_TEST_ONLY + * flag). It calls drm_atomic_check_only(), which in turn checks + * all these states by invoking atomic_check on all affected + * pipeline stages. + * + * - If the calling code actually wants to trigger a commit, it + * calls drm_atomic_commit(). The first stage is the check + * mentioned above, and if the check is successful, it performs + * the commit. Part of the commit is a call to + * drm_atomic_helper_swap_state() which turns the new states into + * the active states. After swapping states, each object's state + * pointer now refers to the formerly new state. The + * state_to_destroy now refers to the formerly old state. + * + * + Once done, and when the last reference to our &drm_atomic_commit + * is given up through drm_atomic_commit_put(), it calls + * __drm_atomic_commit_free(). In turn, __drm_atomic_commit_free() + * calls drm_atomic_commit_clear() that will free all + * state_to_destroy (ie. old states), and it finally frees + * &drm_atomic_commit instance. + * + * + Now, we don't have any active &drm_atomic_commit anymore, and + * only the entity active states remain allocated. + */ + void __drm_crtc_commit_free(struct kref *kref) { struct drm_crtc_commit *commit = From 8cd7d745faa6a9a9fb4de8872cccc1960e6f0104 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:14 +0200 Subject: [PATCH 005/167] drm/colorop: Fix typos in the doc In the documentation of drm_colorop introduced by commit cfc27680ee20 ("drm/colorop: Introduce new drm_colorop mode object"), the documentation of __drm_colorop_state_reset() and __drm_colorop_reset() were mentioning CRTC when they really meant colorop, probably due to copy and paste. Fixes: cfc27680ee20 ("drm/colorop: Introduce new drm_colorop mode object") Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-2-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_colorop.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c index 764d12060666..48d0b7ae3fc9 100644 --- a/drivers/gpu/drm/drm_colorop.c +++ b/drivers/gpu/drm/drm_colorop.c @@ -505,7 +505,7 @@ void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop, * @colorop: colorop object, must not be NULL * * Initializes the newly allocated @colorop_state with default - * values. This is useful for drivers that subclass the CRTC state. + * values. This is useful for drivers that subclass the colorop state. */ static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state, struct drm_colorop *colorop) @@ -528,10 +528,10 @@ static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state, * @colorop: drm colorop * @colorop_state: colorop state to assign * - * Initializes the newly allocated @colorop_state and assigns it to - * the &drm_crtc->state pointer of @colorop, usually required when - * initializing the drivers or when called from the &drm_colorop_funcs.reset - * hook. + * Initializes the newly allocated @colorop_state and assigns it to the + * &drm_colorop->state pointer of @colorop, usually required when + * initializing the drivers or when called from the + * &drm_colorop_funcs.reset hook. * * This is useful for drivers that subclass the colorop state. */ From dcf0292f4828341474428d016661b6c292d7211c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:15 +0200 Subject: [PATCH 006/167] drm/atomic: Drop drm_private_obj.state assignment from create_state The initial intent of the atomic_create_state hook was to simply allocate a proper drm_private_state and return it, without any side effect. However, __drm_atomic_helper_private_obj_create_state(), which most atomic_create_state implementations call, introduces a side effect by setting drm_private_obj.state to the newly allocated state. This assignment defeats the purpose, but is also redundant since drm_atomic_private_obj_init(), the only call site for the atomic_create_state hook, will also set this pointer to the newly allocated state. Drop the assignment in __drm_atomic_helper_private_obj_create_state(). Fixes: e7be39ed1716 ("drm/atomic-helper: Add private_obj atomic_create_state helper") Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-3-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index cc70508d4fdb..a82568d87e4f 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -731,8 +731,6 @@ void __drm_atomic_helper_private_obj_create_state(struct drm_private_obj *obj, { if (state) state->obj = obj; - - obj->state = state; } EXPORT_SYMBOL(__drm_atomic_helper_private_obj_create_state); From bde05d3527eadaf3164bac6a04a938acfde14d0e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:16 +0200 Subject: [PATCH 007/167] drm/atomic: Expand atomic_create_state expectations for drm_private_obj The atomic_create_state callback documentation for planes, CRTCs, and connectors explicitly states the expected behaviour: the returned state must not be assigned to the object's state pointer, and hardware must not be touched. The drm_private_state_funcs.atomic_create_state documentation is missing this clarification. Add it for consistency. Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-4-852346394200@kernel.org Signed-off-by: Maxime Ripard --- include/drm/drm_atomic.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 1a80a8cdf269..88087910ab1a 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -265,7 +265,10 @@ struct drm_private_state_funcs { * @atomic_create_state: * * Allocates a pristine, initialized, state for the private - * object and returns it. + * object and returns it. This callback must have no side + * effects: in particular, the returned state must not be + * assigned to the object's state pointer and it must not affect + * the hardware state. * * RETURNS: * From d37a0bac8682785954c04fd9f717707188194f47 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:17 +0200 Subject: [PATCH 008/167] drm/mode-config: Document drm_private_obj exclusion from drm_mode_config_reset() drm_mode_config_reset() does not reset drm_private_states by design. This is especially significant for the DP MST and tunneling code that expect to be preserved across a suspend/resume cycle, where drm_mode_config_reset() is also used. Document this expectation. Link: https://lore.kernel.org/dri-devel/aOaQLx-7EpsHRwkH@ideak-desk/ Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-5-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_mode_config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 66f7dc37b597..c33382a38191 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -189,6 +189,10 @@ int drm_mode_getresources(struct drm_device *dev, void *data, * This functions calls all the crtc's, encoder's and connector's ->reset * callback. Drivers can use this in e.g. their driver load or resume code to * reset hardware and software state. + * + * Note that &drm_private_obj structures are expected to be stable across + * suspend/resume cycles, and drm_mode_config_reset() does not affect these + * structures. */ void drm_mode_config_reset(struct drm_device *dev) { From 8e135a47d3586234f1a2714dcf0a19e49c9a5354 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:18 +0200 Subject: [PATCH 009/167] drm/colorop: Rename __drm_colorop_state_reset() __drm_colorop_state_reset() is used to initialize a newly allocated drm_colorop_state, and is being typically called by drm_colorop_reset(). Since we want to consolidate DRM objects state allocation around the atomic_create_state callback that will only allocate and initialize a new drm_colorop_state instance, we will need to call __drm_colorop_state_reset() from both the reset and atomic_create paths. To avoid any confusion, we can thus rename __drm_colorop_state_reset() to __drm_colorop_state_init(). Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-6-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_colorop.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c index 48d0b7ae3fc9..4c4d0a953e35 100644 --- a/drivers/gpu/drm/drm_colorop.c +++ b/drivers/gpu/drm/drm_colorop.c @@ -500,15 +500,15 @@ void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop, } /** - * __drm_colorop_state_reset - resets colorop state to default values + * __drm_colorop_state_init - Initializes colorop state to default values * @colorop_state: atomic colorop state, must not be NULL * @colorop: colorop object, must not be NULL * * Initializes the newly allocated @colorop_state with default * values. This is useful for drivers that subclass the colorop state. */ -static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state, - struct drm_colorop *colorop) +static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state, + struct drm_colorop *colorop) { u64 val; @@ -539,7 +539,7 @@ static void __drm_colorop_reset(struct drm_colorop *colorop, struct drm_colorop_state *colorop_state) { if (colorop_state) - __drm_colorop_state_reset(colorop_state, colorop); + __drm_colorop_state_init(colorop_state, colorop); colorop->state = colorop_state; } From 8497ae67d3788fab2fdde38c8e51591f706debfd Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:19 +0200 Subject: [PATCH 010/167] drm/colorop: Create drm_atomic_helper_colorop_create_state() Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj") introduced a new pattern for allocating drm object states. Instead of relying on the reset() callback, it created a new atomic_create_state hook. This is helpful because reset is a bit overloaded: it's used to create the initial software state, reset it, but also reset the hardware. It can also be used either at probe time, to create the initial state and possibly reset the hardware to an expected default, but also during suspend/resume. Both these cases come with different expectations too: during the initialization, we want to initialize all states, but during suspend/resume, drm_private_states for example are expected to be kept around. reset() also isn't fallible, which makes it harder to handle initialization errors properly. This is only really relevant for some drivers though, since all the helpers for reset only create a new state, and don't touch the hardware at all. It was thus decided to create a new hook that would allocate and initialize a pristine state without any side effect: atomic_create_state to untangle a bit some of it, and to separate the initialization with the actual reset one might need during a suspend/resume. Continue the transition to the new pattern with drm_colorop. Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-7-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_colorop.c | 23 +++++++++++++++++++++++ include/drm/drm_colorop.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c index 4c4d0a953e35..c0eecde8c176 100644 --- a/drivers/gpu/drm/drm_colorop.c +++ b/drivers/gpu/drm/drm_colorop.c @@ -523,6 +523,29 @@ static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state, } } +/** + * drm_atomic_helper_colorop_create_state - Allocates and initializes colorop atomic state + * @colorop: drm colorop + * + * Initializes a pristine @drm_colorop_state. + * + * RETURNS: + * Pointer to new colorop state, or ERR_PTR on failure. + */ +struct drm_colorop_state * +drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop) +{ + struct drm_colorop_state *state; + + state = kzalloc_obj(*state); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_colorop_state_init(state, colorop); + + return state; +} + /** * __drm_colorop_reset - reset state on colorop * @colorop: drm colorop diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h index c873199c60da..b4b9e4f558ab 100644 --- a/include/drm/drm_colorop.h +++ b/include/drm/drm_colorop.h @@ -425,6 +425,8 @@ int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *col enum drm_colorop_lut3d_interpolation_type interpolation, uint32_t flags); +struct drm_colorop_state * +drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop); struct drm_colorop_state * drm_atomic_helper_colorop_duplicate_state(struct drm_colorop *colorop); From 104ac1687b4bc6e3b1f79e86ba0350b2914d1091 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:20 +0200 Subject: [PATCH 011/167] drm/atomic-state-helper: Fix __drm_atomic_helper_plane_reset() doc typo A typo has slipped through in the __drm_atomic_helper_plane_reset() documentation, probably due to copy and paste. It will not assign drm_crtc state pointer, but rather the drm_plane's. Reviewed-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-8-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index a82568d87e4f..c8ccf8be5074 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -305,7 +305,7 @@ EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset); * @plane_state: plane state to assign * * Initializes the newly allocated @plane_state and assigns it to - * the &drm_crtc->state pointer of @plane, usually required when + * the &drm_plane->state pointer of @plane, usually required when * initializing the drivers or when called from the &drm_plane_funcs.reset * hook. * From 3e5656ea5e7360dc98ec38ecfa9b971ace390781 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:21 +0200 Subject: [PATCH 012/167] drm/atomic-state-helper: Rename __drm_atomic_helper_plane_state_reset() __drm_atomic_helper_plane_state_reset() is used to initialize a newly allocated drm_plane_state, and is being typically called by the drm_plane_funcs.reset implementation. Since we want to consolidate DRM objects state allocation around the atomic_create_state callback that will only allocate and initialize a new drm_plane_state instance, we will need to call __drm_atomic_helper_plane_state_reset() from both the reset and atomic_create hooks. To avoid any confusion, we can thus rename __drm_atomic_helper_plane_state_reset() to __drm_atomic_helper_plane_state_init(). Suggested-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-9-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 12 ++++++------ drivers/gpu/drm/i915/display/intel_plane.c | 2 +- include/drm/drm_atomic_state_helper.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index c8ccf8be5074..ee01700d4ca7 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -237,15 +237,15 @@ void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc, EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state); /** - * __drm_atomic_helper_plane_state_reset - resets plane state to default values + * __drm_atomic_helper_plane_state_init - Initialize the plane state * @plane_state: atomic plane state, must not be NULL * @plane: plane object, must not be NULL * * Initializes the newly allocated @plane_state with default - * values. This is useful for drivers that subclass the CRTC state. + * values. This is useful for drivers that subclass the plane state. */ -void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state, - struct drm_plane *plane) +void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state, + struct drm_plane *plane) { u64 val; @@ -297,7 +297,7 @@ void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state, plane_state->hotspot_y = val; } } -EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset); +EXPORT_SYMBOL(__drm_atomic_helper_plane_state_init); /** * __drm_atomic_helper_plane_reset - reset state on plane @@ -315,7 +315,7 @@ void __drm_atomic_helper_plane_reset(struct drm_plane *plane, struct drm_plane_state *plane_state) { if (plane_state) - __drm_atomic_helper_plane_state_reset(plane_state, plane); + __drm_atomic_helper_plane_state_init(plane_state, plane); plane->state = plane_state; } diff --git a/drivers/gpu/drm/i915/display/intel_plane.c b/drivers/gpu/drm/i915/display/intel_plane.c index a1f9558d53af..43b3547096ae 100644 --- a/drivers/gpu/drm/i915/display/intel_plane.c +++ b/drivers/gpu/drm/i915/display/intel_plane.c @@ -67,7 +67,7 @@ static void intel_plane_state_reset(struct intel_plane_state *plane_state, { memset(plane_state, 0, sizeof(*plane_state)); - __drm_atomic_helper_plane_state_reset(&plane_state->uapi, &plane->base); + __drm_atomic_helper_plane_state_init(&plane_state->uapi, &plane->base); plane_state->scaler_id = -1; plane_state->fence_id = -1; diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 61a3b38ad49f..691c1ccfa4e0 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -53,7 +53,7 @@ void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc_state *state); void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state); -void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *state, +void __drm_atomic_helper_plane_state_init(struct drm_plane_state *state, struct drm_plane *plane); void __drm_atomic_helper_plane_reset(struct drm_plane *plane, struct drm_plane_state *state); From 1a185ddecaf9609e76ed289037afd6cb289e7bee Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:22 +0200 Subject: [PATCH 013/167] drm/plane: Add new atomic_create_state callback Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj") introduced a new pattern for allocating drm object states. Instead of relying on the reset() callback, it created a new atomic_create_state hook. This is helpful because reset is a bit overloaded: it's used to create the initial software state, reset it, but also reset the hardware. It can also be used either at probe time, to create the initial state and possibly reset the hardware to an expected default, but also during suspend/resume. Both these cases come with different expectations too: during the initialization, we want to initialize all states, but during suspend/resume, drm_private_states for example are expected to be kept around. reset() also isn't fallible, which makes it harder to handle initialization errors properly. This is only really relevant for some drivers though, since all the helpers for reset only create a new state, and don't touch the hardware at all. It was thus decided to create a new hook that would allocate and initialize a pristine state without any side effect: atomic_create_state to untangle a bit some of it, and to separate the initialization with the actual reset one might need during a suspend/resume. Continue the transition to the new pattern with planes. Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-10-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 25 ++++++++++++++++++ drivers/gpu/drm/drm_mode_config.c | 31 ++++++++++++++++++++++- include/drm/drm_atomic_state_helper.h | 2 ++ include/drm/drm_plane.h | 16 ++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index ee01700d4ca7..ab171bfe6e86 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -340,6 +340,31 @@ void drm_atomic_helper_plane_reset(struct drm_plane *plane) } EXPORT_SYMBOL(drm_atomic_helper_plane_reset); +/** + * drm_atomic_helper_plane_create_state - default &drm_plane_funcs.atomic_create_state hook for planes + * @plane: plane object + * + * Allocates and initializes pristine @drm_plane_state. + * + * This is useful for drivers that don't subclass @drm_plane_state. + * + * RETURNS: + * Pointer to new plane state, or ERR_PTR on failure. + */ +struct drm_plane_state *drm_atomic_helper_plane_create_state(struct drm_plane *plane) +{ + struct drm_plane_state *state; + + state = kzalloc_obj(*state); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_plane_state_init(state, plane); + + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_plane_create_state); + /** * __drm_atomic_helper_plane_duplicate_state - copy atomic plane state * @plane: plane object diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index c33382a38191..fa609357858f 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -182,6 +182,32 @@ int drm_mode_getresources(struct drm_device *dev, void *data, return ret; } +static int drm_mode_config_plane_create_state(struct drm_plane *plane) +{ + struct drm_plane_state *plane_state; + + if (!plane->funcs->atomic_create_state) + return 0; + + plane_state = plane->funcs->atomic_create_state(plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + + plane->state = plane_state; + + return 0; +} + +static int drm_mode_config_plane_reset_with_create_state(struct drm_plane *plane) +{ + if (plane->state) { + plane->funcs->atomic_destroy_state(plane, plane->state); + plane->state = NULL; + } + + return drm_mode_config_plane_create_state(plane); +} + /** * drm_mode_config_reset - call ->reset callbacks * @dev: drm device @@ -206,9 +232,12 @@ void drm_mode_config_reset(struct drm_device *dev) drm_for_each_colorop(colorop, dev) drm_colorop_reset(colorop); - drm_for_each_plane(plane, dev) + drm_for_each_plane(plane, dev) { if (plane->funcs->reset) plane->funcs->reset(plane); + else if (plane->funcs->atomic_create_state) + drm_mode_config_plane_reset_with_create_state(plane); + } drm_for_each_crtc(crtc, dev) if (crtc->funcs->reset) diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 691c1ccfa4e0..8d1ef268fdef 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -55,6 +55,8 @@ void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc, void __drm_atomic_helper_plane_state_init(struct drm_plane_state *state, struct drm_plane *plane); +struct drm_plane_state * +drm_atomic_helper_plane_create_state(struct drm_plane *plane); void __drm_atomic_helper_plane_reset(struct drm_plane *plane, struct drm_plane_state *state); void drm_atomic_helper_plane_reset(struct drm_plane *plane); diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h index 419c88c873a6..2c5a5a70a71b 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -388,6 +388,22 @@ struct drm_plane_funcs { int (*set_property)(struct drm_plane *plane, struct drm_property *property, uint64_t val); + /** + * @atomic_create_state: + * + * Allocate a pristine, initialized, state for the plane object + * and return it. This callback must have no side effects: in + * particular, the returned state must not be assigned to the + * object's state pointer and it must not affect the hardware + * state. + * + * RETURNS: + * + * A new, pristine, plane state instance or an error pointer + * on failure. + */ + struct drm_plane_state *(*atomic_create_state)(struct drm_plane *plane); + /** * @atomic_duplicate_state: * From 915fdd2c7e87c5af4fe2d1a0ef4fac515da6ef9b Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:23 +0200 Subject: [PATCH 014/167] drm/atomic-state-helper: Rename __drm_atomic_helper_crtc_state_reset() __drm_atomic_helper_crtc_state_reset() is used to initialize a newly allocated drm_crtc_state, and is being typically called by the drm_crtc_funcs.reset implementation. Since we want to consolidate DRM objects state allocation around the atomic_create_state callback that will only allocate and initialize a new drm_crtc_state instance, we will need to call __drm_atomic_helper_crtc_state_reset() from both the reset and atomic_create hooks. To avoid any confusion, we can thus rename __drm_atomic_helper_crtc_state_reset() to __drm_atomic_helper_crtc_state_init(). Suggested-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-11-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 10 +++++----- drivers/gpu/drm/i915/display/intel_crtc.c | 2 +- include/drm/drm_atomic_state_helper.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index ab171bfe6e86..b277f92f4532 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -63,7 +63,7 @@ */ /** - * __drm_atomic_helper_crtc_state_reset - reset the CRTC state + * __drm_atomic_helper_crtc_state_init - Initialize the CRTC state * @crtc_state: atomic CRTC state, must not be NULL * @crtc: CRTC object, must not be NULL * @@ -71,13 +71,13 @@ * values. This is useful for drivers that subclass the CRTC state. */ void -__drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *crtc_state, - struct drm_crtc *crtc) +__drm_atomic_helper_crtc_state_init(struct drm_crtc_state *crtc_state, + struct drm_crtc *crtc) { crtc_state->crtc = crtc; crtc_state->background_color = DRM_ARGB64_PREP(0xffff, 0, 0, 0); } -EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset); +EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_init); /** * __drm_atomic_helper_crtc_reset - reset state on CRTC @@ -96,7 +96,7 @@ __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) { if (crtc_state) - __drm_atomic_helper_crtc_state_reset(crtc_state, crtc); + __drm_atomic_helper_crtc_state_init(crtc_state, crtc); if (drm_dev_has_vblank(crtc->dev)) drm_crtc_vblank_reset(crtc); diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c index 03de219f7a64..7486f2dc60ef 100644 --- a/drivers/gpu/drm/i915/display/intel_crtc.c +++ b/drivers/gpu/drm/i915/display/intel_crtc.c @@ -181,7 +181,7 @@ void intel_crtc_state_reset(struct intel_crtc_state *crtc_state, { memset(crtc_state, 0, sizeof(*crtc_state)); - __drm_atomic_helper_crtc_state_reset(&crtc_state->uapi, &crtc->base); + __drm_atomic_helper_crtc_state_init(&crtc_state->uapi, &crtc->base); crtc_state->cpu_transcoder = INVALID_TRANSCODER; crtc_state->master_transcoder = INVALID_TRANSCODER; diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 8d1ef268fdef..0bb72453464a 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -40,7 +40,7 @@ struct drm_private_state; struct drm_modeset_acquire_ctx; struct drm_device; -void __drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *state, +void __drm_atomic_helper_crtc_state_init(struct drm_crtc_state *state, struct drm_crtc *crtc); void __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc, struct drm_crtc_state *state); From 58d426b5ed0d3ec6d7f3df32e32aab2c5b1daab5 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:24 +0200 Subject: [PATCH 015/167] drm/crtc: Add new atomic_create_state callback Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj") introduced a new pattern for allocating drm object states. Instead of relying on the reset() callback, it created a new atomic_create_state hook. This is helpful because reset is a bit overloaded: it's used to create the initial software state, reset it, but also reset the hardware. It can also be used either at probe time, to create the initial state and possibly reset the hardware to an expected default, but also during suspend/resume. Both these cases come with different expectations too: during the initialization, we want to initialize all states, but during suspend/resume, drm_private_states for example are expected to be kept around. reset() also isn't fallible, which makes it harder to handle initialization errors properly. This is only really relevant for some drivers though, since all the helpers for reset only create a new state, and don't touch the hardware at all. It was thus decided to create a new hook that would allocate and initialize a pristine state without any side effect: atomic_create_state to untangle a bit some of it, and to separate the initialization with the actual reset one might need during a suspend/resume. Continue the transition to the new pattern with CRTCs. Reviewed-by: Dmitry Baryshkov Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-12-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 25 +++++++++++++++++ drivers/gpu/drm/drm_mode_config.c | 34 ++++++++++++++++++++++- include/drm/drm_atomic_state_helper.h | 2 ++ include/drm/drm_crtc.h | 16 +++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index b277f92f4532..8762171c9432 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -124,6 +124,31 @@ void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc) } EXPORT_SYMBOL(drm_atomic_helper_crtc_reset); +/** + * drm_atomic_helper_crtc_create_state - default &drm_crtc_funcs.atomic_create_state hook for crtcs + * @crtc: crtc object + * + * Allocates and initializes pristine @drm_crtc_state. + * + * This is useful for drivers that don't subclass @drm_crtc_state. + * + * RETURNS: + * Pointer to new crtc state, or ERR_PTR on failure. + */ +struct drm_crtc_state *drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc) +{ + struct drm_crtc_state *state; + + state = kzalloc_obj(*state); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_crtc_state_init(state, crtc); + + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_crtc_create_state); + /** * __drm_atomic_helper_crtc_duplicate_state - copy atomic CRTC state * @crtc: CRTC object diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index fa609357858f..2e2cd18a14b4 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -208,6 +208,35 @@ static int drm_mode_config_plane_reset_with_create_state(struct drm_plane *plane return drm_mode_config_plane_create_state(plane); } +static int drm_mode_config_crtc_create_state(struct drm_crtc *crtc) +{ + struct drm_crtc_state *crtc_state; + + if (!crtc->funcs->atomic_create_state) + return 0; + + crtc_state = crtc->funcs->atomic_create_state(crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (drm_dev_has_vblank(crtc->dev)) + drm_crtc_vblank_reset(crtc); + + crtc->state = crtc_state; + + return 0; +} + +static int drm_mode_config_crtc_reset_with_create_state(struct drm_crtc *crtc) +{ + if (crtc->state) { + crtc->funcs->atomic_destroy_state(crtc, crtc->state); + crtc->state = NULL; + } + + return drm_mode_config_crtc_create_state(crtc); +} + /** * drm_mode_config_reset - call ->reset callbacks * @dev: drm device @@ -239,9 +268,12 @@ void drm_mode_config_reset(struct drm_device *dev) drm_mode_config_plane_reset_with_create_state(plane); } - drm_for_each_crtc(crtc, dev) + drm_for_each_crtc(crtc, dev) { if (crtc->funcs->reset) crtc->funcs->reset(crtc); + else if (crtc->funcs->atomic_create_state) + drm_mode_config_crtc_reset_with_create_state(crtc); + } drm_for_each_encoder(encoder, dev) if (encoder->funcs && encoder->funcs->reset) diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 0bb72453464a..213f7e298008 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -45,6 +45,8 @@ void __drm_atomic_helper_crtc_state_init(struct drm_crtc_state *state, void __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc, struct drm_crtc_state *state); void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc); +struct drm_crtc_state * +drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc); void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc, struct drm_crtc_state *state); struct drm_crtc_state * diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index c6dbe8b7db9e..152349f973e3 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -638,6 +638,22 @@ struct drm_crtc_funcs { int (*set_property)(struct drm_crtc *crtc, struct drm_property *property, uint64_t val); + /** + * @atomic_create_state: + * + * Allocate a pristine, initialized, state for the CRTC object + * and return it. This callback must have no side effects: in + * particular, the returned state must not be assigned to the + * object's state pointer and it must not affect the hardware + * state. + * + * RETURNS: + * + * A new, pristine, CRTC state instance or an error pointer + * on failure. + */ + struct drm_crtc_state *(*atomic_create_state)(struct drm_crtc *crtc); + /** * @atomic_duplicate_state: * From b74940afe5bc06dff15dcbfcb01500177574c778 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:25 +0200 Subject: [PATCH 016/167] drm/atomic-state-helper: Rename __drm_atomic_helper_connector_state_reset() __drm_atomic_helper_connector_state_reset() is used to initialize a newly allocated drm_connector_state, and is being typically called by the drm_connector_funcs.reset implementation. Since we want to consolidate DRM objects state allocation around the atomic_create_state callback that will only allocate and initialize a new drm_connector_state instance, we will need to call __drm_atomic_helper_connector_state_reset() from both the reset and atomic_create hooks. To avoid any confusion, we can thus rename __drm_atomic_helper_connector_state_reset() to __drm_atomic_helper_connector_state_init(). Suggested-by: Laurent Pinchart Reviewed-by: Dmitry Baryshkov Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-13-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 12 ++++++------ include/drm/drm_atomic_state_helper.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index 8762171c9432..e2e5a1b8a820 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -476,20 +476,20 @@ void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane, EXPORT_SYMBOL(drm_atomic_helper_plane_destroy_state); /** - * __drm_atomic_helper_connector_state_reset - reset the connector state + * __drm_atomic_helper_connector_state_init - Initialize the connector state * @conn_state: atomic connector state, must not be NULL - * @connector: connectotr object, must not be NULL + * @connector: connector object, must not be NULL * * Initializes the newly allocated @conn_state with default * values. This is useful for drivers that subclass the connector state. */ void -__drm_atomic_helper_connector_state_reset(struct drm_connector_state *conn_state, - struct drm_connector *connector) +__drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state, + struct drm_connector *connector) { conn_state->connector = connector; } -EXPORT_SYMBOL(__drm_atomic_helper_connector_state_reset); +EXPORT_SYMBOL(__drm_atomic_helper_connector_state_init); /** * __drm_atomic_helper_connector_reset - reset state on connector @@ -508,7 +508,7 @@ __drm_atomic_helper_connector_reset(struct drm_connector *connector, struct drm_connector_state *conn_state) { if (conn_state) - __drm_atomic_helper_connector_state_reset(conn_state, connector); + __drm_atomic_helper_connector_state_init(conn_state, connector); connector->state = conn_state; } diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 213f7e298008..9634a70e0401 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -70,7 +70,7 @@ void __drm_atomic_helper_plane_destroy_state(struct drm_plane_state *state); void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane, struct drm_plane_state *state); -void __drm_atomic_helper_connector_state_reset(struct drm_connector_state *conn_state, +void __drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state, struct drm_connector *connector); void __drm_atomic_helper_connector_reset(struct drm_connector *connector, struct drm_connector_state *conn_state); From f029d933561e6b79b86c726db93893a54001b461 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:26 +0200 Subject: [PATCH 017/167] drm/hdmi: Rename __drm_atomic_helper_connector_hdmi_reset() __drm_atomic_helper_connector_hdmi_reset() is typically used to initialize a newly allocated drm_connector_state when the connector is using the HDMI helpers, and is being called by the drm_connector_funcs.reset implementation. Since we want to consolidate DRM objects state allocation around the atomic_create_state callback that will only allocate and initialize a new drm_connector_state instance, we will need to call __drm_atomic_helper_connector_hdmi_reset() from both the reset and atomic_create hooks. To avoid any confusion, we can thus rename __drm_atomic_helper_connector_hdmi_reset() to __drm_atomic_helper_connector_hdmi_state_init(). Suggested-by: Laurent Pinchart Reviewed-by: Dmitry Baryshkov Reviewed-by: Thomas Zimmermann Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-14-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_bridge_connector.c | 4 ++-- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 15 ++++++++------- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +- .../gpu/drm/tests/drm_hdmi_state_helper_test.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- include/drm/display/drm_hdmi_state_helper.h | 4 ++-- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 649969fca141..50408af746d8 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -272,8 +272,8 @@ static void drm_bridge_connector_reset(struct drm_connector *connector) drm_atomic_helper_connector_reset(connector); if (bridge_connector->bridge_hdmi) - __drm_atomic_helper_connector_hdmi_reset(connector, - connector->state); + __drm_atomic_helper_connector_hdmi_state_init(connector, + connector->state); } static const struct drm_connector_funcs drm_bridge_connector_funcs = { diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 4867edbf2622..a331ebdd65af 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -306,17 +306,18 @@ */ /** - * __drm_atomic_helper_connector_hdmi_reset() - Initializes all HDMI @drm_connector_state resources + * __drm_atomic_helper_connector_hdmi_state_init() - Initialize all HDMI @drm_connector_state resources * @connector: DRM connector - * @new_conn_state: connector state to reset + * @new_conn_state: connector state to initialize * * Initializes all HDMI resources from a @drm_connector_state without * actually allocating it. This is useful for HDMI drivers, in - * combination with __drm_atomic_helper_connector_reset() or - * drm_atomic_helper_connector_reset(). + * combination with __drm_atomic_helper_connector_state_init(), + * drm_atomic_helper_connector_reset(), or + * drm_atomic_helper_connector_create_state(). */ -void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, - struct drm_connector_state *new_conn_state) +void __drm_atomic_helper_connector_hdmi_state_init(struct drm_connector *connector, + struct drm_connector_state *new_conn_state) { unsigned int max_bpc = connector->max_bpc; @@ -324,7 +325,7 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, new_conn_state->max_requested_bpc = max_bpc; new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; } -EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); +EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_state_init); static enum hdmi_colorspace output_color_format_to_hdmi_colorspace(const struct drm_connector *connector, diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 74c7c3720ba8..8f64464621c9 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -285,7 +285,7 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) static void sun4i_hdmi_connector_reset(struct drm_connector *connector) { drm_atomic_helper_connector_reset(connector); - __drm_atomic_helper_connector_hdmi_reset(connector, connector->state); + __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state); } static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index c9819c3fc635..e89e1af7a811 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -168,7 +168,7 @@ static const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { static void dummy_hdmi_connector_reset(struct drm_connector *connector) { drm_atomic_helper_connector_reset(connector); - __drm_atomic_helper_connector_hdmi_reset(connector, connector->state); + __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state); } static const struct drm_connector_funcs dummy_connector_funcs = { diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index a161d3b00a25..74dce4be0c00 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -508,7 +508,7 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector, static void vc4_hdmi_connector_reset(struct drm_connector *connector) { drm_atomic_helper_connector_reset(connector); - __drm_atomic_helper_connector_hdmi_reset(connector, connector->state); + __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state); drm_atomic_helper_connector_tv_margins_reset(connector); } diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h index 0adc30c55ec9..13375bd0f4ae 100644 --- a/include/drm/display/drm_hdmi_state_helper.h +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -11,8 +11,8 @@ struct hdmi_audio_infoframe; enum drm_connector_status; -void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, - struct drm_connector_state *new_conn_state); +void __drm_atomic_helper_connector_hdmi_state_init(struct drm_connector *connector, + struct drm_connector_state *new_conn_state); int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, struct drm_atomic_commit *state); From 6db0e11f480632986a16a61833e2904e809c4692 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:27 +0200 Subject: [PATCH 018/167] drm/connector: Add new atomic_create_state callback Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback to drm_private_obj") introduced a new pattern for allocating drm object states. Instead of relying on the reset() callback, it created a new atomic_create_state hook. This is helpful because reset is a bit overloaded: it's used to create the initial software state, reset it, but also reset the hardware. It can also be used either at probe time, to create the initial state and possibly reset the hardware to an expected default, but also during suspend/resume. Both these cases come with different expectations too: during the initialization, we want to initialize all states, but during suspend/resume, drm_private_states for example are expected to be kept around. reset() also isn't fallible, which makes it harder to handle initialization errors properly. This is only really relevant for some drivers though, since all the helpers for reset only create a new state, and don't touch the hardware at all. It was thus decided to create a new hook that would allocate and initialize a pristine state without any side effect: atomic_create_state to untangle a bit some of it, and to separate the initialization with the actual reset one might need during a suspend/resume. Continue the transition to the new pattern with connectors. Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-15-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic_state_helper.c | 26 +++++++++++++++++++ drivers/gpu/drm/drm_connector.c | 11 +++++++- drivers/gpu/drm/drm_mode_config.c | 31 ++++++++++++++++++++++- include/drm/drm_atomic_state_helper.h | 2 ++ include/drm/drm_connector.h | 16 ++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index e2e5a1b8a820..07686e94aae0 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -534,6 +534,32 @@ void drm_atomic_helper_connector_reset(struct drm_connector *connector) } EXPORT_SYMBOL(drm_atomic_helper_connector_reset); +/** + * drm_atomic_helper_connector_create_state - default &drm_connector_funcs.atomic_create_state hook for connectors + * @connector: connector object + * + * Allocates and initializes pristine @drm_connector_state. + * + * This is useful for drivers that don't subclass @drm_connector_state. + * + * RETURNS: + * Pointer to new connector state, or ERR_PTR on failure. + */ +struct drm_connector_state * +drm_atomic_helper_connector_create_state(struct drm_connector *connector) +{ + struct drm_connector_state *state; + + state = kzalloc_obj(*state); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_connector_state_init(state, connector); + + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_connector_create_state); + /** * drm_atomic_helper_connector_tv_margins_reset - Resets TV connector properties * @connector: DRM connector diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 3fa4d2082cd7..a5d13b92b665 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -618,8 +618,17 @@ int drmm_connector_hdmi_init(struct drm_device *dev, * drm_connector_attach_max_bpc_property() requires the * connector to have a state. */ - if (connector->funcs->reset) + if (connector->funcs->atomic_create_state) { + struct drm_connector_state *state; + + state = connector->funcs->atomic_create_state(connector); + if (IS_ERR(state)) + return PTR_ERR(state); + + connector->state = state; + } else if (connector->funcs->reset) { connector->funcs->reset(connector); + } drm_connector_attach_max_bpc_property(connector, 8, max_bpc); connector->max_bpc = max_bpc; diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 2e2cd18a14b4..9d240817f8b6 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -237,6 +237,32 @@ static int drm_mode_config_crtc_reset_with_create_state(struct drm_crtc *crtc) return drm_mode_config_crtc_create_state(crtc); } +static int drm_mode_config_connector_create_state(struct drm_connector *connector) +{ + struct drm_connector_state *conn_state; + + if (!connector->funcs->atomic_create_state) + return 0; + + conn_state = connector->funcs->atomic_create_state(connector); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + connector->state = conn_state; + + return 0; +} + +static int drm_mode_config_connector_reset_with_create_state(struct drm_connector *connector) +{ + if (connector->state) { + connector->funcs->atomic_destroy_state(connector, connector->state); + connector->state = NULL; + } + + return drm_mode_config_connector_create_state(connector); +} + /** * drm_mode_config_reset - call ->reset callbacks * @dev: drm device @@ -280,9 +306,12 @@ void drm_mode_config_reset(struct drm_device *dev) encoder->funcs->reset(encoder); drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->funcs->reset) connector->funcs->reset(connector); + else if (connector->funcs->atomic_create_state) + drm_mode_config_connector_reset_with_create_state(connector); + } drm_connector_list_iter_end(&conn_iter); } EXPORT_SYMBOL(drm_mode_config_reset); diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 9634a70e0401..f4b6d8833bc2 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -75,6 +75,8 @@ void __drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_s void __drm_atomic_helper_connector_reset(struct drm_connector *connector, struct drm_connector_state *conn_state); void drm_atomic_helper_connector_reset(struct drm_connector *connector); +struct drm_connector_state * +drm_atomic_helper_connector_create_state(struct drm_connector *connector); void drm_atomic_helper_connector_tv_reset(struct drm_connector *connector); int drm_atomic_helper_connector_tv_check(struct drm_connector *connector, struct drm_atomic_commit *state); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 5ad62c207d00..529755c2e862 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1571,6 +1571,22 @@ struct drm_connector_funcs { */ void (*destroy)(struct drm_connector *connector); + /** + * @atomic_create_state: + * + * Allocate a pristine, initialized, state for the connector + * object and return it. This callback must have no side + * effects: in particular, the returned state must not be + * assigned to the object's state pointer and it must not affect + * the hardware state. + * + * RETURNS: + * + * A new, pristine, connector state instance or an error pointer + * on failure. + */ + struct drm_connector_state *(*atomic_create_state)(struct drm_connector *connector); + /** * @atomic_duplicate_state: * From c9497dda313c8d830357669400393d7eacc9ea13 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:28 +0200 Subject: [PATCH 019/167] drm/mode-config: Create drm_mode_config_create_initial_state() drm_mode_config_reset() can be used to create the initial state, but also to return to the initial state, when doing a suspend/resume cycle for example. It also affects both the software and the hardware, and drivers can choose to reset the hardware as well. Most will just create an empty state and the synchronisation between hardware and software states will effectively be done when the first commit is done. That dual role can be harmful, since some objects do need to be initialized but also need to be preserved across a suspend/resume cycle. drm_private_obj are such objects for example. Thus, create another helper for drivers to call to initialize their state when the driver is loaded, so we can make drm_mode_config_reset() only about handling suspend/resume and similar. Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-16-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic.c | 12 ++++- drivers/gpu/drm/drm_mode_config.c | 89 +++++++++++++++++++++++++++++++ include/drm/drm_mode_config.h | 1 + 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index a1bcaaf71eea..3af1b9cc9a06 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -60,8 +60,16 @@ * * Their respective lifetimes are: * - * - at reset time, the object reset implementation allocates a new - * default state and stores it in the object state pointer. + * - at driver initialization time, the driver calls + * drm_mode_config_create_initial_state() to allocate an initial, + * pristine, state for each object and stores it in the objects state + * pointer. Historically, this was one of drm_mode_config_reset() job, + * so one might still encounter it in a driver. + * + * - When resuming from suspend, drm_mode_config_reset() resets the + * software and hardware state to a known default and stores it in the + * object's state pointer. Not all objects are affected by + * drm_mode_config_reset() though. * * - whenever a new update is needed: * diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 9d240817f8b6..f432f485a914 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -316,6 +317,94 @@ void drm_mode_config_reset(struct drm_device *dev) } EXPORT_SYMBOL(drm_mode_config_reset); +/** + * drm_mode_config_create_initial_state - Allocates the initial state + * @dev: drm device + * + * This functions creates the initial state for all the objects. Drivers + * can use this in e.g. probe to initialize their software state. + * + * It has two main differences with drm_mode_config_reset(): the reset() + * hooks aren't called and thus the hardware will be left untouched, but + * also the &drm_private_obj structures will be initialized as opposed + * to drm_mode_config_reset() that skips them. + * + * Returns: 0 on success, negative error value on failure. + */ +int drm_mode_config_create_initial_state(struct drm_device *dev) +{ + struct drm_crtc *crtc; + struct drm_colorop *colorop; + struct drm_plane *plane; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct drm_private_obj *privobj; + int ret; + + drm_for_each_privobj(privobj, dev) { + struct drm_private_state *privobj_state; + + if (privobj->state) + continue; + + if (!privobj->funcs->atomic_create_state) + continue; + + privobj_state = privobj->funcs->atomic_create_state(privobj); + if (IS_ERR(privobj_state)) + return PTR_ERR(privobj_state); + + privobj->state = privobj_state; + } + + drm_for_each_colorop(colorop, dev) { + struct drm_colorop_state *colorop_state; + + if (colorop->state) + continue; + + colorop_state = drm_atomic_helper_colorop_create_state(colorop); + if (IS_ERR(colorop_state)) + return PTR_ERR(colorop_state); + + colorop->state = colorop_state; + } + + drm_for_each_plane(plane, dev) { + if (plane->state) + continue; + + ret = drm_mode_config_plane_create_state(plane); + if (ret) + return ret; + } + + drm_for_each_crtc(crtc, dev) { + if (crtc->state) + continue; + + ret = drm_mode_config_crtc_create_state(crtc); + if (ret) + return ret; + } + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->state) + continue; + + ret = drm_mode_config_connector_create_state(connector); + if (ret) { + drm_connector_list_iter_end(&conn_iter); + return ret; + } + } + drm_connector_list_iter_end(&conn_iter); + + return 0; +} +EXPORT_SYMBOL(drm_mode_config_create_initial_state); + /* * Global properties */ diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h index e584652ddf67..d8f5b7e9673e 100644 --- a/include/drm/drm_mode_config.h +++ b/include/drm/drm_mode_config.h @@ -1007,6 +1007,7 @@ static inline int drm_mode_config_init(struct drm_device *dev) return drmm_mode_config_init(dev); } +int drm_mode_config_create_initial_state(struct drm_device *dev); void drm_mode_config_reset(struct drm_device *dev); void drm_mode_config_cleanup(struct drm_device *dev); From 0e1c76e7d27f9e52850d70328f90d1b55d772461 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:29 +0200 Subject: [PATCH 020/167] drm/drv: Switch skeleton to drm_mode_config_create_initial_state() The driver skeleton currently recommends calling drm_mode_config_reset() at probe time to create the initial state. Now that drm_mode_config_create_initial_state() exists to handle initial state allocation without hardware side effects, update the skeleton to recommend it instead. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-17-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 675675480da4..1ff0bf7cba6a 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -342,7 +342,9 @@ void drm_minor_release(struct drm_minor *minor) * * platform_set_drvdata(pdev, drm); * - * drm_mode_config_reset(drm); + * ret = drm_mode_config_create_initial_state(drm); + * if (ret) + * return ret; * * ret = drm_dev_register(drm); * if (ret) From 03c13fa1399179d846e2796de20de8d55f5edae7 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:30 +0200 Subject: [PATCH 021/167] drm/tidss: Convert to atomic_create_state Our driver uses reset to create the various object states, but only calls the helper that allocate a new state. They are thus strictly equivalent to the new atomic_create_state helpers, so let's switch to these. Reviewed-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-18-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/tidss/tidss_crtc.c | 17 +++++++---------- drivers/gpu/drm/tidss/tidss_plane.c | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c index acab9307bdf3..400329aa2200 100644 --- a/drivers/gpu/drm/tidss/tidss_crtc.c +++ b/drivers/gpu/drm/tidss/tidss_crtc.c @@ -357,20 +357,17 @@ static void tidss_crtc_destroy_state(struct drm_crtc *crtc, kfree(tstate); } -static void tidss_crtc_reset(struct drm_crtc *crtc) +static struct drm_crtc_state *tidss_crtc_create_state(struct drm_crtc *crtc) { struct tidss_crtc_state *tstate; - if (crtc->state) - tidss_crtc_destroy_state(crtc, crtc->state); - tstate = kzalloc_obj(*tstate); - if (!tstate) { - crtc->state = NULL; - return; - } + if (!tstate) + return ERR_PTR(-ENOMEM); - __drm_atomic_helper_crtc_reset(crtc, &tstate->base); + __drm_atomic_helper_crtc_state_init(&tstate->base, crtc); + + return &tstate->base; } static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc) @@ -405,10 +402,10 @@ static void tidss_crtc_destroy(struct drm_crtc *crtc) } static const struct drm_crtc_funcs tidss_crtc_funcs = { - .reset = tidss_crtc_reset, .destroy = tidss_crtc_destroy, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, + .atomic_create_state = tidss_crtc_create_state, .atomic_duplicate_state = tidss_crtc_duplicate_state, .atomic_destroy_state = tidss_crtc_destroy_state, .enable_vblank = tidss_crtc_enable_vblank, diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c index 1a8b44fb45f8..6d82976c2db1 100644 --- a/drivers/gpu/drm/tidss/tidss_plane.c +++ b/drivers/gpu/drm/tidss/tidss_plane.c @@ -178,8 +178,8 @@ static const struct drm_plane_helper_funcs tidss_primary_plane_helper_funcs = { static const struct drm_plane_funcs tidss_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, .destroy = drm_plane_destroy, + .atomic_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; From c453c60fdba437f69209e027b418c4a24143605a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 26 May 2026 18:46:31 +0200 Subject: [PATCH 022/167] drm/bridge_connector: Convert to atomic_create_state The connector created by drm_bridge_connector only initializes a pristine state in reset, which is equivalent to what atomic_create_state would expect. Convert to it. Reviewed-by: Laurent Pinchart Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260526-drm-mode-config-init-v6-19-852346394200@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_bridge_connector.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 50408af746d8..cafa498c3848 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -265,22 +265,29 @@ static void drm_bridge_connector_debugfs_init(struct drm_connector *connector, } } -static void drm_bridge_connector_reset(struct drm_connector *connector) +static struct drm_connector_state * +drm_bridge_connector_create_state(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); + struct drm_connector_state *conn_state; + + conn_state = drm_atomic_helper_connector_create_state(connector); + if (IS_ERR(conn_state)) + return conn_state; - drm_atomic_helper_connector_reset(connector); if (bridge_connector->bridge_hdmi) __drm_atomic_helper_connector_hdmi_state_init(connector, - connector->state); + conn_state); + + return conn_state; } static const struct drm_connector_funcs drm_bridge_connector_funcs = { - .reset = drm_bridge_connector_reset, .detect = drm_bridge_connector_detect, .force = drm_bridge_connector_force, .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_create_state = drm_bridge_connector_create_state, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .debugfs_init = drm_bridge_connector_debugfs_init, From cad6a879a7fbbed85e1e59f8f8dd82a98d25980c Mon Sep 17 00:00:00 2001 From: Dongwon Kim Date: Tue, 26 May 2026 12:28:12 -0700 Subject: [PATCH 023/167] drm/virtio: Freeze and restore hooks to support suspend and resume virtio device needs to delete before VM suspend happens then reinitialize all virtqueues again upon resume Suggested-by: Dmitry Osipenko Tested-by: Dmitry Osipenko Cc: Vivek Kasireddy Cc: Nirmoy Das Signed-off-by: Dongwon Kim Tested-by: Morris Pan Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260526192814.179673-2-dongwon.kim@intel.com [dmitry.osipenko@collabora.com: add missed drm_mode_config_helper_resume()] [dmitry.osipenko@collabora.com: cosmetic change to virtio_gpu_wait_queue()] [dmitry.osipenko@collabora.com: improve error messages] --- drivers/gpu/drm/virtio/virtgpu_drv.c | 71 +++++++++++++++++++++++++++- drivers/gpu/drm/virtio/virtgpu_drv.h | 2 + drivers/gpu/drm/virtio/virtgpu_kms.c | 23 ++++++--- drivers/gpu/drm/virtio/virtgpu_vq.c | 14 ++++++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c b/drivers/gpu/drm/virtio/virtgpu_drv.c index 812ee3f5e4aa..c14873d81ed2 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.c +++ b/drivers/gpu/drm/virtio/virtgpu_drv.c @@ -165,6 +165,71 @@ static unsigned int features[] = { VIRTIO_GPU_F_CONTEXT_INIT, VIRTIO_GPU_F_BLOB_ALIGNMENT, }; + +#ifdef CONFIG_PM_SLEEP +static int virtgpu_freeze(struct virtio_device *vdev) +{ + struct drm_device *dev = vdev->priv; + struct virtio_gpu_device *vgdev = dev->dev_private; + int error; + + error = drm_mode_config_helper_suspend(dev); + if (error) { + DRM_ERROR("suspend error: %d\n", error); + return error; + } + + flush_work(&vgdev->obj_free_work); + flush_work(&vgdev->ctrlq.dequeue_work); + flush_work(&vgdev->cursorq.dequeue_work); + flush_work(&vgdev->config_changed_work); + + error = virtio_gpu_wait_queue(&vgdev->ctrlq, vgdev->ctrlq.vq->num_max); + if (error) { + DRM_ERROR("ctrlq wait failed: %d\n", error); + goto err_resume; + } + + error = virtio_gpu_wait_queue(&vgdev->cursorq, vgdev->cursorq.vq->num_max); + if (error) { + DRM_ERROR("cursorq wait failed: %d\n", error); + goto err_resume; + } + + vdev->config->del_vqs(vdev); + + return 0; + +err_resume: + drm_mode_config_helper_resume(dev); + + return error; +} + +static int virtgpu_restore(struct virtio_device *vdev) +{ + struct drm_device *dev = vdev->priv; + struct virtio_gpu_device *vgdev = dev->dev_private; + int error; + + error = virtio_gpu_find_vqs(vgdev); + if (error) { + DRM_ERROR("failed to find virt queues\n"); + return error; + } + + virtio_device_ready(vdev); + + error = drm_mode_config_helper_resume(dev); + if (error) { + DRM_ERROR("resume error: %d\n", error); + return error; + } + + return 0; +} +#endif + static struct virtio_driver virtio_gpu_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), @@ -173,7 +238,11 @@ static struct virtio_driver virtio_gpu_driver = { .probe = virtio_gpu_probe, .remove = virtio_gpu_remove, .shutdown = virtio_gpu_shutdown, - .config_changed = virtio_gpu_config_changed + .config_changed = virtio_gpu_config_changed, +#ifdef CONFIG_PM_SLEEP + .freeze = virtgpu_freeze, + .restore = virtgpu_restore, +#endif }; static int __init virtio_gpu_driver_init(void) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 7449907754a4..7e15ce02fad0 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -303,6 +303,7 @@ void virtio_gpu_deinit(struct drm_device *dev); void virtio_gpu_release(struct drm_device *dev); int virtio_gpu_driver_open(struct drm_device *dev, struct drm_file *file); void virtio_gpu_driver_postclose(struct drm_device *dev, struct drm_file *file); +int virtio_gpu_find_vqs(struct virtio_gpu_device *vgdev); /* virtgpu_gem.c */ int virtio_gpu_gem_object_open(struct drm_gem_object *obj, @@ -424,6 +425,7 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work); void virtio_gpu_dequeue_cursor_func(struct work_struct *work); void virtio_gpu_panic_notify(struct virtio_gpu_device *vgdev); void virtio_gpu_notify(struct virtio_gpu_device *vgdev); +int virtio_gpu_wait_queue(struct virtio_gpu_queue *vgvq, unsigned int num_elem); int virtio_gpu_cmd_resource_assign_uuid(struct virtio_gpu_device *vgdev, diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index cfde9f573df6..82baf1b3717e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -115,15 +115,28 @@ static void virtio_gpu_get_capsets(struct virtio_gpu_device *vgdev, vgdev->num_capsets = num_capsets; } -int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) +int virtio_gpu_find_vqs(struct virtio_gpu_device *vgdev) { struct virtqueue_info vqs_info[] = { { "control", virtio_gpu_ctrl_ack }, { "cursor", virtio_gpu_cursor_ack }, }; - struct virtio_gpu_device *vgdev; - /* this will expand later */ struct virtqueue *vqs[2]; + int ret; + + ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL); + if (ret) + return ret; + + vgdev->ctrlq.vq = vqs[0]; + vgdev->cursorq.vq = vqs[1]; + + return 0; +} + +int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) +{ + struct virtio_gpu_device *vgdev; u32 num_scanouts, num_capsets, blob_alignment; int ret = 0; @@ -215,13 +228,11 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) vgdev->has_context_init ? '+' : '-', vgdev->has_blob_alignment ? '+' : '-'); - ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL); + ret = virtio_gpu_find_vqs(vgdev); if (ret) { DRM_ERROR("failed to find virt queues\n"); goto err_vqs; } - vgdev->ctrlq.vq = vqs[0]; - vgdev->cursorq.vq = vqs[1]; ret = virtio_gpu_alloc_vbufs(vgdev); if (ret) { DRM_ERROR("failed to alloc vbufs\n"); diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index 67865810a2e7..378e6d6d9f5a 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -369,6 +369,20 @@ static int virtio_gpu_panic_queue_ctrl_sgs(struct virtio_gpu_device *vgdev, return 0; } +int virtio_gpu_wait_queue(struct virtio_gpu_queue *vgvq, unsigned int num_elem) +{ + int ret; + + /* Wait up to 5 seconds for enough free slots to become available */ + ret = wait_event_timeout(vgvq->ack_queue, + vgvq->vq->num_free >= num_elem, + 5 * HZ); + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + static int virtio_gpu_queue_ctrl_sgs(struct virtio_gpu_device *vgdev, struct virtio_gpu_vbuffer *vbuf, struct virtio_gpu_fence *fence, From 54a9700482968744793f24a062e8433bfee2e2e9 Mon Sep 17 00:00:00 2001 From: Dongwon Kim Date: Tue, 26 May 2026 12:28:13 -0700 Subject: [PATCH 024/167] drm/virtio: Add support for saving and restoring virtio_gpu_objects When the host KVM/QEMU resumes from hibernation, it loses all graphics resources previously submitted by the guest OS, as the QEMU process is terminated during the suspend-resume cycle. This leads to invalid resource errors when the guest OS attempts to interact with the host using those resources after resumption. To resolve this, the virtio-gpu driver now tracks all active virtio_gpu_objects and provides a mechanism to restore them by re-submitting the objects to QEMU when needed (e.g., during resume from hibernation). Cc: Dmitry Osipenko Cc: Vivek Kasireddy Cc: Nirmoy Das Signed-off-by: Dongwon Kim Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260526192814.179673-3-dongwon.kim@intel.com [dmitry.osipenko@collabora.com: remove extra blank line] [dmitry.osipenko@collabora.com: move err msg to virtio_gpu_object_restore_all] --- drivers/gpu/drm/virtio/virtgpu_drv.h | 15 +++++ drivers/gpu/drm/virtio/virtgpu_kms.c | 3 + drivers/gpu/drm/virtio/virtgpu_object.c | 73 +++++++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_prime.c | 43 +++++++++++++++ drivers/gpu/drm/virtio/virtgpu_vram.c | 3 + 5 files changed, 137 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 7e15ce02fad0..c91ef4255d66 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -99,6 +99,10 @@ struct virtio_gpu_object { int uuid_state; uuid_t uuid; + + /* for restoration of objects after hibernation */ + struct virtio_gpu_object_params params; + struct list_head restore_node; }; #define gem_to_virtio_gpu_obj(gobj) \ container_of((gobj), struct virtio_gpu_object, base.base) @@ -267,6 +271,8 @@ struct virtio_gpu_device { struct work_struct obj_free_work; spinlock_t obj_free_lock; struct list_head obj_free_list; + struct mutex obj_restore_lock; + struct list_head obj_restore_list; struct virtio_gpu_drv_capset *capsets; uint32_t num_capsets; @@ -472,6 +478,7 @@ void virtio_gpu_fence_event_process(struct virtio_gpu_device *vdev, u64 fence_id); /* virtgpu_object.c */ +void virtio_gpu_remove_from_restore_list(struct virtio_gpu_object *bo); void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo); struct drm_gem_object *virtio_gpu_create_object(struct drm_device *dev, size_t size); @@ -484,6 +491,12 @@ bool virtio_gpu_is_shmem(struct virtio_gpu_object *bo); int virtio_gpu_resource_id_get(struct virtio_gpu_device *vgdev, uint32_t *resid); + +void virtio_gpu_add_object_to_restore_list(struct virtio_gpu_device *vgdev, + struct virtio_gpu_object *bo); + +int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev); + /* virtgpu_prime.c */ int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo); @@ -498,6 +511,8 @@ int virtgpu_dma_buf_import_sgt(struct virtio_gpu_mem_entry **ents, unsigned int *nents, struct virtio_gpu_object *bo, struct dma_buf_attachment *attach); +int virtgpu_dma_buf_obj_resubmit(struct virtio_gpu_device *vgdev, + struct virtio_gpu_object *bo); /* virtgpu_debugfs.c */ void virtio_gpu_debugfs_init(struct drm_minor *minor); diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index 82baf1b3717e..9601f310875d 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -171,6 +171,8 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) virtio_gpu_array_put_free_work); INIT_LIST_HEAD(&vgdev->obj_free_list); spin_lock_init(&vgdev->obj_free_lock); + INIT_LIST_HEAD(&vgdev->obj_restore_list); + mutex_init(&vgdev->obj_restore_lock); #ifdef __LITTLE_ENDIAN if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_VIRGL)) @@ -307,6 +309,7 @@ void virtio_gpu_deinit(struct drm_device *dev) flush_work(&vgdev->config_changed_work); virtio_reset_device(vgdev->vdev); vgdev->vdev->config->del_vqs(vgdev->vdev); + mutex_destroy(&vgdev->obj_restore_lock); } void virtio_gpu_release(struct drm_device *dev) diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c index ec9efacc6919..3545831c5a54 100644 --- a/drivers/gpu/drm/virtio/virtgpu_object.c +++ b/drivers/gpu/drm/virtio/virtgpu_object.c @@ -63,6 +63,15 @@ static void virtio_gpu_resource_id_put(struct virtio_gpu_device *vgdev, uint32_t ida_free(&vgdev->resource_ida, id - 1); } +void virtio_gpu_remove_from_restore_list(struct virtio_gpu_object *bo) +{ + struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private; + + mutex_lock(&vgdev->obj_restore_lock); + list_del_init(&bo->restore_node); + mutex_unlock(&vgdev->obj_restore_lock); +} + void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo) { struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private; @@ -94,6 +103,7 @@ static void virtio_gpu_free_object(struct drm_gem_object *obj) struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private; if (bo->created) { + virtio_gpu_remove_from_restore_list(bo); virtio_gpu_cmd_unref_resource(vgdev, bo); virtio_gpu_notify(vgdev); /* completion handler calls virtio_gpu_cleanup_object() */ @@ -220,6 +230,8 @@ int virtio_gpu_object_create(struct virtio_gpu_device *vgdev, return PTR_ERR(shmem_obj); bo = gem_to_virtio_gpu_obj(&shmem_obj->base); + INIT_LIST_HEAD(&bo->restore_node); + ret = virtio_gpu_resource_id_get(vgdev, &bo->hw_res_handle); if (ret < 0) goto err_free_gem; @@ -258,6 +270,12 @@ int virtio_gpu_object_create(struct virtio_gpu_device *vgdev, virtio_gpu_object_attach(vgdev, bo, ents, nents); } + if (!params->virgl) { + /* store non-virgl object with its param to the restore list */ + bo->params = *params; + virtio_gpu_add_object_to_restore_list(vgdev, bo); + } + *bo_ptr = bo; return 0; @@ -271,3 +289,58 @@ int virtio_gpu_object_create(struct virtio_gpu_device *vgdev, drm_gem_shmem_free(shmem_obj); return ret; } + +void virtio_gpu_add_object_to_restore_list(struct virtio_gpu_device *vgdev, + struct virtio_gpu_object *bo) +{ + mutex_lock(&vgdev->obj_restore_lock); + list_add_tail(&bo->restore_node, &vgdev->obj_restore_list); + mutex_unlock(&vgdev->obj_restore_lock); +} + +int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev) +{ + struct virtio_gpu_object *bo, *tmp; + struct virtio_gpu_mem_entry *ents; + unsigned int nents; + int ret = 0; + + mutex_lock(&vgdev->obj_restore_lock); + list_for_each_entry_safe(bo, tmp, &vgdev->obj_restore_list, + restore_node) { + if (drm_gem_is_imported(&bo->base.base)) { + ret = virtgpu_dma_buf_obj_resubmit(vgdev, bo); + if (ret) + break; + + continue; + } + + if (bo->params.blob || bo->attached) { + ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents, + &nents); + if (ret) + break; + } + + if (bo->params.blob) { + virtio_gpu_cmd_resource_create_blob(vgdev, bo, + &bo->params, + ents, nents); + } else { + virtio_gpu_cmd_create_resource(vgdev, bo, &bo->params, + NULL, NULL); + if (bo->attached) { + bo->attached = false; + virtio_gpu_object_attach(vgdev, bo, ents, + nents); + } + } + } + mutex_unlock(&vgdev->obj_restore_lock); + + if (ret) + DRM_ERROR("failed to restore virtio-gpu objects: %d\n", ret); + + return ret; +} diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 70b3b836e1c9..6611b21f6e7e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -23,6 +23,7 @@ */ #include +#include #include #include "virtgpu_drv.h" @@ -216,6 +217,7 @@ static void virtgpu_dma_buf_free_obj(struct drm_gem_object *obj) } if (bo->created) { + virtio_gpu_remove_from_restore_list(bo); virtio_gpu_cmd_unref_resource(vgdev, bo); virtio_gpu_notify(vgdev); return; @@ -262,6 +264,13 @@ static int virtgpu_dma_buf_init_obj(struct drm_device *dev, dma_buf_unpin(attach); dma_resv_unlock(resv); + /* + * Store the dmabuf imported object with its params to the + * restore list. + */ + bo->params = params; + virtio_gpu_add_object_to_restore_list(vgdev, bo); + return 0; err_import: @@ -272,6 +281,38 @@ static int virtgpu_dma_buf_init_obj(struct drm_device *dev, return ret; } +int virtgpu_dma_buf_obj_resubmit(struct virtio_gpu_device *vgdev, + struct virtio_gpu_object *bo) +{ + struct virtio_gpu_mem_entry *ents; + struct scatterlist *sl; + int i; + + if (!bo->sgt) { + DRM_ERROR("no sgt bound to virtio_gpu_object\n"); + return -ENOMEM; + } + + ents = kvmalloc_array(bo->sgt->nents, + sizeof(struct virtio_gpu_mem_entry), + GFP_KERNEL); + if (!ents) { + DRM_ERROR("failed to allocate ent list\n"); + return -ENOMEM; + } + + for_each_sgtable_dma_sg(bo->sgt, sl, i) { + ents[i].addr = cpu_to_le64(sg_dma_address(sl)); + ents[i].length = cpu_to_le32(sg_dma_len(sl)); + ents[i].padding = 0; + } + + virtio_gpu_cmd_resource_create_blob(vgdev, bo, &bo->params, + ents, bo->sgt->nents); + + return 0; +} + static const struct drm_gem_object_funcs virtgpu_gem_dma_buf_funcs = { .free = virtgpu_dma_buf_free_obj, }; @@ -317,6 +358,8 @@ struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev, if (!bo) return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&bo->restore_node); + obj = &bo->base.base; obj->resv = buf->resv; obj->funcs = &virtgpu_gem_dma_buf_funcs; diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c index 4ae3cbc35dd3..9ad11c7f1194 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vram.c +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c @@ -20,6 +20,7 @@ static void virtio_gpu_vram_free(struct drm_gem_object *obj) if (unmap) virtio_gpu_cmd_unmap(vgdev, bo); + virtio_gpu_remove_from_restore_list(bo); virtio_gpu_cmd_unref_resource(vgdev, bo); virtio_gpu_notify(vgdev); return; @@ -207,6 +208,8 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, obj = &vram->base.base.base; obj->funcs = &virtio_gpu_vram_funcs; + INIT_LIST_HEAD(&vram->base.restore_node); + params->size = PAGE_ALIGN(params->size); drm_gem_private_object_init(vgdev->ddev, obj, params->size); From 78e434bdc0ab16e65b4e3f684a1578a100b3895b Mon Sep 17 00:00:00 2001 From: Dongwon Kim Date: Tue, 26 May 2026 12:28:14 -0700 Subject: [PATCH 025/167] drm/virtio: Add PM notifier to restore objects after hibernation Register a PM notifier in virtio-gpu to handle suspend/hibernate events. On PM_POST_HIBERNATION, restore all GPU objects so that the driver can properly recover after resume. Suggested-by: Dmitry Osipenko Cc: Vivek Kasireddy Signed-off-by: Dongwon Kim Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260526192814.179673-4-dongwon.kim@intel.com [dmitry.osipenko@collabora.com: rename vgdev->hibernation -> hibernated] [dmitry.osipenko@collabora.com: restore objs on suspend error] [dmitry.osipenko@collabora.com: no_cb int -> bool] [dmitry.osipenko@collabora.com: don't include pm_runtime.h] [dmitry.osipenko@collabora.com: proeprly handle failing register_pm_notifier] --- drivers/gpu/drm/virtio/virtgpu_drv.c | 15 +++++++++++ drivers/gpu/drm/virtio/virtgpu_drv.h | 8 +++++- drivers/gpu/drm/virtio/virtgpu_kms.c | 35 +++++++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_object.c | 17 +++++++++++- drivers/gpu/drm/virtio/virtgpu_prime.c | 2 +- drivers/gpu/drm/virtio/virtgpu_vq.c | 13 ++++++--- drivers/gpu/drm/virtio/virtgpu_vram.c | 2 +- 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c b/drivers/gpu/drm/virtio/virtgpu_drv.c index c14873d81ed2..c04af986e63e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.c +++ b/drivers/gpu/drm/virtio/virtgpu_drv.c @@ -167,6 +167,14 @@ static unsigned int features[] = { }; #ifdef CONFIG_PM_SLEEP +static void virtgpu_hibernation_restore(struct virtio_gpu_device *vgdev) +{ + if (vgdev->hibernated) { + vgdev->hibernated = false; + virtio_gpu_object_restore_all(vgdev); + } +} + static int virtgpu_freeze(struct virtio_device *vdev) { struct drm_device *dev = vdev->priv; @@ -179,6 +187,9 @@ static int virtgpu_freeze(struct virtio_device *vdev) return error; } + if (vgdev->hibernated) + virtio_gpu_object_unref_all(vgdev); + flush_work(&vgdev->obj_free_work); flush_work(&vgdev->ctrlq.dequeue_work); flush_work(&vgdev->cursorq.dequeue_work); @@ -201,6 +212,8 @@ static int virtgpu_freeze(struct virtio_device *vdev) return 0; err_resume: + virtgpu_hibernation_restore(vgdev); + drm_mode_config_helper_resume(dev); return error; @@ -220,6 +233,8 @@ static int virtgpu_restore(struct virtio_device *vdev) virtio_device_ready(vdev); + virtgpu_hibernation_restore(vgdev); + error = drm_mode_config_helper_resume(dev); if (error) { DRM_ERROR("resume error: %d\n", error); diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index c91ef4255d66..055e5bedc91f 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -263,6 +263,7 @@ struct virtio_gpu_device { bool has_host_visible; bool has_context_init; bool has_blob_alignment; + bool hibernated; struct virtio_shm_region host_visible_region; struct drm_mm host_visible_mm; @@ -280,6 +281,8 @@ struct virtio_gpu_device { struct list_head cap_cache; uint32_t blob_alignment; + struct notifier_block pm_nb; + /* protects uuid state when exporting */ spinlock_t resource_export_lock; /* protects map state and host_visible_mm */ @@ -345,7 +348,8 @@ void virtio_gpu_cmd_create_resource(struct virtio_gpu_device *vgdev, struct virtio_gpu_object_array *objs, struct virtio_gpu_fence *fence); void virtio_gpu_cmd_unref_resource(struct virtio_gpu_device *vgdev, - struct virtio_gpu_object *bo); + struct virtio_gpu_object *bo, + bool no_cb); int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev, uint64_t offset, uint32_t width, uint32_t height, @@ -497,6 +501,8 @@ void virtio_gpu_add_object_to_restore_list(struct virtio_gpu_device *vgdev, int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev); +void virtio_gpu_object_unref_all(struct virtio_gpu_device *vgdev); + /* virtgpu_prime.c */ int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo); diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index 9601f310875d..922782bdc9cd 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -23,6 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -134,6 +135,31 @@ int virtio_gpu_find_vqs(struct virtio_gpu_device *vgdev) return 0; } +static int virtio_gpu_pm_notifier(struct notifier_block *nb, unsigned long mode, + void *data) +{ + struct virtio_gpu_device *vgdev = container_of(nb, + struct virtio_gpu_device, + pm_nb); + + switch (mode) { + case PM_HIBERNATION_PREPARE: + if (vgdev->has_virgl_3d) { + DRM_ERROR("S4 not allowed when VIRGL is enabled\n"); + return notifier_from_errno(-EPERM); + } + + vgdev->hibernated = true; + break; + + case PM_POST_HIBERNATION: + vgdev->hibernated = false; + break; + } + + return NOTIFY_DONE; +} + int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) { struct virtio_gpu_device *vgdev; @@ -278,8 +304,16 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) wait_event_timeout(vgdev->resp_wq, !vgdev->display_info_pending, 5 * HZ); } + + vgdev->pm_nb.notifier_call = virtio_gpu_pm_notifier; + ret = register_pm_notifier(&vgdev->pm_nb); + if (ret) + goto err_modeset_fini; + return 0; +err_modeset_fini: + virtio_gpu_modeset_fini(vgdev); err_scanouts: virtio_gpu_free_vbufs(vgdev); err_vbufs: @@ -303,6 +337,7 @@ void virtio_gpu_deinit(struct drm_device *dev) { struct virtio_gpu_device *vgdev = dev->dev_private; + unregister_pm_notifier(&vgdev->pm_nb); flush_work(&vgdev->obj_free_work); flush_work(&vgdev->ctrlq.dequeue_work); flush_work(&vgdev->cursorq.dequeue_work); diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c index 3545831c5a54..9bc0bd68c314 100644 --- a/drivers/gpu/drm/virtio/virtgpu_object.c +++ b/drivers/gpu/drm/virtio/virtgpu_object.c @@ -104,7 +104,7 @@ static void virtio_gpu_free_object(struct drm_gem_object *obj) if (bo->created) { virtio_gpu_remove_from_restore_list(bo); - virtio_gpu_cmd_unref_resource(vgdev, bo); + virtio_gpu_cmd_unref_resource(vgdev, bo, false); virtio_gpu_notify(vgdev); /* completion handler calls virtio_gpu_cleanup_object() */ return; @@ -344,3 +344,18 @@ int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev) return ret; } + +void virtio_gpu_object_unref_all(struct virtio_gpu_device *vgdev) +{ + struct virtio_gpu_object *bo, *tmp; + + mutex_lock(&vgdev->obj_restore_lock); + list_for_each_entry_safe(bo, tmp, &vgdev->obj_restore_list, + restore_node) + if (bo->created) { + virtio_gpu_cmd_unref_resource(vgdev, bo, true); + virtio_gpu_notify(vgdev); + } + + mutex_unlock(&vgdev->obj_restore_lock); +} diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 6611b21f6e7e..216c77cd0d21 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -218,7 +218,7 @@ static void virtgpu_dma_buf_free_obj(struct drm_gem_object *obj) if (bo->created) { virtio_gpu_remove_from_restore_list(bo); - virtio_gpu_cmd_unref_resource(vgdev, bo); + virtio_gpu_cmd_unref_resource(vgdev, bo, false); virtio_gpu_notify(vgdev); return; } diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index 378e6d6d9f5a..d6069c3f9766 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -640,14 +640,21 @@ static void virtio_gpu_cmd_unref_cb(struct virtio_gpu_device *vgdev, } void virtio_gpu_cmd_unref_resource(struct virtio_gpu_device *vgdev, - struct virtio_gpu_object *bo) + struct virtio_gpu_object *bo, + bool no_cb) { struct virtio_gpu_resource_unref *cmd_p; struct virtio_gpu_vbuffer *vbuf; int ret; - cmd_p = virtio_gpu_alloc_cmd_cb(vgdev, &vbuf, sizeof(*cmd_p), - virtio_gpu_cmd_unref_cb); + if (no_cb) { + cmd_p = virtio_gpu_alloc_cmd_cb(vgdev, &vbuf, sizeof(*cmd_p), + NULL); + } else { + cmd_p = virtio_gpu_alloc_cmd_cb(vgdev, &vbuf, sizeof(*cmd_p), + virtio_gpu_cmd_unref_cb); + } + memset(cmd_p, 0, sizeof(*cmd_p)); cmd_p->hdr.type = cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_UNREF); diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c index 9ad11c7f1194..e2e799b42c61 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vram.c +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c @@ -21,7 +21,7 @@ static void virtio_gpu_vram_free(struct drm_gem_object *obj) virtio_gpu_cmd_unmap(vgdev, bo); virtio_gpu_remove_from_restore_list(bo); - virtio_gpu_cmd_unref_resource(vgdev, bo); + virtio_gpu_cmd_unref_resource(vgdev, bo, false); virtio_gpu_notify(vgdev); return; } From 1502b42819c2eee9658009ff0b7c9249078d7366 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Thu, 28 May 2026 18:48:26 -0700 Subject: [PATCH 026/167] drm/hyperv: Use "hv_drm_" as symbol name prefix Function and structure names in the Hyper-V DRM driver currently use "hyperv_" as the prefix. This conflicts with usage in core Hyper-V and VMBus code, and incorrectly implies that functions and structures in this driver apply generically to Hyper-V. A specific conflict arises for "hyperv_init", which is an initcall for generic Hyper-V initialization on arm64. The conflict prevents the use of initcall_blacklist on the kernel boot line to skip loading this driver. Fix this by substituting "hv_drm_" as the prefix for all functions and structures in this driver. In most places, this is replacing "hyperv_" with "hv_drm_". In a few places, the substitution results in "hv_drm_drm_", which has been collapsed to just "hv_drm_". In two cases, the existing prefix is a bare "hv" (including in the to_hv() macro), which has been replaced with "hv_drm" for consistency. The changes are all mechanical text substitution in symbol names. There are no other code or functional changes. Signed-off-by: Michael Kelley Reviewed-by: Dexuan Cui Signed-off-by: Hamza Mahfooz Link: https://patchwork.freedesktop.org/patch/msgid/20260529014826.41256-1-mhklkml@zohomail.com --- drivers/gpu/drm/hyperv/hyperv_drm.h | 16 +-- drivers/gpu/drm/hyperv/hyperv_drm_drv.c | 92 ++++++++-------- drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 110 ++++++++++---------- drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 70 ++++++------- 4 files changed, 144 insertions(+), 144 deletions(-) diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h index 9e776112c03e..78136ec2c2f4 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm.h +++ b/drivers/gpu/drm/hyperv/hyperv_drm.h @@ -8,7 +8,7 @@ #define VMBUS_MAX_PACKET_SIZE 0x4000 -struct hyperv_drm_device { +struct hv_drm_device { /* drm */ struct drm_device dev; struct drm_plane plane; @@ -39,17 +39,17 @@ struct hyperv_drm_device { struct hv_device *hdev; }; -#define to_hv(_dev) container_of(_dev, struct hyperv_drm_device, dev) +#define to_hv_drm(_dev) container_of(_dev, struct hv_drm_device, dev) /* hyperv_drm_modeset */ -int hyperv_mode_config_init(struct hyperv_drm_device *hv); +int hv_drm_mode_config_init(struct hv_drm_device *hv); /* hyperv_drm_proto */ -int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp); -int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp, +int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp); +int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp, u32 w, u32 h, u32 pitch); -int hyperv_hide_hw_ptr(struct hv_device *hdev); -int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect); -int hyperv_connect_vsp(struct hv_device *hdev); +int hv_drm_hide_hw_ptr(struct hv_device *hdev); +int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect); +int hv_drm_connect_vsp(struct hv_device *hdev); #endif diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c index 06b5d96e6eaf..b12606934f36 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c +++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c @@ -24,9 +24,9 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 -DEFINE_DRM_GEM_FOPS(hv_fops); +DEFINE_DRM_GEM_FOPS(hv_drm_fops); -static struct drm_driver hyperv_driver = { +static struct drm_driver hv_drm_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, .name = DRIVER_NAME, @@ -34,22 +34,22 @@ static struct drm_driver hyperv_driver = { .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, - .fops = &hv_fops, + .fops = &hv_drm_fops, DRM_GEM_SHMEM_DRIVER_OPS, DRM_FBDEV_SHMEM_DRIVER_OPS, }; -static int hyperv_pci_probe(struct pci_dev *pdev, +static int hv_drm_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { return 0; } -static void hyperv_pci_remove(struct pci_dev *pdev) +static void hv_drm_pci_remove(struct pci_dev *pdev) { } -static const struct pci_device_id hyperv_pci_tbl[] = { +static const struct pci_device_id hv_drm_pci_tbl[] = { { .vendor = PCI_VENDOR_ID_MICROSOFT, .device = PCI_DEVICE_ID_HYPERV_VIDEO, @@ -60,14 +60,14 @@ static const struct pci_device_id hyperv_pci_tbl[] = { /* * PCI stub to support gen1 VM. */ -static struct pci_driver hyperv_pci_driver = { +static struct pci_driver hv_drm_pci_driver = { .name = KBUILD_MODNAME, - .id_table = hyperv_pci_tbl, - .probe = hyperv_pci_probe, - .remove = hyperv_pci_remove, + .id_table = hv_drm_pci_tbl, + .probe = hv_drm_pci_probe, + .remove = hv_drm_pci_remove, }; -static int hyperv_setup_vram(struct hyperv_drm_device *hv, +static int hv_drm_setup_vram(struct hv_drm_device *hv, struct hv_device *hdev) { struct drm_device *dev = &hv->dev; @@ -102,15 +102,15 @@ static int hyperv_setup_vram(struct hyperv_drm_device *hv, return ret; } -static int hyperv_vmbus_probe(struct hv_device *hdev, +static int hv_drm_vmbus_probe(struct hv_device *hdev, const struct hv_vmbus_device_id *dev_id) { - struct hyperv_drm_device *hv; + struct hv_drm_device *hv; struct drm_device *dev; int ret; - hv = devm_drm_dev_alloc(&hdev->device, &hyperv_driver, - struct hyperv_drm_device, dev); + hv = devm_drm_dev_alloc(&hdev->device, &hv_drm_driver, + struct hv_drm_device, dev); if (IS_ERR(hv)) return PTR_ERR(hv); @@ -119,15 +119,15 @@ static int hyperv_vmbus_probe(struct hv_device *hdev, hv_set_drvdata(hdev, hv); hv->hdev = hdev; - ret = hyperv_connect_vsp(hdev); + ret = hv_drm_connect_vsp(hdev); if (ret) { drm_err(dev, "Failed to connect to vmbus.\n"); goto err_hv_set_drv_data; } - aperture_remove_all_conflicting_devices(hyperv_driver.name); + aperture_remove_all_conflicting_devices(hv_drm_driver.name); - ret = hyperv_setup_vram(hv, hdev); + ret = hv_drm_setup_vram(hv, hdev); if (ret) goto err_vmbus_close; @@ -136,11 +136,11 @@ static int hyperv_vmbus_probe(struct hv_device *hdev, * vram location is not fatal. Device will update dirty area till * preferred resolution only. */ - ret = hyperv_update_vram_location(hdev, hv->fb_base); + ret = hv_drm_update_vram_location(hdev, hv->fb_base); if (ret) drm_warn(dev, "Failed to update vram location.\n"); - ret = hyperv_mode_config_init(hv); + ret = hv_drm_mode_config_init(hv); if (ret) goto err_free_mmio; @@ -164,10 +164,10 @@ static int hyperv_vmbus_probe(struct hv_device *hdev, return ret; } -static void hyperv_vmbus_remove(struct hv_device *hdev) +static void hv_drm_vmbus_remove(struct hv_device *hdev) { struct drm_device *dev = hv_get_drvdata(hdev); - struct hyperv_drm_device *hv = to_hv(dev); + struct hv_drm_device *hv = to_hv_drm(dev); drm_dev_unplug(dev); drm_atomic_helper_shutdown(dev); @@ -178,12 +178,12 @@ static void hyperv_vmbus_remove(struct hv_device *hdev) vmbus_free_mmio(hv->mem->start, hv->fb_size); } -static void hyperv_vmbus_shutdown(struct hv_device *hdev) +static void hv_drm_vmbus_shutdown(struct hv_device *hdev) { drm_atomic_helper_shutdown(hv_get_drvdata(hdev)); } -static int hyperv_vmbus_suspend(struct hv_device *hdev) +static int hv_drm_vmbus_suspend(struct hv_device *hdev) { struct drm_device *dev = hv_get_drvdata(hdev); int ret; @@ -197,67 +197,67 @@ static int hyperv_vmbus_suspend(struct hv_device *hdev) return 0; } -static int hyperv_vmbus_resume(struct hv_device *hdev) +static int hv_drm_vmbus_resume(struct hv_device *hdev) { struct drm_device *dev = hv_get_drvdata(hdev); - struct hyperv_drm_device *hv = to_hv(dev); + struct hv_drm_device *hv = to_hv_drm(dev); int ret; - ret = hyperv_connect_vsp(hdev); + ret = hv_drm_connect_vsp(hdev); if (ret) return ret; - ret = hyperv_update_vram_location(hdev, hv->fb_base); + ret = hv_drm_update_vram_location(hdev, hv->fb_base); if (ret) return ret; return drm_mode_config_helper_resume(dev); } -static const struct hv_vmbus_device_id hyperv_vmbus_tbl[] = { +static const struct hv_vmbus_device_id hv_drm_vmbus_tbl[] = { /* Synthetic Video Device GUID */ {HV_SYNTHVID_GUID}, {} }; -static struct hv_driver hyperv_hv_driver = { +static struct hv_driver hv_drm_hv_driver = { .name = KBUILD_MODNAME, - .id_table = hyperv_vmbus_tbl, - .probe = hyperv_vmbus_probe, - .remove = hyperv_vmbus_remove, - .shutdown = hyperv_vmbus_shutdown, - .suspend = hyperv_vmbus_suspend, - .resume = hyperv_vmbus_resume, + .id_table = hv_drm_vmbus_tbl, + .probe = hv_drm_vmbus_probe, + .remove = hv_drm_vmbus_remove, + .shutdown = hv_drm_vmbus_shutdown, + .suspend = hv_drm_vmbus_suspend, + .resume = hv_drm_vmbus_resume, .driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; -static int __init hyperv_init(void) +static int __init hv_drm_init(void) { int ret; if (drm_firmware_drivers_only()) return -ENODEV; - ret = pci_register_driver(&hyperv_pci_driver); + ret = pci_register_driver(&hv_drm_pci_driver); if (ret != 0) return ret; - return vmbus_driver_register(&hyperv_hv_driver); + return vmbus_driver_register(&hv_drm_hv_driver); } -static void __exit hyperv_exit(void) +static void __exit hv_drm_exit(void) { - vmbus_driver_unregister(&hyperv_hv_driver); - pci_unregister_driver(&hyperv_pci_driver); + vmbus_driver_unregister(&hv_drm_hv_driver); + pci_unregister_driver(&hv_drm_pci_driver); } -module_init(hyperv_init); -module_exit(hyperv_exit); +module_init(hv_drm_init); +module_exit(hv_drm_exit); -MODULE_DEVICE_TABLE(pci, hyperv_pci_tbl); -MODULE_DEVICE_TABLE(vmbus, hyperv_vmbus_tbl); +MODULE_DEVICE_TABLE(pci, hv_drm_pci_tbl); +MODULE_DEVICE_TABLE(vmbus, hv_drm_vmbus_tbl); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Deepak Rawat "); MODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device"); diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c index 1bbb7de5ab49..44425f2d7e04 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c +++ b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c @@ -25,11 +25,11 @@ #include "hyperv_drm.h" -static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb, +static int hv_drm_blit_to_vram_rect(struct drm_framebuffer *fb, const struct iosys_map *vmap, struct drm_rect *rect) { - struct hyperv_drm_device *hv = to_hv(fb->dev); + struct hv_drm_device *hv = to_hv_drm(fb->dev); struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(hv->vram); int idx; @@ -44,9 +44,9 @@ static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb, return 0; } -static int hyperv_connector_get_modes(struct drm_connector *connector) +static int hv_drm_connector_get_modes(struct drm_connector *connector) { - struct hyperv_drm_device *hv = to_hv(connector->dev); + struct hv_drm_device *hv = to_hv_drm(connector->dev); int count; count = drm_add_modes_noedid(connector, @@ -58,11 +58,11 @@ static int hyperv_connector_get_modes(struct drm_connector *connector) return count; } -static const struct drm_connector_helper_funcs hyperv_connector_helper_funcs = { - .get_modes = hyperv_connector_get_modes, +static const struct drm_connector_helper_funcs hv_drm_connector_helper_funcs = { + .get_modes = hv_drm_connector_get_modes, }; -static const struct drm_connector_funcs hyperv_connector_funcs = { +static const struct drm_connector_funcs hv_drm_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .destroy = drm_connector_cleanup, .reset = drm_atomic_helper_connector_reset, @@ -70,15 +70,15 @@ static const struct drm_connector_funcs hyperv_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; -static inline int hyperv_conn_init(struct hyperv_drm_device *hv) +static inline int hv_drm_conn_init(struct hv_drm_device *hv) { - drm_connector_helper_add(&hv->connector, &hyperv_connector_helper_funcs); + drm_connector_helper_add(&hv->connector, &hv_drm_connector_helper_funcs); return drm_connector_init(&hv->dev, &hv->connector, - &hyperv_connector_funcs, + &hv_drm_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL); } -static int hyperv_check_size(struct hyperv_drm_device *hv, int w, int h, +static int hv_drm_check_size(struct hv_drm_device *hv, int w, int h, struct drm_framebuffer *fb) { u32 pitch = w * (hv->screen_depth / 8); @@ -92,25 +92,25 @@ static int hyperv_check_size(struct hyperv_drm_device *hv, int w, int h, return 0; } -static const uint32_t hyperv_formats[] = { +static const uint32_t hv_drm_formats[] = { DRM_FORMAT_XRGB8888, }; -static const uint64_t hyperv_modifiers[] = { +static const uint64_t hv_drm_modifiers[] = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID }; -static void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc, +static void hv_drm_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state) { - struct hyperv_drm_device *hv = to_hv(crtc->dev); + struct hv_drm_device *hv = to_hv_drm(crtc->dev); struct drm_plane *plane = &hv->plane; struct drm_plane_state *plane_state = plane->state; struct drm_crtc_state *crtc_state = crtc->state; - hyperv_hide_hw_ptr(hv->hdev); - hyperv_update_situation(hv->hdev, 1, hv->screen_depth, + hv_drm_hide_hw_ptr(hv->hdev); + hv_drm_update_situation(hv->hdev, 1, hv->screen_depth, crtc_state->mode.hdisplay, crtc_state->mode.vdisplay, plane_state->fb->pitches[0]); @@ -118,14 +118,14 @@ static void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc, drm_crtc_vblank_on(crtc); } -static const struct drm_crtc_helper_funcs hyperv_crtc_helper_funcs = { +static const struct drm_crtc_helper_funcs hv_drm_crtc_helper_funcs = { .atomic_check = drm_crtc_helper_atomic_check, .atomic_flush = drm_crtc_vblank_atomic_flush, - .atomic_enable = hyperv_crtc_helper_atomic_enable, + .atomic_enable = hv_drm_crtc_helper_atomic_enable, .atomic_disable = drm_crtc_vblank_atomic_disable, }; -static const struct drm_crtc_funcs hyperv_crtc_funcs = { +static const struct drm_crtc_funcs hv_drm_crtc_funcs = { .reset = drm_atomic_helper_crtc_reset, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, @@ -135,11 +135,11 @@ static const struct drm_crtc_funcs hyperv_crtc_funcs = { DRM_CRTC_VBLANK_TIMER_FUNCS, }; -static int hyperv_plane_atomic_check(struct drm_plane *plane, +static int hv_drm_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) { struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); - struct hyperv_drm_device *hv = to_hv(plane->dev); + struct hv_drm_device *hv = to_hv_drm(plane->dev); struct drm_framebuffer *fb = plane_state->fb; struct drm_crtc *crtc = plane_state->crtc; struct drm_crtc_state *crtc_state = NULL; @@ -167,10 +167,10 @@ static int hyperv_plane_atomic_check(struct drm_plane *plane, return 0; } -static void hyperv_plane_atomic_update(struct drm_plane *plane, +static void hv_drm_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state) { - struct hyperv_drm_device *hv = to_hv(plane->dev); + struct hv_drm_device *hv = to_hv_drm(plane->dev); struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(new_state); @@ -185,15 +185,15 @@ static void hyperv_plane_atomic_update(struct drm_plane *plane, if (!drm_rect_intersect(&dst_clip, &damage)) continue; - hyperv_blit_to_vram_rect(new_state->fb, &shadow_plane_state->data[0], &damage); - hyperv_update_dirt(hv->hdev, &damage); + hv_drm_blit_to_vram_rect(new_state->fb, &shadow_plane_state->data[0], &damage); + hv_drm_update_dirt(hv->hdev, &damage); } } -static int hyperv_plane_get_scanout_buffer(struct drm_plane *plane, +static int hv_drm_plane_get_scanout_buffer(struct drm_plane *plane, struct drm_scanout_buffer *sb) { - struct hyperv_drm_device *hv = to_hv(plane->dev); + struct hv_drm_device *hv = to_hv_drm(plane->dev); struct iosys_map map = IOSYS_MAP_INIT_VADDR_IOMEM(hv->vram); if (plane->state && plane->state->fb) { @@ -207,9 +207,9 @@ static int hyperv_plane_get_scanout_buffer(struct drm_plane *plane, return -ENODEV; } -static void hyperv_plane_panic_flush(struct drm_plane *plane) +static void hv_drm_plane_panic_flush(struct drm_plane *plane) { - struct hyperv_drm_device *hv = to_hv(plane->dev); + struct hv_drm_device *hv = to_hv_drm(plane->dev); struct drm_rect rect; if (!plane->state || !plane->state->fb) @@ -220,29 +220,29 @@ static void hyperv_plane_panic_flush(struct drm_plane *plane) rect.x2 = plane->state->fb->width; rect.y2 = plane->state->fb->height; - hyperv_update_dirt(hv->hdev, &rect); + hv_drm_update_dirt(hv->hdev, &rect); } -static const struct drm_plane_helper_funcs hyperv_plane_helper_funcs = { +static const struct drm_plane_helper_funcs hv_drm_plane_helper_funcs = { DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, - .atomic_check = hyperv_plane_atomic_check, - .atomic_update = hyperv_plane_atomic_update, - .get_scanout_buffer = hyperv_plane_get_scanout_buffer, - .panic_flush = hyperv_plane_panic_flush, + .atomic_check = hv_drm_plane_atomic_check, + .atomic_update = hv_drm_plane_atomic_update, + .get_scanout_buffer = hv_drm_plane_get_scanout_buffer, + .panic_flush = hv_drm_plane_panic_flush, }; -static const struct drm_plane_funcs hyperv_plane_funcs = { +static const struct drm_plane_funcs hv_drm_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = drm_plane_cleanup, DRM_GEM_SHADOW_PLANE_FUNCS, }; -static const struct drm_encoder_funcs hyperv_drm_simple_encoder_funcs_cleanup = { +static const struct drm_encoder_funcs hv_drm_simple_encoder_funcs_cleanup = { .destroy = drm_encoder_cleanup, }; -static inline int hyperv_pipe_init(struct hyperv_drm_device *hv) +static inline int hv_drm_pipe_init(struct hv_drm_device *hv) { struct drm_device *dev = &hv->dev; struct drm_encoder *encoder = &hv->encoder; @@ -252,29 +252,29 @@ static inline int hyperv_pipe_init(struct hyperv_drm_device *hv) int ret; ret = drm_universal_plane_init(dev, plane, 0, - &hyperv_plane_funcs, - hyperv_formats, ARRAY_SIZE(hyperv_formats), - hyperv_modifiers, + &hv_drm_plane_funcs, + hv_drm_formats, ARRAY_SIZE(hv_drm_formats), + hv_drm_modifiers, DRM_PLANE_TYPE_PRIMARY, NULL); if (ret) return ret; - drm_plane_helper_add(plane, &hyperv_plane_helper_funcs); + drm_plane_helper_add(plane, &hv_drm_plane_helper_funcs); drm_plane_enable_fb_damage_clips(plane); ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL, - &hyperv_crtc_funcs, NULL); + &hv_drm_crtc_funcs, NULL); if (ret) return ret; - drm_crtc_helper_add(crtc, &hyperv_crtc_helper_funcs); + drm_crtc_helper_add(crtc, &hv_drm_crtc_helper_funcs); encoder->possible_crtcs = drm_crtc_mask(crtc); ret = drm_encoder_init(dev, encoder, - &hyperv_drm_simple_encoder_funcs_cleanup, + &hv_drm_simple_encoder_funcs_cleanup, DRM_MODE_ENCODER_NONE, NULL); if (ret) return ret; - ret = hyperv_conn_init(hv); + ret = hv_drm_conn_init(hv); if (ret) { drm_err(dev, "Failed to initialized connector.\n"); return ret; @@ -284,25 +284,25 @@ static inline int hyperv_pipe_init(struct hyperv_drm_device *hv) } static enum drm_mode_status -hyperv_mode_valid(struct drm_device *dev, +hv_drm_mode_valid(struct drm_device *dev, const struct drm_display_mode *mode) { - struct hyperv_drm_device *hv = to_hv(dev); + struct hv_drm_device *hv = to_hv_drm(dev); - if (hyperv_check_size(hv, mode->hdisplay, mode->vdisplay, NULL)) + if (hv_drm_check_size(hv, mode->hdisplay, mode->vdisplay, NULL)) return MODE_BAD; return MODE_OK; } -static const struct drm_mode_config_funcs hyperv_mode_config_funcs = { +static const struct drm_mode_config_funcs hv_drm_mode_config_funcs = { .fb_create = drm_gem_fb_create_with_dirty, - .mode_valid = hyperv_mode_valid, + .mode_valid = hv_drm_mode_valid, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, }; -int hyperv_mode_config_init(struct hyperv_drm_device *hv) +int hv_drm_mode_config_init(struct hv_drm_device *hv) { struct drm_device *dev = &hv->dev; int ret; @@ -321,9 +321,9 @@ int hyperv_mode_config_init(struct hyperv_drm_device *hv) dev->mode_config.preferred_depth = hv->screen_depth; dev->mode_config.prefer_shadow = 0; - dev->mode_config.funcs = &hyperv_mode_config_funcs; + dev->mode_config.funcs = &hv_drm_mode_config_funcs; - ret = hyperv_pipe_init(hv); + ret = hv_drm_pipe_init(hv); if (ret) { drm_err(dev, "Failed to initialized pipe.\n"); return ret; diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c index 753d97bff76f..f4c4e576fbee 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c +++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c @@ -181,7 +181,7 @@ struct synthvid_msg { }; } __packed; -static inline bool hyperv_version_ge(u32 ver1, u32 ver2) +static inline bool hv_drm_version_ge(u32 ver1, u32 ver2) { if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) || (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) && @@ -191,10 +191,10 @@ static inline bool hyperv_version_ge(u32 ver1, u32 ver2) return false; } -static inline int hyperv_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg) +static inline int hv_drm_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg) { static atomic64_t request_id = ATOMIC64_INIT(0); - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); int ret; msg->pipe_hdr.type = PIPE_MSG_DATA; @@ -211,9 +211,9 @@ static inline int hyperv_sendpacket(struct hv_device *hdev, struct synthvid_msg return ret; } -static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver) +static int hv_drm_negotiate_version(struct hv_device *hdev, u32 ver) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf; struct drm_device *dev = &hv->dev; unsigned long t; @@ -223,7 +223,7 @@ static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver) msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) + sizeof(struct synthvid_version_req); msg->ver_req.version = ver; - hyperv_sendpacket(hdev, msg); + hv_drm_sendpacket(hdev, msg); t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT); if (!t) { @@ -243,9 +243,9 @@ static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver) return 0; } -int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp) +int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf; struct drm_device *dev = &hv->dev; unsigned long t; @@ -257,7 +257,7 @@ int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp) msg->vram.user_ctx = vram_pp; msg->vram.vram_gpa = vram_pp; msg->vram.is_vram_gpa_specified = 1; - hyperv_sendpacket(hdev, msg); + hv_drm_sendpacket(hdev, msg); t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT); if (!t) { @@ -272,7 +272,7 @@ int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp) return 0; } -int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp, +int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp, u32 w, u32 h, u32 pitch) { struct synthvid_msg msg; @@ -292,7 +292,7 @@ int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp, msg.situ.video_output[0].height_pixels = h; msg.situ.video_output[0].pitch_bytes = pitch; - hyperv_sendpacket(hdev, &msg); + hv_drm_sendpacket(hdev, &msg); return 0; } @@ -306,11 +306,11 @@ int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp, * the msg.ptr_shape.data. Note: setting msg.ptr_pos.is_visible to 0 doesn't * work in tests. * - * The hyperv_hide_hw_ptr() is also called in the handler of the + * The hv_drm_hide_hw_ptr() is also called in the handler of the * SYNTHVID_FEATURE_CHANGE event, otherwise the host still draws an extra * unwanted mouse pointer after the VM Connection window is closed and reopened. */ -int hyperv_hide_hw_ptr(struct hv_device *hdev) +int hv_drm_hide_hw_ptr(struct hv_device *hdev) { struct synthvid_msg msg; @@ -322,7 +322,7 @@ int hyperv_hide_hw_ptr(struct hv_device *hdev) msg.ptr_pos.video_output = 0; msg.ptr_pos.image_x = 0; msg.ptr_pos.image_y = 0; - hyperv_sendpacket(hdev, &msg); + hv_drm_sendpacket(hdev, &msg); memset(&msg, 0, sizeof(struct synthvid_msg)); msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE; @@ -338,14 +338,14 @@ int hyperv_hide_hw_ptr(struct hv_device *hdev) msg.ptr_shape.data[1] = 1; msg.ptr_shape.data[2] = 1; msg.ptr_shape.data[3] = 1; - hyperv_sendpacket(hdev, &msg); + hv_drm_sendpacket(hdev, &msg); return 0; } -int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect) +int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg msg; if (!hv->dirt_needed) @@ -363,14 +363,14 @@ int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect) msg.dirt.rect[0].x2 = rect->x2; msg.dirt.rect[0].y2 = rect->y2; - hyperv_sendpacket(hdev, &msg); + hv_drm_sendpacket(hdev, &msg); return 0; } -static int hyperv_get_supported_resolution(struct hv_device *hdev) +static int hv_drm_get_supported_resolution(struct hv_device *hdev) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf; struct drm_device *dev = &hv->dev; unsigned long t; @@ -383,7 +383,7 @@ static int hyperv_get_supported_resolution(struct hv_device *hdev) sizeof(struct synthvid_supported_resolution_req); msg->resolution_req.maximum_resolution_count = SYNTHVID_MAX_RESOLUTION_COUNT; - hyperv_sendpacket(hdev, msg); + hv_drm_sendpacket(hdev, msg); t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT); if (!t) { @@ -417,9 +417,9 @@ static int hyperv_get_supported_resolution(struct hv_device *hdev) return 0; } -static void hyperv_receive_sub(struct hv_device *hdev) +static void hv_drm_receive_sub(struct hv_device *hdev) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg *msg; if (!hv) @@ -439,14 +439,14 @@ static void hyperv_receive_sub(struct hv_device *hdev) if (msg->vid_hdr.type == SYNTHVID_FEATURE_CHANGE) { hv->dirt_needed = msg->feature_chg.is_dirt_needed; if (hv->dirt_needed) - hyperv_hide_hw_ptr(hv->hdev); + hv_drm_hide_hw_ptr(hv->hdev); } } -static void hyperv_receive(void *ctx) +static void hv_drm_receive(void *ctx) { struct hv_device *hdev = ctx; - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct synthvid_msg *recv_buf; u32 bytes_recvd; u64 req_id; @@ -463,18 +463,18 @@ static void hyperv_receive(void *ctx) &bytes_recvd, &req_id); if (bytes_recvd > 0 && recv_buf->pipe_hdr.type == PIPE_MSG_DATA) - hyperv_receive_sub(hdev); + hv_drm_receive_sub(hdev); } while (bytes_recvd > 0 && ret == 0); } -int hyperv_connect_vsp(struct hv_device *hdev) +int hv_drm_connect_vsp(struct hv_device *hdev) { - struct hyperv_drm_device *hv = hv_get_drvdata(hdev); + struct hv_drm_device *hv = hv_get_drvdata(hdev); struct drm_device *dev = &hv->dev; int ret; ret = vmbus_open(hdev->channel, VMBUS_RING_BUFSIZE, VMBUS_RING_BUFSIZE, - NULL, 0, hyperv_receive, hdev); + NULL, 0, hv_drm_receive, hdev); if (ret) { drm_err(dev, "Unable to open vmbus channel\n"); return ret; @@ -484,16 +484,16 @@ int hyperv_connect_vsp(struct hv_device *hdev) switch (vmbus_proto_version) { case VERSION_WIN10: case VERSION_WIN10_V5: - ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); + ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); if (!ret) break; fallthrough; case VERSION_WIN8: case VERSION_WIN8_1: - ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN8); + ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN8); break; default: - ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); + ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); break; } @@ -504,8 +504,8 @@ int hyperv_connect_vsp(struct hv_device *hdev) hv->screen_depth = SYNTHVID_DEPTH_WIN8; - if (hyperv_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) { - ret = hyperv_get_supported_resolution(hdev); + if (hv_drm_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) { + ret = hv_drm_get_supported_resolution(hdev); if (ret) drm_err(dev, "Failed to get supported resolution from host, use default\n"); } else { From 0251963afd22d1365d07bf81135ef0c694f6c3b5 Mon Sep 17 00:00:00 2001 From: Francois Dugast Date: Fri, 22 May 2026 11:25:28 +0200 Subject: [PATCH 027/167] gpu/buddy: Fix use-after-free in split_block() call sites When split_block() fails it returns before calling mark_split(), leaving the block in the FREE state and still linked in the rbtree. The four err_undo paths then call __gpu_buddy_free() without first removing the block from the tree, which leads to two distinct bugs: - If the buddy is also free, __gpu_buddy_free() merges the two siblings by calling gpu_block_free(mm, block) while block->rb is still linked in the tree. Any subsequent rbtree traversal will follow the now- dangling pointer, causing a use-after-free. - In alloc_from_freetree(), where there is no buddy guard, __gpu_buddy_free() always reaches mark_free() -> rbtree_insert() with block still in the tree, corrupting the rbtree. The same pattern is already used correctly in __force_merge(): call rbtree_remove() to unlink the block before handing it to __gpu_buddy_free(). Apply the same fix to all four err_undo sites. Reported-by: Sashiko Assisted-by: GitHub Copilot:claude-sonnet-4.6 Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20260522092600.32818-2-francois.dugast@intel.com Signed-off-by: Francois Dugast --- drivers/gpu/buddy.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index eb1457376307..dac2027bb64a 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -737,8 +737,10 @@ __alloc_range_bias(struct gpu_buddy *mm, buddy = __get_buddy(block); if (buddy && (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) + gpu_buddy_block_is_free(buddy))) { + rbtree_remove(mm, block); __gpu_buddy_free(mm, block, false); + } return ERR_PTR(err); } @@ -847,8 +849,10 @@ alloc_from_freetree(struct gpu_buddy *mm, return block; err_undo: - if (tmp != order) + if (tmp != order) { + rbtree_remove(mm, block); __gpu_buddy_free(mm, block, false); + } return ERR_PTR(err); } @@ -968,8 +972,10 @@ gpu_buddy_offset_aligned_allocation(struct gpu_buddy *mm, buddy = __get_buddy(block); if (buddy && (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) + gpu_buddy_block_is_free(buddy))) { + rbtree_remove(mm, block); __gpu_buddy_free(mm, block, false); + } return ERR_PTR(err); } @@ -1054,8 +1060,10 @@ static int __alloc_range(struct gpu_buddy *mm, buddy = __get_buddy(block); if (buddy && (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) + gpu_buddy_block_is_free(buddy))) { + rbtree_remove(mm, block); __gpu_buddy_free(mm, block, false); + } err_free: if (err == -ENOSPC && total_allocated_on_err) { From 51d28339bfefe1ed603820669534440465f5dc46 Mon Sep 17 00:00:00 2001 From: Francois Dugast Date: Fri, 22 May 2026 11:25:29 +0200 Subject: [PATCH 028/167] gpu/buddy: Remove redundant condition in alloc_from_freetree() error path The err_undo label in alloc_from_freetree() is only reachable via a goto from inside the `while (tmp != order)` loop, which means tmp is guaranteed to differ from order at that point. The surrounding `if (tmp != order)` guard was therefore always true and can be dropped without any behavioral change. Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20260522092600.32818-3-francois.dugast@intel.com Signed-off-by: Francois Dugast --- drivers/gpu/buddy.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index dac2027bb64a..9f6696f47f89 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -849,10 +849,8 @@ alloc_from_freetree(struct gpu_buddy *mm, return block; err_undo: - if (tmp != order) { - rbtree_remove(mm, block); - __gpu_buddy_free(mm, block, false); - } + rbtree_remove(mm, block); + __gpu_buddy_free(mm, block, false); return ERR_PTR(err); } From e16ac0132f1dcb2ad403de6dbefc2a3881cd5112 Mon Sep 17 00:00:00 2001 From: Francois Dugast Date: Fri, 22 May 2026 11:25:30 +0200 Subject: [PATCH 029/167] gpu/buddy: Introduce __gpu_buddy_undo_splits() helper The pattern of merging a block back with its buddy on error paths is duplicated across multiple locations. Extract it into a __gpu_buddy_undo_splits() helper to avoid repetition and prepare for future changes. Suggested-by: Matthew Auld Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20260522092600.32818-4-francois.dugast@intel.com Signed-off-by: Francois Dugast --- drivers/gpu/buddy.c | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index 9f6696f47f89..8654604b87a4 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -650,6 +650,19 @@ static bool block_incompatible(struct gpu_buddy_block *block, unsigned int flags return needs_clear != gpu_buddy_block_is_clear(block); } +static void __gpu_buddy_undo_splits(struct gpu_buddy *mm, + struct gpu_buddy_block *block) +{ + struct gpu_buddy_block *buddy = __get_buddy(block); + + if (buddy && + (gpu_buddy_block_is_free(block) && + gpu_buddy_block_is_free(buddy))) { + rbtree_remove(mm, block); + __gpu_buddy_free(mm, block, false); + } +} + static struct gpu_buddy_block * __alloc_range_bias(struct gpu_buddy *mm, u64 start, u64 end, @@ -659,7 +672,6 @@ __alloc_range_bias(struct gpu_buddy *mm, { u64 req_size = mm->chunk_size << order; struct gpu_buddy_block *block; - struct gpu_buddy_block *buddy; LIST_HEAD(dfs); int err; int i; @@ -734,13 +746,7 @@ __alloc_range_bias(struct gpu_buddy *mm, * bigger is better, so make sure we merge everything back before we * free the allocated blocks. */ - buddy = __get_buddy(block); - if (buddy && - (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) { - rbtree_remove(mm, block); - __gpu_buddy_free(mm, block, false); - } + __gpu_buddy_undo_splits(mm, block); return ERR_PTR(err); } @@ -849,8 +855,7 @@ alloc_from_freetree(struct gpu_buddy *mm, return block; err_undo: - rbtree_remove(mm, block); - __gpu_buddy_free(mm, block, false); + __gpu_buddy_undo_splits(mm, block); return ERR_PTR(err); } @@ -914,7 +919,6 @@ gpu_buddy_offset_aligned_allocation(struct gpu_buddy *mm, { struct gpu_buddy_block *block = NULL; unsigned int order, tmp, alignment; - struct gpu_buddy_block *buddy; enum gpu_buddy_free_tree tree; unsigned long pages; int err; @@ -967,13 +971,7 @@ gpu_buddy_offset_aligned_allocation(struct gpu_buddy *mm, * bigger is better, so make sure we merge everything back before we * free the allocated blocks. */ - buddy = __get_buddy(block); - if (buddy && - (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) { - rbtree_remove(mm, block); - __gpu_buddy_free(mm, block, false); - } + __gpu_buddy_undo_splits(mm, block); return ERR_PTR(err); } @@ -984,7 +982,6 @@ static int __alloc_range(struct gpu_buddy *mm, u64 *total_allocated_on_err) { struct gpu_buddy_block *block; - struct gpu_buddy_block *buddy; u64 total_allocated = 0; LIST_HEAD(allocated); u64 end; @@ -1055,13 +1052,7 @@ static int __alloc_range(struct gpu_buddy *mm, * bigger is better, so make sure we merge everything back before we * free the allocated blocks. */ - buddy = __get_buddy(block); - if (buddy && - (gpu_buddy_block_is_free(block) && - gpu_buddy_block_is_free(buddy))) { - rbtree_remove(mm, block); - __gpu_buddy_free(mm, block, false); - } + __gpu_buddy_undo_splits(mm, block); err_free: if (err == -ENOSPC && total_allocated_on_err) { From a03721ee484c6a5cbc58ece2cf6feaa2159761e7 Mon Sep 17 00:00:00 2001 From: Francois Dugast Date: Fri, 22 May 2026 11:25:31 +0200 Subject: [PATCH 030/167] gpu/buddy: Track per-order free blocks with a scoreboard Reporting per-order free block counts in drm_buddy_print() currently requires walking all rbtrees, which is O(n) over the total number of free blocks and holds the allocator lock for the duration. This becomes expensive on large VRAM heaps with many small free fragments. Maintain a free_scoreboard[] array indexed by order instead, so that the count for any order is always available in O(1). The scoreboard is kept accurate by hooking into the four places where a block's free state changes: mark_free(), mark_allocated(), mark_split(), and the sites in __gpu_buddy_free(), __force_merge(), and the four err_undo paths that call rbtree_remove() directly on free blocks without going through mark_*(). The print functions are simplified as a result: the rbtree traversal is replaced by a direct array lookup. v3: Update after introducing __gpu_buddy_undo_splits() helper v2: Update after fix for use-after-free in split_block() call sites Assisted-by: GitHub Copilot:claude-sonnet-4.6 Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20260522092600.32818-5-francois.dugast@intel.com Signed-off-by: Francois Dugast --- drivers/gpu/buddy.c | 36 +++++++++++++++++++++--------------- drivers/gpu/drm/drm_buddy.c | 16 ++-------------- include/linux/gpu_buddy.h | 7 +++++++ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index 8654604b87a4..de18b63fef0a 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -193,6 +193,8 @@ static void mark_allocated(struct gpu_buddy *mm, block->header &= ~GPU_BUDDY_HEADER_STATE; block->header |= GPU_BUDDY_ALLOCATED; + mm->free_scoreboard[gpu_buddy_block_order(block)]--; + rbtree_remove(mm, block); } @@ -204,6 +206,8 @@ static void mark_free(struct gpu_buddy *mm, block->header &= ~GPU_BUDDY_HEADER_STATE; block->header |= GPU_BUDDY_FREE; + mm->free_scoreboard[gpu_buddy_block_order(block)]++; + tree = get_block_tree(block); rbtree_insert(mm, block, tree); } @@ -214,6 +218,8 @@ static void mark_split(struct gpu_buddy *mm, block->header &= ~GPU_BUDDY_HEADER_STATE; block->header |= GPU_BUDDY_SPLIT; + mm->free_scoreboard[gpu_buddy_block_order(block)]--; + rbtree_remove(mm, block); } @@ -271,6 +277,7 @@ static unsigned int __gpu_buddy_free(struct gpu_buddy *mm, } rbtree_remove(mm, buddy); + mm->free_scoreboard[gpu_buddy_block_order(buddy)]--; if (force_merge && gpu_buddy_block_is_clear(buddy)) mm->clear_avail -= gpu_buddy_block_size(mm, buddy); @@ -335,6 +342,7 @@ static int __force_merge(struct gpu_buddy *mm, iter = rb_prev(iter); rbtree_remove(mm, block); + mm->free_scoreboard[gpu_buddy_block_order(block)]--; if (gpu_buddy_block_is_clear(block)) mm->clear_avail -= gpu_buddy_block_size(mm, block); @@ -384,11 +392,17 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) BUG_ON(mm->max_order > GPU_BUDDY_MAX_ORDER); + mm->free_scoreboard = kcalloc(mm->max_order + 1, + sizeof(*mm->free_scoreboard), + GFP_KERNEL); + if (!mm->free_scoreboard) + return -ENOMEM; + mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES, sizeof(*mm->free_trees), GFP_KERNEL); if (!mm->free_trees) - return -ENOMEM; + goto out_free_scoreboard; for_each_free_tree(i) { mm->free_trees[i] = kmalloc_array(mm->max_order + 1, @@ -450,6 +464,8 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) while (i--) kfree(mm->free_trees[i]); kfree(mm->free_trees); +out_free_scoreboard: + kfree(mm->free_scoreboard); return -ENOMEM; } EXPORT_SYMBOL(gpu_buddy_init); @@ -488,6 +504,7 @@ void gpu_buddy_fini(struct gpu_buddy *mm) kfree(mm->free_trees[i]); kfree(mm->free_trees); kfree(mm->roots); + kfree(mm->free_scoreboard); } EXPORT_SYMBOL(gpu_buddy_fini); @@ -659,6 +676,7 @@ static void __gpu_buddy_undo_splits(struct gpu_buddy *mm, (gpu_buddy_block_is_free(block) && gpu_buddy_block_is_free(buddy))) { rbtree_remove(mm, block); + mm->free_scoreboard[gpu_buddy_block_order(block)]--; __gpu_buddy_free(mm, block, false); } } @@ -1487,21 +1505,9 @@ void gpu_buddy_print(struct gpu_buddy *mm) mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20); for (order = mm->max_order; order >= 0; order--) { - struct gpu_buddy_block *block, *tmp; - struct rb_root *root; - u64 count = 0, free; - unsigned int tree; + u64 count = mm->free_scoreboard[order]; + u64 free = count * (mm->chunk_size << order); - for_each_free_tree(tree) { - root = &mm->free_trees[tree][order]; - - rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) { - BUG_ON(!gpu_buddy_block_is_free(block)); - count++; - } - } - - free = count * (mm->chunk_size << order); if (free < SZ_1M) pr_info("order-%2d free: %8llu KiB, blocks: %llu\n", order, free >> 10, count); diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index faa025498de4..eef995e08a37 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -47,23 +47,11 @@ void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p) mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20); for (order = mm->max_order; order >= 0; order--) { - struct gpu_buddy_block *block, *tmp; - struct rb_root *root; - u64 count = 0, free; - unsigned int tree; - - for_each_free_tree(tree) { - root = &mm->free_trees[tree][order]; - - rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) { - BUG_ON(!gpu_buddy_block_is_free(block)); - count++; - } - } + u64 count = mm->free_scoreboard[order]; + u64 free = count * (mm->chunk_size << order); drm_printf(p, "order-%2d ", order); - free = count * (mm->chunk_size << order); if (free < SZ_1M) drm_printf(p, "free: %8llu KiB", free >> 10); else diff --git a/include/linux/gpu_buddy.h b/include/linux/gpu_buddy.h index 71941a039648..a28f7d7637ca 100644 --- a/include/linux/gpu_buddy.h +++ b/include/linux/gpu_buddy.h @@ -173,6 +173,13 @@ struct gpu_buddy { * that fits in the remaining space. */ struct gpu_buddy_block **roots; + /* + * Per-order free block scoreboard: free_scoreboard[order] holds the + * number of blocks of that order currently in the free state. + * Incremented in mark_free(), decremented wherever rbtree_remove() is + * called on a free block. + */ + u64 *free_scoreboard; /* public: */ unsigned int n_roots; unsigned int max_order; From 25d912475e8b734ac7bf1880920b6b42514e9472 Mon Sep 17 00:00:00 2001 From: Francois Dugast Date: Fri, 22 May 2026 11:25:32 +0200 Subject: [PATCH 031/167] gpu/buddy: Track per-order used blocks with a scoreboard Extend the scoreboard approach from the previous commit to used blocks, so drm_buddy_print() can report per-order allocation pressure in O(1). Unlike free blocks, an allocated block can leave the allocated state through mark_free() (normal free and gpu_buddy_block_trim()) or be consumed directly by gpu_block_free() during coalescing. Both sites are guarded by gpu_buddy_block_is_allocated() and paired with the increment in mark_allocated(). v3: - Assert scoreboard is empty at fini(), as sanity check (Matthew Auld) v2: - Update after fix for use-after-free in split_block() call sites - Change goto label to out_free_used_scoreboard for clarity - Make drm_buddy_print() and gpu_buddy_print() symmetric for used and free Assisted-by: GitHub Copilot:claude-sonnet-4.6 Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20260522092600.32818-6-francois.dugast@intel.com Signed-off-by: Francois Dugast --- drivers/gpu/buddy.c | 40 ++++++++++++++++++++++++++++--------- drivers/gpu/drm/drm_buddy.c | 18 +++++++++++------ include/linux/gpu_buddy.h | 8 ++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index de18b63fef0a..dc81fe0301ce 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -194,6 +194,7 @@ static void mark_allocated(struct gpu_buddy *mm, block->header |= GPU_BUDDY_ALLOCATED; mm->free_scoreboard[gpu_buddy_block_order(block)]--; + mm->used_scoreboard[gpu_buddy_block_order(block)]++; rbtree_remove(mm, block); } @@ -203,6 +204,9 @@ static void mark_free(struct gpu_buddy *mm, { enum gpu_buddy_free_tree tree; + if (gpu_buddy_block_is_allocated(block)) + mm->used_scoreboard[gpu_buddy_block_order(block)]--; + block->header &= ~GPU_BUDDY_HEADER_STATE; block->header |= GPU_BUDDY_FREE; @@ -281,6 +285,9 @@ static unsigned int __gpu_buddy_free(struct gpu_buddy *mm, if (force_merge && gpu_buddy_block_is_clear(buddy)) mm->clear_avail -= gpu_buddy_block_size(mm, buddy); + if (gpu_buddy_block_is_allocated(block)) + mm->used_scoreboard[gpu_buddy_block_order(block)]--; + gpu_block_free(mm, block); gpu_block_free(mm, buddy); @@ -398,11 +405,17 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) if (!mm->free_scoreboard) return -ENOMEM; + mm->used_scoreboard = kcalloc(mm->max_order + 1, + sizeof(*mm->used_scoreboard), + GFP_KERNEL); + if (!mm->used_scoreboard) + goto out_free_free_scoreboard; + mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES, sizeof(*mm->free_trees), GFP_KERNEL); if (!mm->free_trees) - goto out_free_scoreboard; + goto out_free_used_scoreboard; for_each_free_tree(i) { mm->free_trees[i] = kmalloc_array(mm->max_order + 1, @@ -464,7 +477,9 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) while (i--) kfree(mm->free_trees[i]); kfree(mm->free_trees); -out_free_scoreboard: +out_free_used_scoreboard: + kfree(mm->used_scoreboard); +out_free_free_scoreboard: kfree(mm->free_scoreboard); return -ENOMEM; } @@ -500,11 +515,15 @@ void gpu_buddy_fini(struct gpu_buddy *mm) gpu_buddy_assert(mm->avail == mm->size); + for (i = 0; i <= mm->max_order; ++i) + gpu_buddy_assert(!mm->used_scoreboard[i]); + for_each_free_tree(i) kfree(mm->free_trees[i]); kfree(mm->free_trees); kfree(mm->roots); kfree(mm->free_scoreboard); + kfree(mm->used_scoreboard); } EXPORT_SYMBOL(gpu_buddy_fini); @@ -1505,15 +1524,18 @@ void gpu_buddy_print(struct gpu_buddy *mm) mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20); for (order = mm->max_order; order >= 0; order--) { - u64 count = mm->free_scoreboard[order]; - u64 free = count * (mm->chunk_size << order); + u64 free_count = mm->free_scoreboard[order]; + u64 used_count = mm->used_scoreboard[order]; + u64 block_size = mm->chunk_size << order; + u64 free = free_count * block_size; + u64 used = used_count * block_size; - if (free < SZ_1M) - pr_info("order-%2d free: %8llu KiB, blocks: %llu\n", - order, free >> 10, count); + if (block_size < SZ_1M) + pr_info("order-%2d free: %8llu KiB, used: %8llu KiB, free_blocks: %llu, used_blocks: %llu\n", + order, free >> 10, used >> 10, free_count, used_count); else - pr_info("order-%2d free: %8llu MiB, blocks: %llu\n", - order, free >> 20, count); + pr_info("order-%2d free: %8llu MiB, used: %8llu MiB, free_blocks: %llu, used_blocks: %llu\n", + order, free >> 20, used >> 20, free_count, used_count); } } EXPORT_SYMBOL(gpu_buddy_print); diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index eef995e08a37..1536e59c6fe7 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -47,17 +47,23 @@ void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p) mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20); for (order = mm->max_order; order >= 0; order--) { - u64 count = mm->free_scoreboard[order]; - u64 free = count * (mm->chunk_size << order); + u64 free_count = mm->free_scoreboard[order]; + u64 used_count = mm->used_scoreboard[order]; + u64 block_size = mm->chunk_size << order; + u64 free = free_count * block_size; + u64 used = used_count * block_size; drm_printf(p, "order-%2d ", order); - if (free < SZ_1M) - drm_printf(p, "free: %8llu KiB", free >> 10); + if (block_size < SZ_1M) + drm_printf(p, "free: %8llu KiB, used: %8llu KiB", + free >> 10, used >> 10); else - drm_printf(p, "free: %8llu MiB", free >> 20); + drm_printf(p, "free: %8llu MiB, used: %8llu MiB", + free >> 20, used >> 20); - drm_printf(p, ", blocks: %llu\n", count); + drm_printf(p, ", free_blocks: %llu, used_blocks: %llu\n", + free_count, used_count); } } EXPORT_SYMBOL(drm_buddy_print); diff --git a/include/linux/gpu_buddy.h b/include/linux/gpu_buddy.h index a28f7d7637ca..e037714563d8 100644 --- a/include/linux/gpu_buddy.h +++ b/include/linux/gpu_buddy.h @@ -180,6 +180,14 @@ struct gpu_buddy { * called on a free block. */ u64 *free_scoreboard; + /* + * Per-order used block scoreboard: used_scoreboard[order] holds the + * number of blocks of that order currently in the allocated state. + * Incremented in mark_allocated(), decremented in mark_free() (guarded + * by gpu_buddy_block_is_allocated()) and in __gpu_buddy_free() when an + * allocated block is consumed directly during buddy coalescing. + */ + u64 *used_scoreboard; /* public: */ unsigned int n_roots; unsigned int max_order; From 6a391474a8398e5fa8ba0d642a43097232a1ab29 Mon Sep 17 00:00:00 2001 From: Godswill Onwusilike Date: Sat, 16 May 2026 21:30:15 +0100 Subject: [PATCH 032/167] docs: gpu: drm-uapi: fix spelling of "unprivileged" Correct the spelling of "unpriviledged" to "unprivileged" in DRM uAPI documentation. Signed-off-by: Godswill Onwusilike Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260516203015.33466-1-onwusilikegodswill@gmail.com --- Documentation/gpu/drm-uapi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/gpu/drm-uapi.rst b/Documentation/gpu/drm-uapi.rst index 2c2f939322fb..93df92c4ac8c 100644 --- a/Documentation/gpu/drm-uapi.rst +++ b/Documentation/gpu/drm-uapi.rst @@ -574,7 +574,7 @@ ENOSPC: EPERM/EACCES: Returned for an operation that is valid, but needs more privileges. E.g. root-only or much more common, DRM master-only operations return - this when called by unpriviledged clients. There's no clear + this when called by unprivileged clients. There's no clear difference between EACCES and EPERM. ENODEV: From ac75f6d6066cc56bc70d1943947af5978bcd1e69 Mon Sep 17 00:00:00 2001 From: Godswill Onwusilike Date: Thu, 28 May 2026 23:37:15 +0100 Subject: [PATCH 033/167] docs: gpu: todo: fix spelling of "fucntion" Correct the spelling of "fucntion" to "function" in Documentation/gpu/todo.rst. v2: Fix the actual typo instead of newline-only change Signed-off-by: Godswill Onwusilike Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260528223715.26645-1-onwusilikegodswill@gmail.com --- Documentation/gpu/todo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index cdddf8db35f5..f5a6139356da 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -55,7 +55,7 @@ There are still drivers that use drm_simple_display_pipe. The task here is to convert them to use regular atomic helpers. Search for a driver that calls drm_simple_display_pipe_init() and inline all helpers from drm_simple_kms_helper.c into the driver, such that no simple-KMS interfaces are required. Please also -rename all inlined fucntions according to driver conventions. +rename all inlined functions according to driver conventions. Contact: Thomas Zimmermann, respective driver maintainer From 39e535c95fdf6e9f90fccb3a26057c221b966970 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 11 May 2026 10:45:36 +0300 Subject: [PATCH 034/167] drm/tegra: dsi: Add support for Tegra20/Tegra30 Tegra20 and Tegra30 are fully compatible with existing Tegra DSI driver apart from clock configuration and pad calibration which are addressed by this patch. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260511074538.24563-2-clamor95@gmail.com --- drivers/gpu/drm/tegra/drm.c | 2 + drivers/gpu/drm/tegra/dsi.c | 117 ++++++++++++++++++++++++++---------- drivers/gpu/drm/tegra/dsi.h | 10 +++ 3 files changed, 98 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index dd6c564ff408..24e8ef261836 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1358,10 +1358,12 @@ static SIMPLE_DEV_PM_OPS(host1x_drm_pm_ops, host1x_drm_suspend, static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra20-dc", }, + { .compatible = "nvidia,tegra20-dsi", }, { .compatible = "nvidia,tegra20-hdmi", }, { .compatible = "nvidia,tegra20-gr2d", }, { .compatible = "nvidia,tegra20-gr3d", }, { .compatible = "nvidia,tegra30-dc", }, + { .compatible = "nvidia,tegra30-dsi", }, { .compatible = "nvidia,tegra30-hdmi", }, { .compatible = "nvidia,tegra30-gr2d", }, { .compatible = "nvidia,tegra30-gr3d", }, diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 7f25c50621c9..fbab10bc5c41 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -54,6 +54,11 @@ to_dsi_state(struct drm_connector_state *state) return container_of(state, struct tegra_dsi_state, base); } +struct tegra_dsi_config { + bool has_multiple_pad_controls; + bool has_mux_parent_clk; +}; + struct tegra_dsi { struct host1x_client client; struct tegra_output output; @@ -83,6 +88,8 @@ struct tegra_dsi { /* for ganged-mode support */ struct tegra_dsi *master; struct tegra_dsi *slave; + + const struct tegra_dsi_config *config; }; static inline struct tegra_dsi * @@ -665,39 +672,46 @@ static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) { u32 value; - value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + if (dsi->config->has_multiple_pad_controls) { + /* + * XXX Is this still needed? The module reset is deasserted right + * before this function is called. + */ + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); + + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3); + } else { + value = DSI_PAD_CONTROL_LPUPADJ(0x1) | DSI_PAD_CONTROL_LPDNADJ(0x1) | + DSI_PAD_CONTROL_PREEMP_EN(0x1) | DSI_PAD_CONTROL_SLEWDNADJ(0x6) | + DSI_PAD_CONTROL_SLEWUPADJ(0x6) | DSI_PAD_CONTROL_PDIO(0) | + DSI_PAD_CONTROL_PDIO_CLK(0) | DSI_PAD_CONTROL_PULLDN_ENAB(0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + } return 0; } static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) { - u32 value; int err; - /* - * XXX Is this still needed? The module reset is deasserted right - * before this function is called. - */ - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); - /* start calibration */ tegra_dsi_pad_enable(dsi); - value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | - DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | - DSI_PAD_OUT_CLK(0x0); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); - - value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | - DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3); - err = tegra_mipi_start_calibration(dsi->mipi); if (err < 0) return err; @@ -1174,6 +1188,12 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) struct clk *parent; int err; + /* + * Tegra124+ uses a clock gate, not a mux, so this step + * should be redundant for configuration; yet, DSI refuses + * to work without it. + */ + parent = clk_get_parent(dsi->clk); if (!parent) return -EINVAL; @@ -1562,6 +1582,10 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (!dsi) return -ENOMEM; + dsi->config = device_get_match_data(&pdev->dev); + if (!dsi->config) + return -ENODEV; + dsi->output.dev = dsi->dev = &pdev->dev; dsi->video_fifo_depth = 1920; dsi->host_fifo_depth = 64; @@ -1600,7 +1624,7 @@ static int tegra_dsi_probe(struct platform_device *pdev) goto remove; } - dsi->clk_lp = devm_clk_get(&pdev->dev, "lp"); + dsi->clk_lp = devm_clk_get_optional(&pdev->dev, "lp"); if (IS_ERR(dsi->clk_lp)) { err = dev_err_probe(&pdev->dev, PTR_ERR(dsi->clk_lp), "cannot get low-power clock\n"); @@ -1621,10 +1645,12 @@ static int tegra_dsi_probe(struct platform_device *pdev) goto remove; } - err = tegra_dsi_setup_clocks(dsi); - if (err < 0) { - dev_err(&pdev->dev, "cannot setup clocks\n"); - goto remove; + if (dsi->config->has_mux_parent_clk) { + err = tegra_dsi_setup_clocks(dsi); + if (err < 0) { + dev_err(&pdev->dev, "cannot setup clocks\n"); + goto remove; + } } dsi->regs = devm_platform_ioremap_resource(pdev, 0); @@ -1688,11 +1714,40 @@ static void tegra_dsi_remove(struct platform_device *pdev) tegra_mipi_free(dsi->mipi); } +static const struct tegra_dsi_config tegra20_dsi_config = { + .has_multiple_pad_controls = false, + .has_mux_parent_clk = false, +}; + +/* + * Tegra30 allows DSIA/DSIB to be muxed to either PLL_D or PLL_D2; this is + * simply not modeled in the clock driver yet. If this functionality is + * required, the has_mux_parent_clk flag can be set to true once the clock + * driver is patched. + */ +static const struct tegra_dsi_config tegra30_dsi_config = { + .has_multiple_pad_controls = false, + .has_mux_parent_clk = false, +}; + +static const struct tegra_dsi_config tegra114_dsi_config = { + .has_multiple_pad_controls = true, + .has_mux_parent_clk = true, +}; + +/* TODO: figure out why has_mux_parent_clk = true is necessary on Tegra124+ */ +static const struct tegra_dsi_config tegra124_dsi_config = { + .has_multiple_pad_controls = true, + .has_mux_parent_clk = true, +}; + static const struct of_device_id tegra_dsi_of_match[] = { - { .compatible = "nvidia,tegra210-dsi", }, - { .compatible = "nvidia,tegra132-dsi", }, - { .compatible = "nvidia,tegra124-dsi", }, - { .compatible = "nvidia,tegra114-dsi", }, + { .compatible = "nvidia,tegra210-dsi", .data = &tegra124_dsi_config }, + { .compatible = "nvidia,tegra132-dsi", .data = &tegra124_dsi_config }, + { .compatible = "nvidia,tegra124-dsi", .data = &tegra124_dsi_config }, + { .compatible = "nvidia,tegra114-dsi", .data = &tegra114_dsi_config }, + { .compatible = "nvidia,tegra30-dsi", .data = &tegra30_dsi_config }, + { .compatible = "nvidia,tegra20-dsi", .data = &tegra20_dsi_config }, { }, }; MODULE_DEVICE_TABLE(of, tegra_dsi_of_match); diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h index f39594e65e97..d834ac0c47ab 100644 --- a/drivers/gpu/drm/tegra/dsi.h +++ b/drivers/gpu/drm/tegra/dsi.h @@ -95,6 +95,16 @@ #define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) #define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) #define DSI_PAD_CONTROL_0 0x4b +/* Tegra20/Tegra30 */ +#define DSI_PAD_CONTROL_PULLDN_ENAB(x) (((x) & 0x1) << 28) +#define DSI_PAD_CONTROL_SLEWUPADJ(x) (((x) & 0x7) << 24) +#define DSI_PAD_CONTROL_SLEWDNADJ(x) (((x) & 0x7) << 20) +#define DSI_PAD_CONTROL_PREEMP_EN(x) (((x) & 0x1) << 19) +#define DSI_PAD_CONTROL_PDIO_CLK(x) (((x) & 0x1) << 18) +#define DSI_PAD_CONTROL_PDIO(x) (((x) & 0x3) << 16) +#define DSI_PAD_CONTROL_LPUPADJ(x) (((x) & 0x3) << 14) +#define DSI_PAD_CONTROL_LPDNADJ(x) (((x) & 0x3) << 12) +/* Tegra114+ */ #define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0) #define DSI_PAD_CONTROL_VS1_PDIO_CLK (1 << 8) #define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16) From 27a58cc1a9c7bbe92a43fb5045bd17e148c8a022 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 11 May 2026 10:45:37 +0300 Subject: [PATCH 035/167] drm/tegra: dsi: Re-add clear enable register if DSI was powered by bootloader Original commit b22fd0b9639e ("drm/tegra: dsi: Clear enable register if powered by bootloader") was added to address the issue of DSI being in an unknown state after the bootloader, ensuring correct panel configuration. This worked fairly well under the assumption that the bootloader had set up DSI; however, in cases where it did not, the device would hang because a DSI read was called before the DSI hardware was ready. Removing this workaround results in the issue described in the original fix: the panel initialization sequence fails and the panel gets stuck in an undefined state. This is especially noticeable with command mode panels In order to properly address this issue, the original workaround is restored and placed after the DSI hardware is prepared for R/W operations. This fixes behavior for both cases: where DSI is set by the bootloader and where DSI is untouched. I have tested this change on Tegra20 (Motorola Atrix 4G), Tegra114 (NVIDIA Tegra Note 7 and ASUS Transformer Pad TF701T), and Tegra124 (Xiaomi Mi Pad) with U-Boot, using both bootloader-initialized DSI and untouched DSI. Fixes: b22fd0b9639e ("drm/tegra: dsi: Clear enable register if powered by bootloader") Fixes: 660b299bed2a ("Revert "drm/tegra: dsi: Clear enable register if powered by bootloader"") Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260511074538.24563-3-clamor95@gmail.com --- drivers/gpu/drm/tegra/dsi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index fbab10bc5c41..e7fdd8c7ac12 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -936,6 +936,15 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) return; } + /* If the bootloader enabled DSI it needs to be disabled + * in order for the panel initialization commands to be + * properly sent. + */ + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + + if (value & DSI_POWER_CONTROL_ENABLE) + tegra_dsi_disable(dsi); + state = tegra_dsi_get_state(dsi); tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh); From 3764568dd2e621850c8c2a34dd8a88096ae73e72 Mon Sep 17 00:00:00 2001 From: Vitor Soares Date: Fri, 22 May 2026 17:11:07 +0100 Subject: [PATCH 036/167] dt-bindings: vendor-prefixes: Add Riverdi Add vendor prefix for Riverdi Sp. z o.o, a design and manufacturer of TFT display solutions. Link: https://riverdi.com Acked-by: Conor Dooley Signed-off-by: Vitor Soares Reviewed-by: Francesco Dolcini Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260522161105.277519-16-ivitro@gmail.com --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 11c55b5df0e4..6d742a317642 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1405,6 +1405,8 @@ patternProperties: description: Embest RIoT "^riscv,.*": description: RISC-V Foundation + "^riverdi,.*": + description: Riverdi Sp. z o.o "^rockchip,.*": description: Rockchip Electronics Co., Ltd. "^rocktech,.*": From 108b5876d6aa2c719001ba64c9c30dfb5ff15807 Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Thu, 2 Apr 2026 20:33:50 +0200 Subject: [PATCH 037/167] drm: lcdif: Set undocumented bit to clear FIFO at vsync There is an undocumented bit used in the NXP BSP to clear the FIFO systematically at vsync. In normal operation, the FIFO should already be empty but it doesn't hurt to add it as an extra safety measure. Signed-off-by: Paul Kocialkowski Reviewed-by: Lucas Stach Reviewed-by: Frieder Schrempf Tested-by: Frieder Schrempf Reviewed-by: Liu Ying Link: https://patch.msgid.link/20260402183351.3281123-2-paulk@sys-base.io Signed-off-by: Lucas Stach --- drivers/gpu/drm/mxsfb/lcdif_kms.c | 3 ++- drivers/gpu/drm/mxsfb/lcdif_regs.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mxsfb/lcdif_kms.c b/drivers/gpu/drm/mxsfb/lcdif_kms.c index ade76c3f4e4e..0b5f86c6d206 100644 --- a/drivers/gpu/drm/mxsfb/lcdif_kms.c +++ b/drivers/gpu/drm/mxsfb/lcdif_kms.c @@ -338,7 +338,8 @@ static void lcdif_set_mode(struct lcdif_drm_private *lcdif, u32 bus_flags) * Downstream set it to 256B burst size to improve the memory * efficiency so set it here too. */ - ctrl = CTRLDESCL0_3_P_SIZE(2) | CTRLDESCL0_3_T_SIZE(2) | + ctrl = CTRLDESCL0_3_STATE_CLEAR_VSYNC | + CTRLDESCL0_3_P_SIZE(2) | CTRLDESCL0_3_T_SIZE(2) | CTRLDESCL0_3_PITCH(lcdif->crtc.primary->state->fb->pitches[0]); writel(ctrl, lcdif->base + LCDC_V8_CTRLDESCL0_3); } diff --git a/drivers/gpu/drm/mxsfb/lcdif_regs.h b/drivers/gpu/drm/mxsfb/lcdif_regs.h index c55dfb236c1d..17882c593d27 100644 --- a/drivers/gpu/drm/mxsfb/lcdif_regs.h +++ b/drivers/gpu/drm/mxsfb/lcdif_regs.h @@ -190,6 +190,7 @@ #define CTRLDESCL0_1_WIDTH(n) ((n) & 0xffff) #define CTRLDESCL0_1_WIDTH_MASK GENMASK(15, 0) +#define CTRLDESCL0_3_STATE_CLEAR_VSYNC BIT(23) #define CTRLDESCL0_3_P_SIZE(n) (((n) << 20) & CTRLDESCL0_3_P_SIZE_MASK) #define CTRLDESCL0_3_P_SIZE_MASK GENMASK(22, 20) #define CTRLDESCL0_3_T_SIZE(n) (((n) << 16) & CTRLDESCL0_3_T_SIZE_MASK) From 351af554edd994898db12217c3be39979e168d35 Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Thu, 2 Apr 2026 20:33:51 +0200 Subject: [PATCH 038/167] drm: lcdif: Wait for vblank before disabling DMA It is necessary to wait for the full frame to finish streaming through the DMA engine before we can safely disable it by removing the DISP_PARA_DISP_ON bit. Disabling it in-flight can leave the hardware confused and unable to resume streaming for the next frame. This causes the FIFO underrun and empty status bits to be set and a single solid color to be shown on the display, coming from one of the pixels of the previous frame. The issue occurs sporadically when a new mode is set, which triggers the crtc disable and enable paths. Setting the shadow load bit and waiting for it to be cleared by the DMA engine allows waiting for completion. The NXP BSP driver addresses this issue with a hardcoded 25 ms sleep. Fixes: 9db35bb349a0 ("drm: lcdif: Add support for i.MX8MP LCDIF variant") Signed-off-by: Paul Kocialkowski Co-developed-by: Lucas Stach Reviewed-by: Frieder Schrempf Tested-by: Frieder Schrempf Acked-by: Liu Ying Link: https://patch.msgid.link/20260402183351.3281123-3-paulk@sys-base.io Signed-off-by: Lucas Stach --- drivers/gpu/drm/mxsfb/lcdif_kms.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/mxsfb/lcdif_kms.c b/drivers/gpu/drm/mxsfb/lcdif_kms.c index 0b5f86c6d206..79e0f483ca20 100644 --- a/drivers/gpu/drm/mxsfb/lcdif_kms.c +++ b/drivers/gpu/drm/mxsfb/lcdif_kms.c @@ -375,14 +375,23 @@ static void lcdif_disable_controller(struct lcdif_drm_private *lcdif) int ret; reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5); + /* Disable the layer for DMA. */ reg &= ~CTRLDESCL0_5_EN; + /* + * It is necessary to wait for the full frame to finish streaming + * through the DMA engine before we can safely disable it by removing + * the DISP_PARA_DISP_ON bit. Disabling it in-flight can leave the + * hardware confused and unable to resume streaming for the next frame. + */ + reg |= CTRLDESCL0_5_SHADOW_LOAD_EN; writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5); + /* Wait for the frame to finish or timeout after 50 ms. */ ret = readl_poll_timeout(lcdif->base + LCDC_V8_CTRLDESCL0_5, - reg, !(reg & CTRLDESCL0_5_EN), - 0, 36000); /* Wait ~2 frame times max */ + reg, !(reg & CTRLDESCL0_5_SHADOW_LOAD_EN), + 200, 50000); if (ret) - drm_err(lcdif->drm, "Failed to disable controller!\n"); + drm_err(lcdif->drm, "Timed out waiting for final vblank!\n"); reg = readl(lcdif->base + LCDC_V8_DISP_PARA); reg &= ~DISP_PARA_DISP_ON; From 21fcb222f0d1e1c9f5b04c09e9fb3408e13a0264 Mon Sep 17 00:00:00 2001 From: Laura Nao Date: Tue, 21 Apr 2026 10:47:01 +0200 Subject: [PATCH 039/167] drm: Remove DRIVER_GEM_GPUVA feature flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DRIVER_GEM_GPUVA feature flag is currently only used to control two behaviors within the DRM core: - calling drm_gem_gpuva_init() during GEM object initialization - creating the "gpuvas" debugfs entry drm_gem_gpuva_init() is a plain INIT_LIST_HEAD() and therefore is cheap to run for every GEM object. The DRM_DEBUGFS_GPUVA_INFO macro is only referenced by GPU-VA capable drivers, so clearing the feature bit does not cause any unrelated drivers to get the "gpuvas" debugfs node. The flag doesn't have any relevant purpose (e.g. gating ioctl handlers or MM logic) and doesn't provide any practical benefit. Remove the flag definition and drop it from all drivers that use it, call drm_gem_gpuva_init() unconditionally and clear the driver features bit in DRM_DEBUGFS_GPUVA_INFO. Signed-off-by: Laura Nao Acked-by: Rob Clark Acked-by: Liviu Dudau Acked-by: Thomas Hellström Link: https://patch.msgid.link/20260421084701.24227-1-laura.nao@collabora.com Signed-off-by: Boris Brezillon --- drivers/gpu/drm/drm_gem.c | 3 +-- drivers/gpu/drm/imagination/pvr_drv.c | 2 +- drivers/gpu/drm/msm/msm_drv.c | 2 -- drivers/gpu/drm/nouveau/nouveau_drm.c | 1 - drivers/gpu/drm/panthor/panthor_drv.c | 2 +- drivers/gpu/drm/xe/xe_device.c | 4 ++-- include/drm/drm_debugfs.h | 2 +- include/drm/drm_drv.h | 6 ------ include/drm/drm_gem.h | 3 --- 9 files changed, 6 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index 4c781c431642..4d75ce66bd4d 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -232,8 +232,7 @@ void drm_gem_private_object_init(struct drm_device *dev, if (!obj->resv) obj->resv = &obj->_resv; - if (drm_core_check_feature(dev, DRIVER_GEM_GPUVA)) - drm_gem_gpuva_init(obj); + drm_gem_gpuva_init(obj); drm_vma_node_reset(&obj->vma_node); INIT_LIST_HEAD(&obj->lru_node); diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c index b20c462bcba0..ca3042d14253 100644 --- a/drivers/gpu/drm/imagination/pvr_drv.c +++ b/drivers/gpu/drm/imagination/pvr_drv.c @@ -1378,7 +1378,7 @@ pvr_drm_driver_postclose(__always_unused struct drm_device *drm_dev, DEFINE_DRM_GEM_FOPS(pvr_drm_driver_fops); static struct drm_driver pvr_drm_driver = { - .driver_features = DRIVER_GEM | DRIVER_GEM_GPUVA | DRIVER_RENDER | + .driver_features = DRIVER_GEM | DRIVER_RENDER | DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE, .open = pvr_drm_driver_open, .postclose = pvr_drm_driver_postclose, diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index cc2bcd14b1c2..ebd0f659e910 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -824,7 +824,6 @@ static const struct file_operations fops = { #define DRIVER_FEATURES_GPU ( \ DRIVER_GEM | \ - DRIVER_GEM_GPUVA | \ DRIVER_RENDER | \ DRIVER_SYNCOBJ | \ DRIVER_SYNCOBJ_TIMELINE | \ @@ -832,7 +831,6 @@ static const struct file_operations fops = { #define DRIVER_FEATURES_KMS ( \ DRIVER_GEM | \ - DRIVER_GEM_GPUVA | \ DRIVER_ATOMIC | \ DRIVER_MODESET | \ 0 ) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index e16f59b00f6f..42a81166f3a9 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1364,7 +1364,6 @@ static struct drm_driver driver_stub = { .driver_features = DRIVER_GEM | DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE | - DRIVER_GEM_GPUVA | DRIVER_MODESET | DRIVER_RENDER, .open = nouveau_drm_open, diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index e8dc4096c1d2..1b8f5d5c2ee9 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -1782,7 +1782,7 @@ static void panthor_debugfs_init(struct drm_minor *minor) */ static const struct drm_driver panthor_drm_driver = { .driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ | - DRIVER_SYNCOBJ_TIMELINE | DRIVER_GEM_GPUVA, + DRIVER_SYNCOBJ_TIMELINE, .open = panthor_open, .postclose = panthor_postclose, .show_fdinfo = panthor_show_fdinfo, diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 576095cf0952..d51573cf7f2f 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -396,7 +396,7 @@ static const struct drm_driver regular_driver = { XE_DISPLAY_DRIVER_FEATURES | DRIVER_GEM | DRIVER_RENDER | DRIVER_SYNCOBJ | - DRIVER_SYNCOBJ_TIMELINE | DRIVER_GEM_GPUVA, + DRIVER_SYNCOBJ_TIMELINE, .open = xe_file_open, .postclose = xe_file_close, @@ -427,7 +427,7 @@ static const struct drm_ioctl_desc xe_ioctls_admin_only[] = { static const struct drm_driver admin_only_driver = { .driver_features = XE_DISPLAY_DRIVER_FEATURES | - DRIVER_GEM | DRIVER_RENDER | DRIVER_GEM_GPUVA, + DRIVER_GEM | DRIVER_RENDER, .open = xe_file_open, .postclose = xe_file_close, .ioctls = xe_ioctls_admin_only, diff --git a/include/drm/drm_debugfs.h b/include/drm/drm_debugfs.h index ea8cba94208a..eb93512b0f23 100644 --- a/include/drm/drm_debugfs.h +++ b/include/drm/drm_debugfs.h @@ -48,7 +48,7 @@ * For each DRM GPU VA space drivers should call drm_debugfs_gpuva_info() from * their @show callback. */ -#define DRM_DEBUGFS_GPUVA_INFO(show, data) {"gpuvas", show, DRIVER_GEM_GPUVA, data} +#define DRM_DEBUGFS_GPUVA_INFO(show, data) {"gpuvas", show, 0, data} /** * struct drm_info_list - debugfs info list entry diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index 42fc085f986d..e09559495c5b 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -107,12 +107,6 @@ enum drm_driver_feature { * acceleration should be handled by two drivers that are connected using auxiliary bus. */ DRIVER_COMPUTE_ACCEL = BIT(7), - /** - * @DRIVER_GEM_GPUVA: - * - * Driver supports user defined GPU VA bindings for GEM objects. - */ - DRIVER_GEM_GPUVA = BIT(8), /** * @DRIVER_CURSOR_HOTSPOT: * diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h index 8a704f6a65c1..885244e375d3 100644 --- a/include/drm/drm_gem.h +++ b/include/drm/drm_gem.h @@ -661,9 +661,6 @@ static inline bool drm_gem_is_imported(const struct drm_gem_object *obj) * * This initializes the &drm_gem_object's &drm_gpuvm_bo list. * - * Calling this function is only necessary for drivers intending to support the - * &drm_driver_feature DRIVER_GEM_GPUVA. - * * See also drm_gem_gpuva_set_lock(). */ static inline void drm_gem_gpuva_init(struct drm_gem_object *obj) From e84b07e0a30b371117f9a1120a4645c213f39cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Sun, 31 May 2026 09:49:59 -0300 Subject: [PATCH 040/167] dma-fence: Clarify external lock use case in dma_fence_init() docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kerneldoc comment on dma_fence_init() and dma_fence_init64() describe the legacy reason to pass an external lock as a need to prevent multiple fences "from signaling out of order". However, this wording is a bit misleading: a shared spinlock does not (and cannot) prevent the signaler from signaling out of order. Signaling order is the driver's responsibility regardless of whether the lock is shared or per-fence. Reword both comments to better describe the legacy use cases where a shared lock was needed. Signed-off-by: Maíra Canal Reviewed-by: Christian König Link: https://lore.kernel.org/r/20260531125115.1136036-1-mcanal@igalia.com Signed-off-by: Christian König --- drivers/dma-buf/dma-fence.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index b3bfa6943a8e..c7ea1e75d38a 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -1102,9 +1102,12 @@ __dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops, * context and seqno are used for easy comparison between fences, allowing * to check which fence is later by simply using dma_fence_later(). * - * It is strongly discouraged to provide an external lock because this couples - * lock and fence life time. This is only allowed for legacy use cases when - * multiple fences need to be prevented from signaling out of order. + * External locks are a relic of legacy use cases that needed a shared lock + * to serialize signaling when no out-of-order signaling was possible through + * &dma_fence_ops.signaled. Drivers have abandoned this concept since the + * introduction of the callback, but the external lock is still around. New + * users MUST NOT use external locks, as they force the issuer to outlive all + * fences that reference the lock. */ void dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops, @@ -1129,9 +1132,8 @@ EXPORT_SYMBOL(dma_fence_init); * Context and seqno are used for easy comparison between fences, allowing * to check which fence is later by simply using dma_fence_later(). * - * It is strongly discouraged to provide an external lock because this couples - * lock and fence life time. This is only allowed for legacy use cases when - * multiple fences need to be prevented from signaling out of order. + * New users MUST NOT use external locks. Check the documentation in + * dma_fence_init() to understand the motives behind the legacy use cases. */ void dma_fence_init64(struct dma_fence *fence, const struct dma_fence_ops *ops, From 17cdb54644e7d92b62cff1c4d1bd3d1486515f68 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Sat, 23 May 2026 10:37:22 +0200 Subject: [PATCH 041/167] accel: ethosu: Add performance counter support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Arm Ethos-U NPUs have a PMU with performance counters. The PMU h/w supports up to 4 (U65) or 8 (U85) counters which can be programmed for different events. There is also a dedicated cycle counter. The ABI and implementation are copied from the V3D driver. The main difference in the ABI is there is no query API for the event list. The events differ between the U65 and U85, so the events lists are maintained in userspace along with other differences between the U65 and U85. The cycle counter is always enabled when the PMU is enabled. When the user requests N events, reading the counters will return the N events plus the cycle counter. Signed-off-by: Rob Herring (Arm) Signed-off-by: Tomeu Vizoso Reviewed-by: Maíra Canal Link: https://patch.msgid.link/20260601173814.250071-1-tomeu@tomeuvizoso.net --- v2: - Use XArray instead of idr - Rework locking to use per device spinlock to protect modifying active perfmon. Based on pending V3D changes: https://lore.kernel.org/all/20260508-v3d-perfmon-lifetime-v1-1-f5b5642c085f@igalia.com/ - Add missing perfmon puts in ethosu_ioctl_perfmon_set_global() and ethosu_ioctl_perfmon_get_values() error paths. - Fix reading number of counters on U85. - Add defines NPU_REG_PMCCNTR_CFG v3: - Add explicit padding to drm_ethosu_perfmon_destroy - Fix SPDX license expression - Fix comment typos - Convert perfmon lock from spinlock to mutex - Simplify switch_perfmon condition check - Remove unused ethosu_perfmon_init - Add lockdep_assert_held to ethosu_perfmon_stop_locked v4: - Use drmm_mutex_init() for perfmon lock - Add lockdep_assert_held() to ethosu_perfmon_start() - Fix a few style issues reported by Maíra --- drivers/accel/ethosu/Makefile | 2 +- drivers/accel/ethosu/ethosu_device.h | 33 +++ drivers/accel/ethosu/ethosu_drv.c | 23 +- drivers/accel/ethosu/ethosu_drv.h | 61 +++++- drivers/accel/ethosu/ethosu_job.c | 39 +++- drivers/accel/ethosu/ethosu_job.h | 2 + drivers/accel/ethosu/ethosu_perfmon.c | 301 ++++++++++++++++++++++++++ include/uapi/drm/ethosu_accel.h | 60 ++++- 8 files changed, 508 insertions(+), 13 deletions(-) create mode 100644 drivers/accel/ethosu/ethosu_perfmon.c diff --git a/drivers/accel/ethosu/Makefile b/drivers/accel/ethosu/Makefile index 17db5a600416..598a388b7179 100644 --- a/drivers/accel/ethosu/Makefile +++ b/drivers/accel/ethosu/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_DRM_ACCEL_ARM_ETHOSU) := ethosu.o -ethosu-y += ethosu_drv.o ethosu_gem.o ethosu_job.o +ethosu-y += ethosu_drv.o ethosu_gem.o ethosu_job.o ethosu_perfmon.o diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h index b189fa783d6a..3a1d07d94785 100644 --- a/drivers/accel/ethosu/ethosu_device.h +++ b/drivers/accel/ethosu/ethosu_device.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -43,6 +44,15 @@ struct gen_pool; #define NPU_REG_BASEP_HI(x) (0x0084 + (x) * 8) #define NPU_BASEP_REGION_MAX 8 +#define NPU_REG_PMCR 0x0180 +#define NPU_REG_PMCNTENSET 0x0184 +#define NPU_REG_PMCNTENCLR 0x0188 +#define NPU_REG_PMCCNTR_LO 0x01A0 +#define NPU_REG_PMCCNTR_HI 0x01A4 +#define NPU_REG_PMCCNTR_CFG 0x01A8 +#define NPU_REG_PMU_EVCNTR(x) (0x0300 + (x) * 4) +#define NPU_REG_PMU_EVTYPER(x) (0x0380 + (x) * 4) + #define ID_ARCH_MAJOR_MASK GENMASK(31, 28) #define ID_ARCH_MINOR_MASK GENMASK(27, 20) #define ID_ARCH_PATCH_MASK GENMASK(19, 16) @@ -67,6 +77,15 @@ struct gen_pool; #define PROT_ACTIVE_CSL BIT(1) +#define PMCR_NUM_EVENT_CNT_MASK GENMASK(15, 11) +#define PMCR_CYCLE_CNT_RST BIT(2) +#define PMCR_EVENT_CNT_RST BIT(1) +#define PMCR_CNT_EN BIT(0) + +#define PMU_EV_TYPE_NONE 0 +#define PMU_EV_TYPE_CYCLES 0x11 +#define PMU_EV_TYPE_IDLE 0x20 + enum ethosu_cmds { NPU_OP_CONV = 0x2, NPU_OP_DEPTHWISE = 0x3, @@ -152,6 +171,8 @@ enum ethosu_cmds { #define ETHOSU_SRAM_REGION 2 /* Matching Vela compiler */ +struct ethosu_perfmon; + /** * struct ethosu_device - Ethosu device */ @@ -161,6 +182,7 @@ struct ethosu_device { /** @iomem: CPU mapping of the registers. */ void __iomem *regs; + void __iomem *pmu_regs; void __iomem *sram; struct gen_pool *srampool; @@ -184,6 +206,17 @@ struct ethosu_device { struct mutex sched_lock; u64 fence_context; u64 emit_seqno; + + /* Tracks the performance monitor state. */ + struct { + /* Protects @active. */ + struct mutex lock; + + /* Perfmon currently programmed in HW (or NULL if none). */ + struct ethosu_perfmon *active; + } perfmon_state; + + struct ethosu_perfmon *global_perfmon; }; #define to_ethosu_device(drm_dev) \ diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c index 9992193d7338..fef4713e8061 100644 --- a/drivers/accel/ethosu/ethosu_drv.c +++ b/drivers/accel/ethosu/ethosu_drv.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "ethosu_drv.h" @@ -155,6 +156,7 @@ static int ethosu_open(struct drm_device *ddev, struct drm_file *file) if (ret) goto err_put_mod; + ethosu_perfmon_open_file(priv); file->driver_priv = no_free_ptr(priv); return 0; @@ -166,6 +168,7 @@ static int ethosu_open(struct drm_device *ddev, struct drm_file *file) static void ethosu_postclose(struct drm_device *ddev, struct drm_file *file) { ethosu_job_close(file->driver_priv); + ethosu_perfmon_close_file(file->driver_priv); kfree(file->driver_priv); module_put(THIS_MODULE); } @@ -180,6 +183,10 @@ static const struct drm_ioctl_desc ethosu_drm_driver_ioctls[] = { ETHOSU_IOCTL(BO_MMAP_OFFSET, bo_mmap_offset, 0), ETHOSU_IOCTL(CMDSTREAM_BO_CREATE, cmdstream_bo_create, 0), ETHOSU_IOCTL(SUBMIT, submit, 0), + ETHOSU_IOCTL(PERFMON_CREATE, perfmon_create, 0), + ETHOSU_IOCTL(PERFMON_DESTROY, perfmon_destroy, 0), + ETHOSU_IOCTL(PERFMON_GET_VALUES, perfmon_get_values, 0), + ETHOSU_IOCTL(PERFMON_SET_GLOBAL, perfmon_set_global, 0), }; DEFINE_DRM_ACCEL_FOPS(ethosu_drm_driver_fops); @@ -315,8 +322,14 @@ static int ethosu_init(struct ethosu_device *ethosudev) ethosu_sram_init(ethosudev); + if (!ethosu_is_u65(ethosudev)) + ethosudev->pmu_regs += 0x1000; + + ethosudev->npu_info.pmu_counters = FIELD_GET(PMCR_NUM_EVENT_CNT_MASK, + readl_relaxed(ethosudev->pmu_regs + NPU_REG_PMCR)); + dev_info(ethosudev->base.dev, - "Ethos-U NPU, arch v%ld.%ld.%ld, rev r%ldp%ld, cmd stream ver%ld, %d MACs, %dKB SRAM\n", + "Ethos-U NPU, arch v%ld.%ld.%ld, rev r%ldp%ld, cmd stream ver%ld, %d MACs, %dKB SRAM, %d PMU cntrs\n", FIELD_GET(ID_ARCH_MAJOR_MASK, id), FIELD_GET(ID_ARCH_MINOR_MASK, id), FIELD_GET(ID_ARCH_PATCH_MASK, id), @@ -324,7 +337,8 @@ static int ethosu_init(struct ethosu_device *ethosudev) FIELD_GET(ID_VER_MINOR_MASK, id), FIELD_GET(CONFIG_CMD_STREAM_VER_MASK, config), 1 << FIELD_GET(CONFIG_MACS_PER_CC_MASK, config), - ethosudev->npu_info.sram_size / 1024); + ethosudev->npu_info.sram_size / 1024, + ethosudev->npu_info.pmu_counters); return 0; } @@ -343,11 +357,16 @@ static int ethosu_probe(struct platform_device *pdev) dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); ethosudev->regs = devm_platform_ioremap_resource(pdev, 0); + ethosudev->pmu_regs = ethosudev->regs; ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, ðosudev->clks); if (ethosudev->num_clks < 0) return ethosudev->num_clks; + ret = drmm_mutex_init(ðosudev->base, ðosudev->perfmon_state.lock); + if (ret) + return ret; + ret = ethosu_job_init(ethosudev); if (ret) return ret; diff --git a/drivers/accel/ethosu/ethosu_drv.h b/drivers/accel/ethosu/ethosu_drv.h index 9e21dfe94184..2193bc51d425 100644 --- a/drivers/accel/ethosu/ethosu_drv.h +++ b/drivers/accel/ethosu/ethosu_drv.h @@ -1,15 +1,74 @@ /* SPDX-License-Identifier: GPL-2.0-only OR MIT */ -/* Copyright 2025 Arm, Ltd. */ +/* Copyright 2025-2026 Arm, Ltd. */ #ifndef __ETHOSU_DRV_H__ #define __ETHOSU_DRV_H__ +#include +#include #include struct ethosu_device; +struct drm_device; +struct drm_file; struct ethosu_file_priv { struct ethosu_device *edev; struct drm_sched_entity sched_entity; + struct xarray perfmons; }; +/* Performance monitor object. The perfmon lifetime is controlled by userspace + * using perfmon related ioctls. A perfmon can be attached to a DRM_ETHOSU_SUBMIT + * request, and when this is the case, HW perf counters will be activated just + * before the job is submitted to the NPU and disabled when the job is + * done. This way, only events related to a specific job will be counted. + */ +struct ethosu_perfmon { + /* Tracks the number of users of the perfmon, when this counter reaches + * zero the perfmon is destroyed. + */ + refcount_t refcnt; + + /* Number of counters activated in this perfmon instance + * (should be less than or equal to DRM_ETHOSU_MAX_PERF_COUNTERS). + */ + u8 ncounters; + + /* Events counted by the HW perf counters. */ + u16 counters[DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS]; + + /* + * Storage for counter values. Counters are incremented by the HW + * perf counter values every time the perfmon is attached to an + * NPU job. This way, perfmon users don't have to retrieve the + * results after each job if they want to track events covering + * several submissions. Note that counter values can't be reset, + * but you can fake a reset by destroying the perfmon and + * creating a new one. + */ + u64 values[] __counted_by(ncounters); +}; + +/* ethosu_perfmon.c */ +void ethosu_perfmon_get(struct ethosu_perfmon *perfmon); +void ethosu_perfmon_put(struct ethosu_perfmon *perfmon); +void ethosu_perfmon_start(struct ethosu_device *ethosu, + struct ethosu_perfmon *perfmon); +void ethosu_perfmon_stop(struct ethosu_device *ethosu, + struct ethosu_perfmon *perfmon, bool capture); +void ethosu_perfmon_stop_locked(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon, + bool capture); +struct ethosu_perfmon *ethosu_perfmon_find(struct ethosu_file_priv *ethosu_priv, + int id); +void ethosu_perfmon_open_file(struct ethosu_file_priv *ethosu_priv); +void ethosu_perfmon_close_file(struct ethosu_file_priv *ethosu_priv); +int ethosu_ioctl_perfmon_create(struct drm_device *dev, void *data, + struct drm_file *file_priv); +int ethosu_ioctl_perfmon_destroy(struct drm_device *dev, void *data, + struct drm_file *file_priv); +int ethosu_ioctl_perfmon_get_values(struct drm_device *dev, void *data, + struct drm_file *file_priv); +int ethosu_ioctl_perfmon_set_global(struct drm_device *dev, void *data, + struct drm_file *file_priv); + #endif diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index b76924645aaa..99dec33f526b 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2024-2025 Tomeu Vizoso */ -/* Copyright 2025 Arm, Ltd. */ +/* Copyright 2025-2026 Arm, Ltd. */ #include #include @@ -147,6 +147,8 @@ static void ethosu_job_err_cleanup(struct ethosu_job *job) { unsigned int i; + ethosu_perfmon_put(job->perfmon); + for (i = 0; i < job->region_cnt; i++) drm_gem_object_put(job->region_bo[i]); @@ -181,6 +183,26 @@ static void ethosu_job_free(struct drm_sched_job *sched_job) ethosu_job_put(job); } +static void +ethosu_switch_perfmon(struct ethosu_device *ethosu, struct ethosu_job *job) +{ + struct ethosu_perfmon *perfmon; + + guard(mutex)(ðosu->perfmon_state.lock); + + perfmon = ethosu->global_perfmon; + if (!perfmon) + perfmon = job->perfmon; + + if (perfmon == ethosu->perfmon_state.active) + return; + + ethosu_perfmon_stop_locked(ethosu, ethosu->perfmon_state.active, true); + + if (perfmon) + ethosu_perfmon_start(ethosu, perfmon); +} + static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job) { struct ethosu_job *job = to_ethosu_job(sched_job); @@ -194,6 +216,8 @@ static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job) dev->fence_context, ++dev->emit_seqno); dma_fence_get(fence); + ethosu_switch_perfmon(dev, job); + scoped_guard(mutex, &dev->job_lock) { dev->in_flight_job = job; ethosu_job_hw_submit(dev, job); @@ -365,7 +389,8 @@ void ethosu_job_close(struct ethosu_file_priv *ethosu_priv) } static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file, - struct drm_ethosu_job *job) + struct drm_ethosu_job *job, + int perfmon_id) { struct ethosu_device *edev = to_ethosu_device(dev); struct ethosu_file_priv *file_priv = file->driver_priv; @@ -389,6 +414,9 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file ejob->dev = edev; ejob->sram_size = job->sram_size; + if (perfmon_id) + ejob->perfmon = ethosu_perfmon_find(file_priv, perfmon_id); + ejob->done_fence = kzalloc_obj(*ejob->done_fence); if (!ejob->done_fence) { ret = -ENOMEM; @@ -491,11 +519,6 @@ int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *fil int ret = 0; unsigned int i = 0; - if (args->pad) { - drm_dbg(dev, "Reserved field in drm_ethosu_submit struct should be 0.\n"); - return -EINVAL; - } - struct drm_ethosu_job __free(kvfree) *jobs = kvmalloc_objs(*jobs, args->job_count); if (!jobs) @@ -509,7 +532,7 @@ int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *fil } for (i = 0; i < args->job_count; i++) { - ret = ethosu_ioctl_submit_job(dev, file, &jobs[i]); + ret = ethosu_ioctl_submit_job(dev, file, &jobs[i], args->perfmon_id); if (ret) return ret; } diff --git a/drivers/accel/ethosu/ethosu_job.h b/drivers/accel/ethosu/ethosu_job.h index ff1cf448d094..8988edd00eed 100644 --- a/drivers/accel/ethosu/ethosu_job.h +++ b/drivers/accel/ethosu/ethosu_job.h @@ -21,6 +21,8 @@ struct ethosu_job { u8 region_cnt; u32 sram_size; + struct ethosu_perfmon *perfmon; + /* Fence to be signaled by drm-sched once its done with the job */ struct dma_fence *inference_done_fence; diff --git a/drivers/accel/ethosu/ethosu_perfmon.c b/drivers/accel/ethosu/ethosu_perfmon.c new file mode 100644 index 000000000000..26f625374f9d --- /dev/null +++ b/drivers/accel/ethosu/ethosu_perfmon.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2026 Arm, Ltd. */ +/* Based on v3d_perfmon.c, Copyright (C) 2021 Raspberry Pi */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "ethosu_drv.h" +#include "ethosu_device.h" + +void ethosu_perfmon_get(struct ethosu_perfmon *perfmon) +{ + if (perfmon) + refcount_inc(&perfmon->refcnt); +} + +void ethosu_perfmon_put(struct ethosu_perfmon *perfmon) +{ + if (perfmon && refcount_dec_and_test(&perfmon->refcnt)) + kfree(perfmon); +} + +void ethosu_perfmon_start(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon) +{ + unsigned int i; + u8 ncounters; + u32 mask; + + lockdep_assert_held(ðosu->perfmon_state.lock); + + if (WARN_ON_ONCE(!perfmon || ethosu->perfmon_state.active)) + return; + + writel_relaxed(PMCR_CNT_EN, ethosu->pmu_regs + NPU_REG_PMCR); + writel_relaxed(PMU_EV_TYPE_CYCLES, ethosu->pmu_regs + NPU_REG_PMCCNTR_CFG); + + mask = 0x80000000; + ncounters = perfmon->ncounters - 1; + if (ncounters) + mask |= GENMASK(ncounters - 1, 0); + + for (i = 0; i < ncounters; i++) + writel_relaxed(perfmon->counters[i], ethosu->pmu_regs + NPU_REG_PMU_EVTYPER(i)); + + writel_relaxed(mask, ethosu->pmu_regs + NPU_REG_PMCNTENSET); + writel_relaxed(PMCR_CNT_EN | PMCR_EVENT_CNT_RST | PMCR_CYCLE_CNT_RST, + ethosu->pmu_regs + NPU_REG_PMCR); + ethosu->perfmon_state.active = perfmon; +} + +void ethosu_perfmon_stop_locked(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon, + bool capture) +{ + unsigned int i; + u8 ncounters; + u32 mask; + + lockdep_assert_held(ðosu->perfmon_state.lock); + + if (!perfmon || perfmon != ethosu->perfmon_state.active) + return; + + ncounters = perfmon->ncounters - 1; + + if (!pm_runtime_get_if_active(ethosu->base.dev)) { + ethosu->perfmon_state.active = NULL; + return; + } + + if (capture) { + for (i = 0; i < ncounters; i++) + perfmon->values[i] += readl_relaxed(ethosu->pmu_regs + NPU_REG_PMU_EVCNTR(i)); + + perfmon->values[ncounters] += + readl_relaxed(ethosu->pmu_regs + NPU_REG_PMCCNTR_LO) | + (u64)readl_relaxed(ethosu->pmu_regs + NPU_REG_PMCCNTR_HI) << 32; + } + + mask = 0x80000000; + if (ncounters) + mask |= GENMASK(ncounters - 1, 0); + writel_relaxed(mask, ethosu->pmu_regs + NPU_REG_PMCNTENCLR); + + writel_relaxed(0, ethosu->pmu_regs + NPU_REG_PMCR); + ethosu->perfmon_state.active = NULL; + + pm_runtime_put(ethosu->base.dev); +} + +void ethosu_perfmon_stop(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon, + bool capture) +{ + if (!perfmon) + return; + + guard(mutex)(ðosu->perfmon_state.lock); + ethosu_perfmon_stop_locked(ethosu, perfmon, capture); +} + +struct ethosu_perfmon *ethosu_perfmon_find(struct ethosu_file_priv *ethosu_priv, int id) +{ + struct ethosu_perfmon *perfmon; + + xa_lock(ðosu_priv->perfmons); + perfmon = xa_load(ðosu_priv->perfmons, id); + ethosu_perfmon_get(perfmon); + xa_unlock(ðosu_priv->perfmons); + + return perfmon; +} + +void ethosu_perfmon_open_file(struct ethosu_file_priv *ethosu_priv) +{ + xa_init_flags(ðosu_priv->perfmons, XA_FLAGS_ALLOC1); +} + +static void ethosu_perfmon_delete(struct ethosu_file_priv *ethosu_priv, + struct ethosu_perfmon *perfmon) +{ + struct ethosu_device *ethosu = ethosu_priv->edev; + + /* If the active perfmon is being destroyed, stop it first */ + scoped_guard(mutex, ðosu->perfmon_state.lock) { + /* If the global perfmon is being destroyed, set it to NULL */ + if (ethosu->global_perfmon == perfmon) { + ethosu->global_perfmon = NULL; + ethosu_perfmon_put(perfmon); + } + + ethosu_perfmon_stop_locked(ethosu, perfmon, false); + } + + ethosu_perfmon_put(perfmon); +} + +void ethosu_perfmon_close_file(struct ethosu_file_priv *ethosu_priv) +{ + struct ethosu_perfmon *perfmon; + unsigned long id; + + xa_for_each(ðosu_priv->perfmons, id, perfmon) + ethosu_perfmon_delete(ethosu_priv, perfmon); + + xa_destroy(ðosu_priv->perfmons); +} + +int ethosu_ioctl_perfmon_create(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv; + struct drm_ethosu_perfmon_create *req = data; + struct ethosu_device *ethosu = to_ethosu_device(dev); + struct ethosu_perfmon *perfmon; + unsigned int i, event_max; + int ret; + u32 id; + + /* Number of monitored counters cannot exceed HW limits. */ + if (req->ncounters > ethosu->npu_info.pmu_counters) + return -EINVAL; + + /* Make sure all counters are valid. */ + event_max = ethosu_is_u65(ethosu) ? 433 : 671; + for (i = 0; i < req->ncounters; i++) { + if (req->counters[i] > event_max) + return -EINVAL; + } + + /* Add 1 more counter for cycle counter */ + req->ncounters++; + + perfmon = kzalloc_flex(*perfmon, values, req->ncounters); + if (!perfmon) + return -ENOMEM; + + for (i = 0; i < req->ncounters - 1; i++) + perfmon->counters[i] = req->counters[i]; + + perfmon->ncounters = req->ncounters; + + refcount_set(&perfmon->refcnt, 1); + + ret = xa_alloc(ðosu_priv->perfmons, &id, perfmon, xa_limit_32b, + GFP_KERNEL); + + if (ret < 0) { + kfree(perfmon); + return ret; + } + + req->id = id; + + return 0; +} + +int ethosu_ioctl_perfmon_destroy(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv; + struct drm_ethosu_perfmon_destroy *req = data; + struct ethosu_perfmon *perfmon; + + perfmon = xa_erase(ðosu_priv->perfmons, req->id); + if (!perfmon) + return -EINVAL; + + ethosu_perfmon_delete(ethosu_priv, perfmon); + + return 0; +} + +int ethosu_ioctl_perfmon_get_values(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct ethosu_device *ethosu = to_ethosu_device(dev); + struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv; + struct drm_ethosu_perfmon_get_values *req = data; + struct ethosu_perfmon *perfmon; + int ret = 0; + + if (req->pad != 0) + return -EINVAL; + + perfmon = ethosu_perfmon_find(ethosu_priv, req->id); + if (!perfmon) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev->dev); + if (ret) { + ethosu_perfmon_put(perfmon); + return ret; + } + ethosu_perfmon_stop(ethosu, perfmon, true); + + pm_runtime_put_autosuspend(dev->dev); + + if (copy_to_user(u64_to_user_ptr(req->values_ptr), perfmon->values, + perfmon->ncounters * sizeof(u64))) + ret = -EFAULT; + + ethosu_perfmon_put(perfmon); + + return ret; +} + +int ethosu_ioctl_perfmon_set_global(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv; + struct drm_ethosu_perfmon_set_global *req = data; + struct ethosu_device *ethosu = to_ethosu_device(dev); + struct ethosu_perfmon *perfmon; + + if (req->flags & ~DRM_ETHOSU_PERFMON_CLEAR_GLOBAL) + return -EINVAL; + + perfmon = ethosu_perfmon_find(ethosu_priv, req->id); + if (!perfmon) + return -EINVAL; + + /* If the request is to clear the global performance monitor */ + if (req->flags & DRM_ETHOSU_PERFMON_CLEAR_GLOBAL) { + struct ethosu_perfmon *old; + + scoped_guard(mutex, ðosu->perfmon_state.lock) { + old = ethosu->global_perfmon; + if (!old) { + ethosu_perfmon_put(perfmon); + return -EINVAL; + } + + ethosu->global_perfmon = NULL; + ethosu_perfmon_stop_locked(ethosu, old, true); + } + + ethosu_perfmon_put(old); + ethosu_perfmon_put(perfmon); + + return 0; + } + + scoped_guard(mutex, ðosu->perfmon_state.lock) { + if (ethosu->perfmon_state.active || ethosu->global_perfmon) { + ethosu_perfmon_put(perfmon); + return -EBUSY; + } + + ethosu->global_perfmon = perfmon; + } + + return 0; +} diff --git a/include/uapi/drm/ethosu_accel.h b/include/uapi/drm/ethosu_accel.h index af78bb4686d7..5b97d59a7806 100644 --- a/include/uapi/drm/ethosu_accel.h +++ b/include/uapi/drm/ethosu_accel.h @@ -43,6 +43,11 @@ enum drm_ethosu_ioctl_id { /** @DRM_ETHOSU_SUBMIT: Submit a job and BOs to run. */ DRM_ETHOSU_SUBMIT, + + DRM_ETHOSU_PERFMON_CREATE, + DRM_ETHOSU_PERFMON_DESTROY, + DRM_ETHOSU_PERFMON_GET_VALUES, + DRM_ETHOSU_PERFMON_SET_GLOBAL, }; /** @@ -79,6 +84,7 @@ struct drm_ethosu_npu_info { __u32 config; __u32 sram_size; + __u32 pmu_counters; }; /** @@ -220,10 +226,54 @@ struct drm_ethosu_submit { /** Input: Number of jobs passed in. */ __u32 job_count; - /** Reserved, must be zero. */ + /** Input: Id returned by DRM_ETHOSU_PERFMON_CREATE */ + __u32 perfmon_id; +}; + +#define DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS 8 +#define DRM_ETHOSU_MAX_PERF_COUNTERS \ + (DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS + 1) + +struct drm_ethosu_perfmon_create { + __u32 id; + __u32 ncounters; + __u16 counters[DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS]; +}; + +struct drm_ethosu_perfmon_destroy { + __u32 id; __u32 pad; }; +/* + * Returns the values of the performance counters tracked by this + * perfmon (as an array of (ncounters + 1) u64 values). + * + * No implicit synchronization is performed, so the user has to + * guarantee that any jobs using this perfmon have already been + * completed. + */ +struct drm_ethosu_perfmon_get_values { + __u32 id; + __u32 pad; + __u64 values_ptr; +}; + +#define DRM_ETHOSU_PERFMON_CLEAR_GLOBAL 0x0001 + +/** + * struct drm_ethosu_perfmon_set_global - ioctl to define a global performance + * monitor + * + * The global performance monitor will be used for all jobs. If a global + * performance monitor is defined, jobs with a self-defined performance + * monitor won't be allowed. + */ +struct drm_ethosu_perfmon_set_global { + __u32 flags; + __u32 id; +}; + /** * DRM_IOCTL_ETHOSU() - Build a ethosu IOCTL number * @__access: Access type. Must be R, W or RW. @@ -252,6 +302,14 @@ enum { DRM_IOCTL_ETHOSU(WR, CMDSTREAM_BO_CREATE, cmdstream_bo_create), DRM_IOCTL_ETHOSU_SUBMIT = DRM_IOCTL_ETHOSU(WR, SUBMIT, submit), + DRM_IOCTL_ETHOSU_PERFMON_CREATE = + DRM_IOCTL_ETHOSU(WR, PERFMON_CREATE, perfmon_create), + DRM_IOCTL_ETHOSU_PERFMON_DESTROY = + DRM_IOCTL_ETHOSU(WR, PERFMON_DESTROY, perfmon_destroy), + DRM_IOCTL_ETHOSU_PERFMON_GET_VALUES = + DRM_IOCTL_ETHOSU(WR, PERFMON_GET_VALUES, perfmon_get_values), + DRM_IOCTL_ETHOSU_PERFMON_SET_GLOBAL = + DRM_IOCTL_ETHOSU(WR, PERFMON_SET_GLOBAL, perfmon_set_global), }; #if defined(__cplusplus) From e1696f1fc99dc0ff761a012230587b23dec064fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Sat, 30 May 2026 15:37:45 -0300 Subject: [PATCH 042/167] drm/v3d: Reduce PM runtime autosuspend delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 100ms autosuspend delay was only ever a workaround: shorter delays caused more frequent runtime suspend/resume cycles on the BCM2711 (Raspberry Pi 4), which exposed the cache and MMU coherency bugs as random GPU hangs. With those hangs resolved, the inflated delay is no longer necessary. Reduce it from 100ms to 50ms so the GPU power domain can be released sooner once the GPU goes idle. Link: https://patch.msgid.link/20260530-v3d-fix-rpi4-freezes-v1-4-c2c8307da6ce@igalia.com Signed-off-by: Maíra Canal Reviewed-by: Iago Toral Quiroga --- drivers/gpu/drm/v3d/v3d_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.c b/drivers/gpu/drm/v3d/v3d_drv.c index fc81dd1247e3..805d84435ee0 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.c +++ b/drivers/gpu/drm/v3d/v3d_drv.c @@ -420,7 +420,7 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) ident3 = V3D_READ(V3D_HUB_IDENT3); v3d->rev = V3D_GET_FIELD(ident3, V3D_HUB_IDENT3_IPREV); - pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_set_autosuspend_delay(dev, 50); pm_runtime_use_autosuspend(dev); ret = drm_dev_register(drm, 0); From 09dfa47fc557f945871dcf279c8e687e55d3550d Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 1 Jun 2026 21:00:03 +0300 Subject: [PATCH 043/167] drm/rockchip: vop2: Rename CSC_BT2020 to CSC_BT2020L Rename CSC_BT2020 to CSC_BT2020L for consistency with the other limited-range enum members (CSC_BT601L, CSC_BT709L) and to distinguish it from a potential future full-range BT.2020 variant. Signed-off-by: Cristian Ciocaltea Acked-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601-vop2-bg-yuv-v2-1-e5aef1d16fec@collabora.com --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index a160077a507f..64ac07cb1b0d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -658,7 +658,7 @@ static int vop2_convert_csc_mode(int csc_mode) case V4L2_COLORSPACE_JPEG: return CSC_BT601F; case V4L2_COLORSPACE_BT2020: - return CSC_BT2020; + return CSC_BT2020L; default: return CSC_BT709L; } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index 37722652844a..ffcb39c130aa 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -373,7 +373,7 @@ enum vop_csc_format { CSC_BT601L, CSC_BT709L, CSC_BT601F, - CSC_BT2020, + CSC_BT2020L, }; enum src_factor_mode { From 28e670a0885cf2501fd51189ce7b6f5b7cacebf9 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 1 Jun 2026 21:00:04 +0300 Subject: [PATCH 044/167] drm/rockchip: vop2: Add YUV support to background color The VOP2 background color must be programmed with 10-bit precision, using YUV format when the overlay operates in YUV mode, and RGB otherwise. Add the required RGB-to-YCbCr conversion logic, covering all color spaces supported by the display controller: BT601L, BT601F, BT709L and BT2020L. Since the color is currently programmed to hardware on every atomic commit, minimize the computation cost by splitting the work across the two paths: in atomic_enable(), perform the conversion unconditionally (the hardware state is unknown after power-on), while in atomic_flush(), perform it only when the DRM property has actually changed. Signed-off-by: Cristian Ciocaltea Acked-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601-vop2-bg-yuv-v2-2-e5aef1d16fec@collabora.com --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 153 +++++++++++++++++-- 1 file changed, 138 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 64ac07cb1b0d..9b261f0c7eec 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -728,6 +728,89 @@ static void vop2_setup_csc_mode(struct vop2_video_port *vp, vop2_win_write(win, VOP2_WIN_CSC_MODE, csc_mode); } +/* + * RGB-to-YCbCr conversion based on color_to_ycbcr() and rgb2ycbcr() from + * drivers/media/common/v4l2-tpg/v4l2-tpg-core.c. + * + * Limited-range Y offset & chroma midpoint are expressed in 16-bit space. + */ +#define RGB2YUV_LIMITED_Y_OFFSET (16 << 8) +#define RGB2YUV_CHROMA_OFFSET (128 << 8) +#define COEFF(v, r) ((s32)(0.5 + (v) * (r) * 256.0)) + +struct rgb2yuv_matrix { + s32 y_r, y_g, y_b; + s32 cb_r, cb_g, cb_b; + s32 cr_r, cr_g, cr_b; + s32 y_offset; +}; + +/* BT.601 Limited range */ +static const struct rgb2yuv_matrix rgb2yuv_bt601l = { + .y_r = COEFF(0.299, 219), .y_g = COEFF(0.587, 219), .y_b = COEFF(0.114, 219), + .cb_r = COEFF(-0.1687, 224), .cb_g = COEFF(-0.3313, 224), .cb_b = COEFF(0.5, 224), + .cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4187, 224), .cr_b = COEFF(-0.0813, 224), + .y_offset = RGB2YUV_LIMITED_Y_OFFSET, +}; + +/* BT.601 Full range */ +static const struct rgb2yuv_matrix rgb2yuv_bt601f = { + .y_r = COEFF(0.299, 255), .y_g = COEFF(0.587, 255), .y_b = COEFF(0.114, 255), + .cb_r = COEFF(-0.1687, 255), .cb_g = COEFF(-0.3313, 255), .cb_b = COEFF(0.5, 255), + .cr_r = COEFF(0.5, 255), .cr_g = COEFF(-0.4187, 255), .cr_b = COEFF(-0.0813, 255), + .y_offset = 0, +}; + +/* BT.709 Limited range */ +static const struct rgb2yuv_matrix rgb2yuv_bt709l = { + .y_r = COEFF(0.2126, 219), .y_g = COEFF(0.7152, 219), .y_b = COEFF(0.0722, 219), + .cb_r = COEFF(-0.1146, 224), .cb_g = COEFF(-0.3854, 224), .cb_b = COEFF(0.5, 224), + .cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4542, 224), .cr_b = COEFF(-0.0458, 224), + .y_offset = RGB2YUV_LIMITED_Y_OFFSET, +}; + +/* BT.2020 Limited range */ +static const struct rgb2yuv_matrix rgb2yuv_bt2020l = { + .y_r = COEFF(0.2627, 219), .y_g = COEFF(0.6780, 219), .y_b = COEFF(0.0593, 219), + .cb_r = COEFF(-0.1396, 224), .cb_g = COEFF(-0.3604, 224), .cb_b = COEFF(0.5, 224), + .cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4598, 224), .cr_b = COEFF(-0.0402, 224), + .y_offset = RGB2YUV_LIMITED_Y_OFFSET, +}; + +static const struct rgb2yuv_matrix * +vop2_rgb2yuv_get_matrix(enum vop_csc_format csc) +{ + switch (csc) { + case CSC_BT601L: + return &rgb2yuv_bt601l; + case CSC_BT601F: + return &rgb2yuv_bt601f; + case CSC_BT2020L: + return &rgb2yuv_bt2020l; + case CSC_BT709L: + default: + return &rgb2yuv_bt709l; + } +} + +/* Convert an RGB (16bpc) to YUV444 (16bpc). */ +static void vop2_rgb16_to_yuv16(int v4l2_cs, u16 r, u16 g, u16 b, + u16 *y, u16 *cb, u16 *cr) +{ + enum vop_csc_format csc = vop2_convert_csc_mode(v4l2_cs); + const struct rgb2yuv_matrix *m = vop2_rgb2yuv_get_matrix(csc); + s64 rs = r, gs = g, bs = b; + s64 ys, cbs, crs; + + ys = m->y_r * rs + m->y_g * gs + m->y_b * bs; + cbs = m->cb_r * rs + m->cb_g * gs + m->cb_b * bs; + crs = m->cr_r * rs + m->cr_g * gs + m->cr_b * bs; + + *y = (ys >> 16) + m->y_offset; + *cb = (cbs >> 16) + RGB2YUV_CHROMA_OFFSET; + *cr = (crs >> 16) + RGB2YUV_CHROMA_OFFSET; +} + static void vop2_crtc_enable_irq(struct vop2_video_port *vp, u32 irq) { struct vop2 *vop2 = vp->vop2; @@ -1554,12 +1637,58 @@ static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl) DITHER_DOWN_ALLEGRO); } -static void vop2_post_config(struct drm_crtc *crtc) +static void vop2_bgcolor_setup(struct drm_crtc *crtc, bool force, + struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state) +{ + struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state); + struct rockchip_crtc_state *old_vcstate = to_rockchip_crtc_state(old_crtc_state); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + u64 bgcolor = new_crtc_state->background_color; + u16 y, cb, cr; + u32 val; + + if (!force && old_crtc_state->background_color == bgcolor && + old_vcstate->color_space == new_vcstate->color_space) + return; + + /* + * Background color is programmed with 10 bits of precision, using YUV + * format when operating in YUV overlay mode, and RGB otherwise. + */ + if (new_vcstate->yuv_overlay) { + vop2_rgb16_to_yuv16(new_vcstate->color_space, + DRM_ARGB64_GETR(bgcolor), + DRM_ARGB64_GETG(bgcolor), + DRM_ARGB64_GETB(bgcolor), + &y, &cb, &cr); + + val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED, cr >> 6); + FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val, y >> 6); + FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val, cb >> 6); + } else { + /* + * Since performance is more important than accuracy here, make + * use of the DRM_ARGB64_GET*_BPCS() helpers. + */ + val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED, + DRM_ARGB64_GETR_BPCS(bgcolor, 10)); + FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val, + DRM_ARGB64_GETG_BPCS(bgcolor, 10)); + FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val, + DRM_ARGB64_GETB_BPCS(bgcolor, 10)); + } + + vop2_vp_write(vp, RK3568_VP_DSP_BG, val); +} + +static void vop2_post_config(struct drm_crtc *crtc, bool force, + struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state) { struct vop2_video_port *vp = to_vop2_video_port(crtc); struct vop2 *vop2 = vp->vop2; - struct drm_display_mode *mode = &crtc->state->adjusted_mode; - u64 bgcolor = crtc->state->background_color; + struct drm_display_mode *mode = &new_crtc_state->adjusted_mode; u16 vtotal = mode->crtc_vtotal; u16 hdisplay = mode->crtc_hdisplay; u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start; @@ -1605,15 +1734,7 @@ static void vop2_post_config(struct drm_crtc *crtc) vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val); } - /* - * Background color is programmed with 10 bits of precision. - * Since performance is more important than accuracy here, - * make use of the DRM_ARGB64_GET*_BPCS() helpers. - */ - val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED, DRM_ARGB64_GETR_BPCS(bgcolor, 10)); - FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val, DRM_ARGB64_GETG_BPCS(bgcolor, 10)); - FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val, DRM_ARGB64_GETB_BPCS(bgcolor, 10)); - vop2_vp_write(vp, RK3568_VP_DSP_BG, val); + vop2_bgcolor_setup(crtc, force, new_crtc_state, old_crtc_state); } static int us_to_vertical_line(struct drm_display_mode *mode, int us) @@ -1628,8 +1749,9 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct vop2 *vop2 = vp->vop2; const struct vop2_data *vop2_data = vop2->data; const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc_state); struct drm_display_mode *mode = &crtc->state->adjusted_mode; unsigned long clock = mode->crtc_clock * 1000; u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; @@ -1799,7 +1921,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, clk_set_rate(vp->dclk, clock); - vop2_post_config(crtc); + vop2_post_config(crtc, true, crtc_state, old_crtc_state); vop2_cfg_done(vp); @@ -1874,6 +1996,7 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_commit *state) { struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); struct vop2_video_port *vp = to_vop2_video_port(crtc); struct vop2 *vop2 = vp->vop2; @@ -1881,7 +2004,7 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc, if (!drm_atomic_crtc_needs_modeset(crtc_state) && crtc_state->color_mgmt_changed) vop2_crtc_atomic_try_set_gamma_locked(vop2, vp, crtc, crtc_state); - vop2_post_config(crtc); + vop2_post_config(crtc, false, crtc_state, old_crtc_state); vop2_cfg_done(vp); From ed04e8e2307f35b3d8d49a554faf5e72d3d224e6 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 1 Jun 2026 19:13:44 +0300 Subject: [PATCH 045/167] drm/bridge: synopsys: dw-dp: Support unregistering the AUX channel The DisplayPort AUX channel gets initialized and registered during dw_dp_bind(), but it is never unregistered, which may lead to resource leaks and/or use-after-free. Add the missing dw_dp_unbind() function to allow the users of the library to handle the required cleanup, i.e. unregister the AUX adapter. Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library") Reviewed-by: Andy Yan Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601-drm-rk-fixes-v4-1-c3f3f123e1da@collabora.com --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 6 ++++++ include/drm/bridge/dw_dp.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 21541be094c4..36ee6e027af5 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -2093,6 +2093,12 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } EXPORT_SYMBOL_GPL(dw_dp_bind); +void dw_dp_unbind(struct dw_dp *dp) +{ + drm_dp_aux_unregister(&dp->aux); +} +EXPORT_SYMBOL_GPL(dw_dp_unbind); + MODULE_AUTHOR("Andy Yan "); MODULE_DESCRIPTION("DW DP Core Library"); MODULE_LICENSE("GPL"); diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h index 25363541e69d..22105c3e8e4d 100644 --- a/include/drm/bridge/dw_dp.h +++ b/include/drm/bridge/dw_dp.h @@ -24,4 +24,5 @@ struct dw_dp_plat_data { struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, const struct dw_dp_plat_data *plat_data); +void dw_dp_unbind(struct dw_dp *dp); #endif /* __DW_DP__ */ From 0a01412178047bf3ff351c7e75d373e411072a87 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 1 Jun 2026 19:13:45 +0300 Subject: [PATCH 046/167] drm/rockchip: dw_dp: Add missing newline in dev_err_probe() message Add the missing trailing newline to dev_err_probe() call in dw_dp_rockchip_bind(). Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Fixes: 26cb3e26efa7 ("drm/rockchip: dw_dp: Simplify error handling") Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601-drm-rk-fixes-v4-2-c3f3f123e1da@collabora.com --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index 32bc73a1d5e4..f137f699737c 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -109,7 +109,7 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * connector = drm_bridge_connector_init(drm_dev, encoder); if (IS_ERR(connector)) return dev_err_probe(dev, PTR_ERR(connector), - "Failed to init bridge connector"); + "Failed to init bridge connector\n"); return 0; } From cc6d7aca2f37a1525a94ef97eb3ce361732c876c Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 1 Jun 2026 19:13:46 +0300 Subject: [PATCH 047/167] drm/rockchip: dw_dp: Release core resources Core resources such as the DisplayPort AUX channel get initialized and registered during dw_dp_bind(), but are never unregistered, which may lead to memory leaks and/or use-after-free: [ 224.661371] BUG: KASAN: slab-use-after-free in device_is_dependent+0xe0/0x2b0 [ 224.662015] Read of size 8 at addr ffff00011aee8550 by task modprobe/658 [ 224.662612] [ 224.662752] CPU: 7 UID: 0 PID: 658 Comm: modprobe Not tainted 7.0.0-rc2-next-20260305 #14 PREEMPT [ 224.662759] Hardware name: Radxa ROCK 5B (DT) [ 224.662762] Call trace: [ 224.662764] show_stack+0x20/0x38 (C) [ 224.662772] dump_stack_lvl+0x6c/0x98 [ 224.662777] print_report+0x160/0x4b8 [ 224.662783] kasan_report+0xb4/0xe0 [ 224.662790] __asan_report_load8_noabort+0x20/0x30 [ 224.662796] device_is_dependent+0xe0/0x2b0 [ 224.662802] device_is_dependent+0x108/0x2b0 [ 224.662808] device_link_add+0x1f8/0x10b0 [ 224.662813] devm_of_phy_get_by_index+0x120/0x200 [ 224.662819] dw_dp_bind+0x34c/0xb10 [dw_dp] [ 224.662830] dw_dp_rockchip_bind+0x194/0x250 [rockchipdrm] [ 224.662864] component_bind_all+0x3a8/0x720 [ 224.662869] rockchip_drm_bind+0x120/0x390 [rockchipdrm] [ 224.662899] try_to_bring_up_aggregate_device+0x76c/0x838 [ 224.662904] component_master_add_with_match+0x1f4/0x230 [ 224.662909] rockchip_drm_platform_probe+0x420/0x538 [rockchipdrm] [ 224.662939] platform_probe+0xe8/0x168 [ 224.662945] really_probe+0x340/0x828 [ 224.662950] __driver_probe_device+0x2e0/0x350 [ 224.662954] driver_probe_device+0x80/0x140 [ 224.662959] __driver_attach+0x398/0x460 [ 224.662964] bus_for_each_dev+0xe0/0x198 [ 224.662968] driver_attach+0x50/0x68 [ 224.662972] bus_add_driver+0x2a0/0x4c0 [ 224.662977] driver_register+0x294/0x360 [ 224.662982] __platform_driver_register+0x7c/0x98 [ 224.662987] rockchip_drm_init+0xc4/0xff8 [rockchipdrm] Since a previous commit exported dw_dp_unbind() function in DW DP core library to take care of the necessary cleanup, use this in the component's unbind() callback, as well as in its bind() error path. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601-drm-rk-fixes-v4-3-c3f3f123e1da@collabora.com --- drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c index f137f699737c..0de822360c8d 100644 --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -107,15 +107,26 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void * return PTR_ERR(dp->base); connector = drm_bridge_connector_init(drm_dev, encoder); - if (IS_ERR(connector)) + if (IS_ERR(connector)) { + dw_dp_unbind(dp->base); return dev_err_probe(dev, PTR_ERR(connector), "Failed to init bridge connector\n"); + } return 0; } +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + + dw_dp_unbind(dp->base); +} + static const struct component_ops dw_dp_rockchip_component_ops = { .bind = dw_dp_rockchip_bind, + .unbind = dw_dp_rockchip_unbind, }; static int dw_dp_probe(struct platform_device *pdev) From 9f5670802df085ad343146561e69bac43e9905d2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 4 May 2026 21:23:59 +0300 Subject: [PATCH 048/167] drm/rockchip: vop2: Fix wrong wait target in layer cfg done check rk3568_vop2_setup_layer_mixer() waits for the previous Video Port (VP) layer configuration to take effect before writing a new one to the shared RK3568_OVL_LAYER_SEL shadow register. However, it passes vop2->old_layer_sel to rk3568_vop2_wait_for_layer_cfg_done() as the expected value, which at that point already contains the new VP layer. This causes the wait to poll for a value that has not been written to the shadow register yet, resulting in spurious timeouts when two non-blocking atomic commits race: rockchip-drm display-subsystem: [drm] *ERROR* wait layer cfg done timeout [...] Pass the local old_layer_sel instead, which still holds the value captured from vop2->old_layer_sel before it was overwritten, i.e. the previous VP target that the hardware is expected to latch. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Cristian Ciocaltea Reviewed-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260504-vop2-layer-cfg-tmout-v1-1-730226a7331e@collabora.com --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 02a788a4dfdd..edca0fb16e08 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2307,7 +2307,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); } vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); From d1ad644e572c0647ad8428439eafea0aacfccf9e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 4 May 2026 21:24:00 +0300 Subject: [PATCH 049/167] drm/rockchip: vop2: Wait for layer cfg done before switching LAYERSEL_REGDONE_SEL LAYERSEL_REGDONE_SEL mask of RK3568_OVL_CTRL register controls which Video Port (VP) vsync latches the shared RK3568_OVL_{LAYER|PORT}_SEL shadow registers into the active configuration. rk3568_vop2_setup_layer_mixer() overwrites LAYERSEL_REGDONE_SEL to the current VP ID before waiting for the previous VP layer configuration to take effect. As a consequence, the previous VP vsync can no longer trigger the latch, so the wait polls a value that might never appear. Move the layer cfg done wait before the RK3568_OVL_CTRL write so the previous VP vsync can still commit the pending configuration. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Cristian Ciocaltea Reviewed-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260504-vop2-layer-cfg-tmout-v1-2-730226a7331e@collabora.com --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index edca0fb16e08..5206f01ec787 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2289,15 +2289,6 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * lead to the configuration of the previous VP being take effect along with the VSYNC * of the new VP. */ - if (layer_sel != old_layer_sel || port_sel != old_port_sel) - ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); - vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); - - if (port_sel != old_port_sel) { - vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); - vop2_cfg_done(vp); - rk3568_vop2_wait_for_port_mux_done(vop2); - } if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) { cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE); @@ -2310,6 +2301,16 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); } + if (layer_sel != old_layer_sel || port_sel != old_port_sel) + ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); + vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); + + if (port_sel != old_port_sel) { + vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); + vop2_cfg_done(vp); + rk3568_vop2_wait_for_port_mux_done(vop2); + } + vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); mutex_unlock(&vop2->ovl_lock); } From 6a2f7339191b652f7ff998595df90987b33c3fda Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 4 May 2026 21:24:01 +0300 Subject: [PATCH 050/167] drm/rockchip: vop2: Delay old_{layer|port}_sel updates in setup_layer_mixer() The old_layer_sel and old_port_sel local variables were introduced to hold the previous VP configuration for comparisons and wait targets, working around the premature update of vop2->old_layer_sel and vop2->old_port_sel earlier in the function. Remove these superfluous locals and instead defer the assignments of vop2->old_layer_sel and vop2->old_port_sel to just before the corresponding shadow register writes, where the transition from old to new logically belongs. No functional change intended. Signed-off-by: Cristian Ciocaltea Reviewed-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260504-vop2-layer-cfg-tmout-v1-3-730226a7331e@collabora.com --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 5206f01ec787..1f5e8c2acecd 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2136,9 +2136,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) struct drm_plane *plane; u32 layer_sel = 0; u32 port_sel; - u32 old_layer_sel = 0; u32 atv_layer_sel = 0; - u32 old_port_sel = 0; u8 layer_id; u8 old_layer_id; u8 layer_sel_id; @@ -2161,8 +2159,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) else ovl_ctrl &= ~RK3568_OVL_CTRL__YUV_MODE(vp->id); - old_port_sel = vop2->old_port_sel; - port_sel = old_port_sel; + port_sel = vop2->old_port_sel; port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT; if (vp0->nlayers) @@ -2188,8 +2185,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) port_sel |= FIELD_PREP(RK3588_OVL_PORT_SET__PORT3_MUX, 7); atv_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL); - old_layer_sel = vop2->old_layer_sel; - layer_sel = old_layer_sel; + layer_sel = vop2->old_layer_sel; ofs = 0; for (i = 0; i < vp->id; i++) @@ -2273,8 +2269,6 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) old_win->data->layer_sel_id[vp->id]); } - vop2->old_layer_sel = layer_sel; - vop2->old_port_sel = port_sel; /* * As the RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are shared by all Video Ports, * and the configuration take effect by one Video Port's vsync. @@ -2290,7 +2284,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * of the new VP. */ - if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) { + if (layer_sel != vop2->old_layer_sel && atv_layer_sel != vop2->old_layer_sel) { cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE); cfg_done &= (BIT(vop2->data->nr_vps) - 1); cfg_done &= ~BIT(vp->id); @@ -2298,20 +2292,23 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); } - if (layer_sel != old_layer_sel || port_sel != old_port_sel) + if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel) ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); - if (port_sel != old_port_sel) { + if (port_sel != vop2->old_port_sel) { + vop2->old_port_sel = port_sel; vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); vop2_cfg_done(vp); rk3568_vop2_wait_for_port_mux_done(vop2); } + vop2->old_layer_sel = layer_sel; vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); + mutex_unlock(&vop2->ovl_lock); } From bc5ffd6fc30cc52983d95fbe8783bb4233db6f86 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 4 May 2026 21:24:02 +0300 Subject: [PATCH 051/167] drm/rockchip: vop2: Drop redundant zero-init in setup_layer_mixer() The layer_sel and atv_layer_sel local variables in rk3568_vop2_setup_layer_mixer() are unconditionally assigned from vop2->old_layer_sel and the RK3568_OVL_LAYER_SEL register read, respectively, before any use. Remove the superfluous zero-initializers. Signed-off-by: Cristian Ciocaltea Reviewed-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260504-vop2-layer-cfg-tmout-v1-4-730226a7331e@collabora.com --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 1f5e8c2acecd..0849bd922ffb 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2134,9 +2134,9 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; struct drm_plane *plane; - u32 layer_sel = 0; + u32 layer_sel; u32 port_sel; - u32 atv_layer_sel = 0; + u32 atv_layer_sel; u8 layer_id; u8 old_layer_id; u8 layer_sel_id; From dc3de97433db03157ba6f87b6494ff1ad416a5a8 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Mon, 4 May 2026 21:24:03 +0300 Subject: [PATCH 052/167] drm/rockchip: vop2: Use vop2->old_layer_sel directly in wait_for_layer_cfg_done() After the old_layer_sel local was removed, the only caller of rk3568_vop2_wait_for_layer_cfg_done() already passes vop2->old_layer_sel as the expected value. Drop the redundant parameter and read the member directly inside the function. Signed-off-by: Cristian Ciocaltea Reviewed-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260504-vop2-layer-cfg-tmout-v1-5-730226a7331e@collabora.com --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 0849bd922ffb..1d8473a6dfd1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2115,7 +2115,7 @@ static u32 rk3568_vop2_read_layer_cfg(struct vop2 *vop2) return vop2_readl(vop2, RK3568_OVL_LAYER_SEL); } -static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg) +static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2) { u32 atv_layer_cfg; int ret; @@ -2124,10 +2124,10 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg) * Spin until the previous layer configuration is done. */ ret = readx_poll_timeout_atomic(rk3568_vop2_read_layer_cfg, vop2, atv_layer_cfg, - atv_layer_cfg == cfg, 10, 50 * 1000); + atv_layer_cfg == vop2->old_layer_sel, 10, 50 * 1000); if (ret) drm_err_ratelimited(vop2->drm, "wait layer cfg done timeout: 0x%x--0x%x\n", - atv_layer_cfg, cfg); + atv_layer_cfg, vop2->old_layer_sel); } static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) @@ -2292,7 +2292,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2); } if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel) From 826ab13cf73e7bd5b268085c9594850f6a7fc7fd Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:50 +0800 Subject: [PATCH 053/167] dt-bindings: display: rockchip: analogix-dp: Fix hclk as third clock for RK3588 RK3588 eDP controller requires HCLK_VO1 to access the VO1 GRF registers and enable the video datapath. Previously, the clock was enabled implicitly via the 'rockchip,vo-grf' phandle reference, which allowed the eDP to work without explicitly managing the hclk_vo1 clock. However, this is not safe or explicit. To make the clock dependency explicit, enforce per-SoC clock-names requirements: - RK3288: 2 clocks (dp, pclk) - RK3399: 3 clocks (dp, pclk, grf) - RK3588: 3 clocks (dp, pclk, hclk) Do not reuse the 'grf' clock name for RK3588 because it represents a different clock with distinct control logic: - The 'grf' clock is only for GRF register access and is toggled dynamically during register access. - The 'hclk' clock controls both GRF access and video datapath gating, and must remain enabled during probe. Fixes: f855146263b1 ("dt-bindings: display: rockchip: analogix-dp: Add support for RK3588") Signed-off-by: Damon Ding Acked-by: Conor Dooley Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-2-damon.ding@rock-chips.com --- .../rockchip/rockchip,analogix-dp.yaml | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml index d99b23b88cc5..6643889f2a9c 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml @@ -26,7 +26,9 @@ properties: items: - const: dp - const: pclk - - const: grf + - enum: + - grf + - hclk power-domains: maxItems: 1 @@ -60,6 +62,35 @@ required: allOf: - $ref: /schemas/display/bridge/analogix,dp.yaml# + - if: + properties: + compatible: + contains: + enum: + - rockchip,rk3288-dp + then: + properties: + clocks: + maxItems: 2 + clock-names: + maxItems: 2 + + - if: + properties: + compatible: + contains: + enum: + - rockchip,rk3399-edp + then: + properties: + clocks: + minItems: 3 + clock-names: + items: + - const: dp + - const: pclk + - const: grf + - if: properties: compatible: @@ -68,6 +99,13 @@ allOf: - rockchip,rk3588-edp then: properties: + clocks: + minItems: 3 + clock-names: + items: + - const: dp + - const: pclk + - const: hclk resets: minItems: 2 reset-names: From 38f41d4e704c2a9bfc025bb17594d53be010a629 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:51 +0800 Subject: [PATCH 054/167] dt-bindings: display: rockchip: analogix-dp: Add per-clock descriptions Supplement dedicated description for each clock in the clocks property, clarifying the function of each clock input for the Analogix DP controller binding. Acked-by: Conor Dooley Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-3-damon.ding@rock-chips.com --- .../bindings/display/rockchip/rockchip,analogix-dp.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml index 6643889f2a9c..1ef1d56ef6e2 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml @@ -19,7 +19,10 @@ properties: clocks: minItems: 2 - maxItems: 3 + items: + - description: Reference clock + - description: APB bus clock + - description: GRF or AHB bus clock clock-names: minItems: 2 From 104f20616d72825fdcf56cfdc5f89f4e96fd8dbe Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:54 +0800 Subject: [PATCH 055/167] drm/rockchip: analogix_dp: Enable hclk for RK3588 Acquire and enable the HCLK_VO1 bus clock explicitly for RK3588 eDP controller to guarantee register and datapath access. The clock was previously enabled implicitly via rockchip,vo-grf phandle reference, which relies on side effect and is fragile. Fetch optional "hclk" clock in driver to align with updated device tree binding and keep consistent with hardware clock dependency. Fixes: 729f8eefdcad ("drm/rockchip: analogix_dp: Add support for RK3588") Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-6-damon.ding@rock-chips.com --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 06072efd7fca..d2af5eb29dbb 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -311,6 +311,7 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp) { struct device *dev = dp->dev; struct device_node *np = dev->of_node; + struct clk *clk; dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(dp->grf)) @@ -327,6 +328,11 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp) return dev_err_probe(dev, PTR_ERR(dp->pclk), "failed to get pclk property\n"); + clk = devm_clk_get_optional_enabled(dev, "hclk"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get hclk property\n"); + dp->rst = devm_reset_control_get(dev, "dp"); if (IS_ERR(dp->rst)) return dev_err_probe(dev, PTR_ERR(dp->rst), From 87e060521371257ddbb77964b66e60d80afcc7b2 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:55 +0800 Subject: [PATCH 056/167] drm/rockchip: analogix_dp: Fix OF node reference leak via auto cleanup Sashiko reported a reference leak in rockchip_dp_drm_encoder_enable(), the of_get_child_by_name() function does not call of_node_put() in a symmetrical way [1]. Fix the device node reference leak by using __free(device_node) to automatically manage of_node_put() for all device nodes. Fixes: 729f8eefdcad ("drm/rockchip: analogix_dp: Add support for RK3588") Link: https://sashiko.dev/#/patchset/20260527024336.191433-1-damon.ding@rock-chips.com?part=5 #1 Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-7-damon.ding@rock-chips.com --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index d2af5eb29dbb..b1ed25cefe5e 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -8,6 +8,7 @@ * Jeff Chen */ +#include #include #include #include @@ -206,7 +207,6 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; struct of_endpoint endpoint; - struct device_node *remote_port, *remote_port_parent; char name[32]; u32 port_id; int ret; @@ -230,18 +230,22 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, if (ret < 0) return; - remote_port_parent = of_graph_get_remote_port_parent(endpoint.local_node); + struct device_node *remote_port_parent __free(device_node) = + of_graph_get_remote_port_parent(endpoint.local_node); if (remote_port_parent) { - if (of_get_child_by_name(remote_port_parent, "ports")) { - remote_port = of_graph_get_remote_port(endpoint.local_node); + struct device_node *ports __free(device_node) = + of_get_child_by_name(remote_port_parent, "ports"); + + if (ports) { + struct device_node *remote_port __free(device_node) = + of_graph_get_remote_port(endpoint.local_node); + of_property_read_u32(remote_port, "reg", &port_id); - of_node_put(remote_port); sprintf(name, "%s vp%d", remote_port_parent->full_name, port_id); } else { sprintf(name, "%s %s", remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb"); } - of_node_put(remote_port_parent); DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); } From 473e50b27b0020ade5fd97ca04d941e448c5659a Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:56 +0800 Subject: [PATCH 057/167] dt-bindings: display: rockchip: analogix-dp: Add support for RK3576 RK3576 integrates an eDP TX controller compatible with the existing RK3588 hardware design, reuse the same binding configuration directly. Acked-by: Conor Dooley Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-8-damon.ding@rock-chips.com --- .../bindings/display/rockchip/rockchip,analogix-dp.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml index 1ef1d56ef6e2..6548f157fd96 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml @@ -15,6 +15,7 @@ properties: enum: - rockchip,rk3288-dp - rockchip,rk3399-edp + - rockchip,rk3576-edp - rockchip,rk3588-edp clocks: @@ -99,6 +100,7 @@ allOf: compatible: contains: enum: + - rockchip,rk3576-edp - rockchip,rk3588-edp then: properties: From 709445fb6fc57aa96d48c8492e84ed4b2a9a4b5e Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:58 +0800 Subject: [PATCH 058/167] drm/bridge: analogix_dp: Rename and simplify is_rockchip() Rename inline helper is_rockchip() to analogix_dp_is_rockchip() to follow driver namespace convention consistently across code. Replace chained equality comparisons with switch-case layout to improve readability and simplify adding new SoC entries later. Signed-off-by: Damon Ding Suggested-by: Nicolas Frattaroli Reviewed-by: Luca Ceresoli Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-10-damon.ding@rock-chips.com --- .../gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +- .../gpu/drm/bridge/analogix/analogix_dp_reg.c | 18 +++++++++--------- include/drm/bridge/analogix_dp.h | 11 +++++++++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 8cf6b73bceac..116de3bd83a3 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -870,7 +870,7 @@ static int analogix_dp_bridge_atomic_check(struct drm_bridge *bridge, struct drm_display_info *di = &conn_state->connector->display_info; u32 mask = BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); - if (is_rockchip(dp->plat_data->dev_type)) { + if (analogix_dp_is_rockchip(dp->plat_data->dev_type)) { if ((di->color_formats & mask)) { DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n"); di->color_formats &= ~mask; diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 38fd8d5014d2..6207ded7ffd5 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -72,7 +72,7 @@ void analogix_dp_init_analog_param(struct analogix_dp_device *dp) reg = SEL_24M | TX_DVDD_BIT_1_0625V; writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_2); - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { reg = REF_CLK_24M; if (dp->plat_data->dev_type == RK3288_DP) reg ^= REF_CLK_MASK; @@ -123,7 +123,7 @@ void analogix_dp_reset(struct analogix_dp_device *dp) analogix_dp_stop_video(dp); analogix_dp_enable_video_mute(dp, 0); - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) reg = RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N | SW_FUNC_EN_N; else @@ -233,7 +233,7 @@ void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable) u32 mask = DP_PLL_PD; u32 pd_addr = ANALOGIX_DP_PLL_CTL; - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { pd_addr = ANALOGIX_DP_PD; mask = RK_PLL_PD; } @@ -254,12 +254,12 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, u32 phy_pd_addr = ANALOGIX_DP_PHY_PD; u32 mask; - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) phy_pd_addr = ANALOGIX_DP_PD; switch (block) { case AUX_BLOCK: - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) mask = RK_AUX_PD; else mask = AUX_PD; @@ -317,7 +317,7 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, * to power off everything instead of DP_PHY_PD in * Rockchip */ - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) mask = DP_INC_BG; else mask = DP_PHY_PD; @@ -329,7 +329,7 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, reg &= ~mask; writel(reg, dp->reg_base + phy_pd_addr); - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) usleep_range(10, 15); break; case POWER_ALL: @@ -465,7 +465,7 @@ void analogix_dp_init_aux(struct analogix_dp_device *dp) analogix_dp_reset_aux(dp); /* AUX_BIT_PERIOD_EXPECTED_DELAY doesn't apply to Rockchip IP */ - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) reg = 0; else reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3); @@ -837,7 +837,7 @@ void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp) u32 reg; reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); - if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { reg &= ~(RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N); } else { reg &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N); diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 854af692229b..7b670dd769e9 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -19,9 +19,16 @@ enum analogix_dp_devtype { RK3588_EDP, }; -static inline bool is_rockchip(enum analogix_dp_devtype type) +static inline bool analogix_dp_is_rockchip(enum analogix_dp_devtype type) { - return type == RK3288_DP || type == RK3399_EDP || type == RK3588_EDP; + switch (type) { + case RK3288_DP: + case RK3399_EDP: + case RK3588_EDP: + return true; + default: + return false; + } } struct analogix_dp_plat_data { From 5442cdc38470ecd6b51562d378b6ed852a4d9d45 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:50:59 +0800 Subject: [PATCH 059/167] drm/bridge: analogix_dp: Add support for RK3576 Add RK3576_EDP device type entry and extend Rockchip check to match existing hardware capabilities shared with RK3588. Set identical maximum link rate and lane count parameters for RK3576 eDP controller to reuse existing RK3588 config. Reviewed-by: Luca Ceresoli Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-11-damon.ding@rock-chips.com --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 1 + include/drm/bridge/analogix_dp.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 116de3bd83a3..c8eb3511f92a 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1249,6 +1249,7 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) video_info->max_link_rate = 0x0A; video_info->max_lane_count = 0x04; break; + case RK3576_EDP: case RK3588_EDP: video_info->max_link_rate = 0x14; video_info->max_lane_count = 0x04; diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 7b670dd769e9..0e0b87abee59 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -16,6 +16,7 @@ enum analogix_dp_devtype { EXYNOS_DP, RK3288_DP, RK3399_EDP, + RK3576_EDP, RK3588_EDP, }; @@ -24,6 +25,7 @@ static inline bool analogix_dp_is_rockchip(enum analogix_dp_devtype type) switch (type) { case RK3288_DP: case RK3399_EDP: + case RK3576_EDP: case RK3588_EDP: return true; default: From 7e60c4e19fd3d3f1203fbb1fdd9b2bb1a1201e65 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 1 Jun 2026 14:51:00 +0800 Subject: [PATCH 060/167] drm/rockchip: analogix_dp: Add support for RK3576 RK3576 integrates Analogix eDP 1.3 TX and Samsung combo PHY hardware blocks that fully match the proven RK3588 design. Add dedicated chip data table and device tree matching entry to bring up basic eDP functionality for the RK3576 platform. Support is limited to RGB output up to 4K@60Hz for now; audio, PSR and other advanced eDP 1.3 features remain unvalidated. Signed-off-by: Damon Ding Reviewed-by: Nicolas Frattaroli Reviewed-by: Luca Ceresoli Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260601065100.1103873-12-damon.ding@rock-chips.com --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index b1ed25cefe5e..776954c7d052 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -526,6 +526,14 @@ static const struct rockchip_dp_chip_data rk3288_dp[] = { { /* sentinel */ } }; +static const struct rockchip_dp_chip_data rk3576_edp[] = { + { + .chip_type = RK3576_EDP, + .reg = 0x27dc0000, + }, + { /* sentinel */ } +}; + static const struct rockchip_dp_chip_data rk3588_edp[] = { { .edp_mode = GRF_REG_FIELD(0x0000, 0, 0), @@ -543,6 +551,7 @@ static const struct rockchip_dp_chip_data rk3588_edp[] = { static const struct of_device_id rockchip_dp_dt_ids[] = { {.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp }, {.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp }, + {.compatible = "rockchip,rk3576-edp", .data = &rk3576_edp }, {.compatible = "rockchip,rk3588-edp", .data = &rk3588_edp }, {} }; From 1965bd5479655362f316035e16a49f4598526b37 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:35 +0000 Subject: [PATCH 061/167] drm/rockchip: dw_hdmi: Use of_device_get_match_data() to get match data Change to use of_device_get_match_data() to get match data prior to allocating private data. All current entries in the of_device_id match table provide match data, so no functional change is intended. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-2-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 7abb42e486c0..011b0b937700 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -538,8 +538,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, { struct platform_device *pdev = to_platform_device(dev); struct device_node *np = dev_of_node(dev); + const struct dw_hdmi_plat_data *drv_data; struct dw_hdmi_plat_data *plat_data; - const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; @@ -548,13 +548,16 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, if (!np) return -ENODEV; + drv_data = of_device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; - match = of_match_node(dw_hdmi_rockchip_dt_ids, np); - plat_data = devm_kmemdup(&pdev->dev, match->data, - sizeof(*plat_data), GFP_KERNEL); + plat_data = devm_kmemdup(&pdev->dev, drv_data, + sizeof(*drv_data), GFP_KERNEL); if (!plat_data) return -ENOMEM; From 9cefcd0e83a38d9ede0bea988b4f7b2f01511e28 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:36 +0000 Subject: [PATCH 062/167] drm/rockchip: dw_hdmi: Use local dev variable consistently in bind() Replace indirect struct device accesses via hdmi->dev and pdev->dev with the local dev parameter already available in dw_hdmi_rockchip_bind(), for consistency and readability. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-3-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 011b0b937700..0d45fb2226dd 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -552,16 +552,15 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, if (!drv_data) return -ENODEV; - hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; - plat_data = devm_kmemdup(&pdev->dev, drv_data, - sizeof(*drv_data), GFP_KERNEL); + plat_data = devm_kmemdup(dev, drv_data, sizeof(*drv_data), GFP_KERNEL); if (!plat_data) return -ENOMEM; - hdmi->dev = &pdev->dev; + hdmi->dev = dev; hdmi->plat_data = plat_data; hdmi->chip_data = plat_data->phy_data; plat_data->phy_data = hdmi; @@ -583,13 +582,13 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, ret = rockchip_hdmi_parse_dt(hdmi); if (ret) { - return dev_err_probe(hdmi->dev, ret, "Unable to parse OF data\n"); + return dev_err_probe(dev, ret, "Unable to parse OF data\n"); } hdmi->phy = devm_phy_optional_get(dev, "hdmi"); if (IS_ERR(hdmi->phy)) { ret = PTR_ERR(hdmi->phy); - return dev_err_probe(hdmi->dev, ret, "failed to get phy\n"); + return dev_err_probe(dev, ret, "failed to get phy\n"); } index = of_property_match_string(np, "phy-names", "hdmi"); From 3de723684be5c13517dea3dba287d06e12678c86 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:37 +0000 Subject: [PATCH 063/167] drm/rockchip: dw_hdmi: Use drmres helpers for encoder resources Change to use drmres helpers drmm_kzalloc() to allocate driver data and drmm_encoder_init() to initialize the encoder. With use of drmres the manual encoder cleanup in failure path and unbind is also removed. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-4-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 0d45fb2226dd..2c68c47cffda 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -552,13 +553,14 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, if (!drv_data) return -ENODEV; - hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; - plat_data = devm_kmemdup(dev, drv_data, sizeof(*drv_data), GFP_KERNEL); + plat_data = drmm_kzalloc(drm, sizeof(*drv_data), GFP_KERNEL); if (!plat_data) return -ENOMEM; + memcpy(plat_data, drv_data, sizeof(*drv_data)); hdmi->dev = dev; hdmi->plat_data = plat_data; @@ -610,28 +612,20 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, FIELD_PREP_WM16(RK3568_HDMI_SCLIN_MSK, 1)); } + ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return dev_err_probe(dev, ret, "failed to init encoder\n"); + drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); - - /* - * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), - * which would have called the encoder cleanup. Do it manually. - */ - if (IS_ERR(hdmi->hdmi)) { - ret = PTR_ERR(hdmi->hdmi); - goto err_bind; - } + if (IS_ERR(hdmi->hdmi)) + return dev_err_probe(dev, PTR_ERR(hdmi->hdmi), + "failed to probe dw-hdmi bridge\n"); return 0; - -err_bind: - drm_encoder_cleanup(encoder); - - return ret; } static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, @@ -640,7 +634,6 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); dw_hdmi_unbind(hdmi->hdmi); - drm_encoder_cleanup(&hdmi->encoder.encoder); } static const struct component_ops dw_hdmi_rockchip_ops = { From c19830fba394f3566cf763864dec44cfd357325f Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:38 +0000 Subject: [PATCH 064/167] drm/rockchip: dw_hdmi: Inline resource lookup into bind() Inline rockchip_hdmi_parse_dt() into dw_hdmi_rockchip_bind() so the probe path is easier to follow in one place. Also ensure failures in bind() use dev_err_probe() so probe deferrals and errors are reported consistently. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-5-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 74 +++++++++------------ 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 2c68c47cffda..57d6df93b303 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -196,41 +196,6 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = { { ~0UL, 0x0000, 0x0000, 0x0000} }; -static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) -{ - struct device_node *np = hdmi->dev->of_node; - int ret; - - hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); - if (IS_ERR(hdmi->regmap)) { - dev_err(hdmi->dev, "Unable to get rockchip,grf\n"); - return PTR_ERR(hdmi->regmap); - } - - hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "ref"); - if (!hdmi->ref_clk) - hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "vpll"); - - if (IS_ERR(hdmi->ref_clk)) { - ret = PTR_ERR(hdmi->ref_clk); - return dev_err_probe(hdmi->dev, ret, "failed to get reference clock\n"); - } - - hdmi->grf_clk = devm_clk_get_optional(hdmi->dev, "grf"); - if (IS_ERR(hdmi->grf_clk)) { - ret = PTR_ERR(hdmi->grf_clk); - return dev_err_probe(hdmi->dev, ret, "failed to get grf clock\n"); - } - - ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9"); - if (ret) - return ret; - - ret = devm_regulator_get_enable(hdmi->dev, "avdd-1v8"); - - return ret; -} - static enum drm_mode_status dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data, const struct drm_display_info *info, @@ -580,18 +545,39 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, * the required CRTC is added later. */ if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; + return dev_err_probe(dev, -EPROBE_DEFER, + "failed to find possible crtcs\n"); - ret = rockchip_hdmi_parse_dt(hdmi); - if (ret) { - return dev_err_probe(dev, ret, "Unable to parse OF data\n"); - } + hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(hdmi->regmap)) + return dev_err_probe(dev, PTR_ERR(hdmi->regmap), + "failed to get rockchip,grf\n"); + + hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "ref"); + if (!hdmi->ref_clk) + hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "vpll"); + + if (IS_ERR(hdmi->ref_clk)) + return dev_err_probe(dev, PTR_ERR(hdmi->ref_clk), + "failed to get reference clock\n"); + + hdmi->grf_clk = devm_clk_get_optional(dev, "grf"); + if (IS_ERR(hdmi->grf_clk)) + return dev_err_probe(dev, PTR_ERR(hdmi->grf_clk), + "failed to get grf clock\n"); + + ret = devm_regulator_get_enable(dev, "avdd-0v9"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable avdd-0v9\n"); + + ret = devm_regulator_get_enable(dev, "avdd-1v8"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable avdd-1v8\n"); hdmi->phy = devm_phy_optional_get(dev, "hdmi"); - if (IS_ERR(hdmi->phy)) { - ret = PTR_ERR(hdmi->phy); - return dev_err_probe(dev, ret, "failed to get phy\n"); - } + if (IS_ERR(hdmi->phy)) + return dev_err_probe(dev, PTR_ERR(hdmi->phy), + "failed to get phy\n"); index = of_property_match_string(np, "phy-names", "hdmi"); if (index >= 0) { From f80fa5817952a24f45659b1377aa5da31941eab5 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:39 +0000 Subject: [PATCH 065/167] drm/rockchip: dw_hdmi: Hold a reference to the dw-hdmi bridge Take a reference on the dw-hdmi bridge during bind and drop it again from unbind to ensure the bridge is kept alive for the lifetime of the encoder component. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-6-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 57d6df93b303..f638dd8f6c20 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -79,6 +79,7 @@ struct rockchip_hdmi { struct clk *hdmiphy_clk; struct clk *ref_clk; struct clk *grf_clk; + struct drm_bridge *bridge; struct dw_hdmi *hdmi; struct phy *phy; }; @@ -611,6 +612,13 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(dev, PTR_ERR(hdmi->hdmi), "failed to probe dw-hdmi bridge\n"); + hdmi->bridge = of_drm_find_and_get_bridge(np); + if (!hdmi->bridge) { + dw_hdmi_unbind(hdmi->hdmi); + return dev_err_probe(dev, -ENODEV, + "failed to find dw-hdmi bridge\n"); + } + return 0; } @@ -619,6 +627,7 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + drm_bridge_put(hdmi->bridge); dw_hdmi_unbind(hdmi->hdmi); } From f98f2ef3ad28ea09cbb1d3706dc59e84668f3fdb Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:40 +0000 Subject: [PATCH 066/167] drm/rockchip: dw_hdmi: Remove empty encoder helper funcs Remove the empty disable() and static return true mode_fixup() encoder helper funcs as they do not provide any useful functionality. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-7-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index f638dd8f6c20..b53762f74251 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -226,18 +226,6 @@ dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data, return MODE_OK; } -static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder) -{ -} - -static bool -dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adj_mode) -{ - return true; -} - static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adj_mode) @@ -290,10 +278,8 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, } static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { - .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup, - .mode_set = dw_hdmi_rockchip_encoder_mode_set, - .enable = dw_hdmi_rockchip_encoder_enable, - .disable = dw_hdmi_rockchip_encoder_disable, + .mode_set = dw_hdmi_rockchip_encoder_mode_set, + .enable = dw_hdmi_rockchip_encoder_enable, .atomic_check = dw_hdmi_rockchip_encoder_atomic_check, }; From 2ca256b5fe2325d0f1134789b507a621775b385e Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:41 +0000 Subject: [PATCH 067/167] drm/rockchip: dw_hdmi: Clean up whitespace Move the blank line before the RK3328 definitions for readability and make the phy_config table spacing consistent with other tables. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-8-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index b53762f74251..3f3ff820b1b9 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -31,8 +31,8 @@ #define RK3288_GRF_SOC_CON6 0x025C #define RK3288_HDMI_LCDC_SEL BIT(4) -#define RK3328_GRF_SOC_CON2 0x0408 +#define RK3328_GRF_SOC_CON2 0x0408 #define RK3328_HDMI_SDAIN_MSK BIT(11) #define RK3328_HDMI_SCLIN_MSK BIT(10) #define RK3328_HDMI_HPD_IOE BIT(2) @@ -190,11 +190,11 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = { static const struct dw_hdmi_phy_config rockchip_phy_config[] = { /*pixelclk symbol term vlev*/ - { 74250000, 0x8009, 0x0004, 0x0272}, - { 165000000, 0x802b, 0x0004, 0x0209}, - { 297000000, 0x8039, 0x0005, 0x028d}, - { 594000000, 0x8039, 0x0000, 0x019d}, - { ~0UL, 0x0000, 0x0000, 0x0000} + { 74250000, 0x8009, 0x0004, 0x0272 }, + { 165000000, 0x802b, 0x0004, 0x0209 }, + { 297000000, 0x8039, 0x0005, 0x028d }, + { 594000000, 0x8039, 0x0000, 0x019d }, + { ~0UL, 0x0000, 0x0000, 0x0000 }, }; static enum drm_mode_status From 1481cc03cc63fc0f6990956264cbb16c8f65f28e Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:42 +0000 Subject: [PATCH 068/167] drm/rockchip: dw_hdmi: Set output_port for RK3568/RK3566 All in-tree RK3568/RK3566 device trees using HDMI also include the required hdmi-connector node at port@1 since their introduction. Define the output_port for RK3568 so that dw-hdmi bridge driver can pick up the display-connector bridge once the dw-hdmi connector is replaced with a bridge connector in a future change. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-9-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 3f3ff820b1b9..14adf6e8ff47 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -461,6 +461,7 @@ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = { .phy_config = rockchip_phy_config, .phy_data = &rk3568_chip_data, .use_drm_infoframe = true, + .output_port = 1, }; static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { From e54d6ed9a0a967ada1e7222d7485e0cbb0e1f563 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:43 +0000 Subject: [PATCH 069/167] drm/rockchip: dw_hdmi: Configure HDMI PHY in atomic_mode_set() The HDMI helpers negotiated TMDS character rate and output bpc are available from the connector state. Change the encoder helper from mode_set() to atomic_mode_set() so these values can be used to configure the HDMI PHY using phy_configure(). This has no impact until the dw-hdmi bridge is fully converted into a HDMI bridge and HDMI helpers are used to assign hdmi.tmds_char_rate. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-10-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 29 +++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 14adf6e8ff47..6bf86a3f3c51 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -226,11 +226,22 @@ dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data, return MODE_OK; } -static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adj_mode) +static void +dw_hdmi_rockchip_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; + + if (hdmi->phy && conn_state->hdmi.tmds_char_rate) { + union phy_configure_opts opts = {}; + + opts.hdmi.bpc = conn_state->hdmi.output_bpc; + opts.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + phy_configure(hdmi->phy, &opts); + } clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000); } @@ -270,15 +281,23 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_connector_state *conn_state) { struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + union phy_configure_opts opts = {}; s->output_mode = ROCKCHIP_OUT_MODE_AAAA; s->output_type = DRM_MODE_CONNECTOR_HDMIA; - return 0; + if (!hdmi->phy || !conn_state->hdmi.tmds_char_rate) + return 0; + + opts.hdmi.bpc = conn_state->hdmi.output_bpc; + opts.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + return phy_validate(hdmi->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS, &opts); } static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { - .mode_set = dw_hdmi_rockchip_encoder_mode_set, + .atomic_mode_set = dw_hdmi_rockchip_encoder_atomic_mode_set, .enable = dw_hdmi_rockchip_encoder_enable, .atomic_check = dw_hdmi_rockchip_encoder_atomic_check, }; From dc70272cc184b945cf526a23999fc8645e76bbed Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:44 +0000 Subject: [PATCH 070/167] drm/rockchip: dw_hdmi: Propagate bus format to display driver The HDMI block is currently hardcoded to expect RGB output from the display controller. However, the VOP in some SoCs are capable of YCbCr output to the HDMI block. Read the negotiated bus format from the bridge state and propagate it to the CRCT state in form of output mode and bus format. Treat the format MEDIA_BUS_FMT_FIXED as RGB888 and reject any unsupported formats. This has no inpact until dw-hdmi bridge is fully converted to a HDMI bridge and also adds support for the "color format" connector property. Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-11-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 44 ++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 6bf86a3f3c51..5ec079eafbf7 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -275,6 +276,26 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) dev_dbg(hdmi->dev, "vop %s output to hdmi\n", ret ? "LIT" : "BIG"); } +static u32 dw_hdmi_rockchip_get_bus_format(struct drm_encoder *encoder, + struct drm_connector_state *conn_state) +{ + struct drm_bridge *bridge __free(drm_bridge_put) = NULL; + struct drm_bridge_state *bridge_state; + + bridge = drm_bridge_chain_get_first_bridge(encoder); + if (!bridge) + return 0; + + bridge_state = drm_atomic_get_bridge_state(conn_state->state, bridge); + if (!bridge_state) + return 0; + + if (bridge_state->input_bus_cfg.format != MEDIA_BUS_FMT_FIXED) + return bridge_state->input_bus_cfg.format; + + return bridge_state->output_bus_cfg.format; +} + static int dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, @@ -283,9 +304,30 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); union phy_configure_opts opts = {}; + u32 bus_format; + + bus_format = dw_hdmi_rockchip_get_bus_format(encoder, conn_state); + + switch (bus_format) { + case MEDIA_BUS_FMT_FIXED: + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + fallthrough; + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + s->output_mode = ROCKCHIP_OUT_MODE_YUV420; + break; + default: + return -EINVAL; + } - s->output_mode = ROCKCHIP_OUT_MODE_AAAA; s->output_type = DRM_MODE_CONNECTOR_HDMIA; + s->bus_format = bus_format; if (!hdmi->phy || !conn_state->hdmi.tmds_char_rate) return 0; From af3a9eadc11896f6677bcb7102bfb7c84be48d2d Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Mon, 18 May 2026 19:37:45 +0000 Subject: [PATCH 071/167] drm/rockchip: dw_hdmi: Use resume_early pm ops for system suspend rockchip_drm_sys_resume()/drm_mode_config_helper_resume() is called before the resume pm ops of dw-hdmi. This result in an atomic_enable() before dw_hdmi_rockchip_resume()/dw_hdmi_resume() is called. Resume (without changes): - rockchip_drm_sys_resume() - drm_mode_config_helper_resume() - atomic_enable() - dw_hdmi_rockchip_resume() - dw_hdmi_resume() - dw_hdmi_init_hw() Change to use resume_early pm ops for system suspend to ensure pm ops for dw-hdmi is run before rockchip-drm pm ops. Also fix a possible NULL pointer dereference timing issue while at it. Resume (with changes): - dw_hdmi_rockchip_resume_early() - dw_hdmi_resume() - dw_hdmi_init_hw() - rockchip_drm_sys_resume() - drm_mode_config_helper_resume() - atomic_enable() Signed-off-by: Jonas Karlman Tested-by: Heiko Stuebner #rk3328 Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260518193748.2482823-12-jonas@kwiboo.se --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 5ec079eafbf7..cffc0af5190f 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -694,17 +694,18 @@ static void dw_hdmi_rockchip_remove(struct platform_device *pdev) component_del(&pdev->dev, &dw_hdmi_rockchip_ops); } -static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev) +static int __maybe_unused dw_hdmi_rockchip_resume_early(struct device *dev) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); - dw_hdmi_resume(hdmi->hdmi); + if (hdmi) + dw_hdmi_resume(hdmi->hdmi); return 0; } static const struct dev_pm_ops dw_hdmi_rockchip_pm = { - SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume) + SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume_early) }; struct platform_driver dw_hdmi_rockchip_pltfm_driver = { From f88b35d79a4b403676b1222b1c28b2daafae2980 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Thu, 21 May 2026 18:27:16 +0800 Subject: [PATCH 072/167] drm/bridge: analogix_dp: Convert status check functions to bool Convert analogix_dp_is_slave_video_stream_clock_on() and analogix_dp_is_video_stream_on() from int to bool return type. This makes the code more readable and aligns with kernel best practices for boolean status checks, while simplifying the callers by removing unnecessary "== 0" comparisons. Signed-off-by: Damon Ding Reviewed-by: Luca Ceresoli Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260521102716.1373519-1-damon.ding@rock-chips.com --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 4 ++-- drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++-- drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index c8eb3511f92a..5dc07ff84cd3 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -619,7 +619,7 @@ static int analogix_dp_config_video(struct analogix_dp_device *dp) for (;;) { timeout_loop++; - if (analogix_dp_is_slave_video_stream_clock_on(dp) == 0) + if (analogix_dp_is_slave_video_stream_clock_on(dp)) break; if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { dev_err(dp->dev, "Timeout of slave video streamclk ok\n"); @@ -647,7 +647,7 @@ static int analogix_dp_config_video(struct analogix_dp_device *dp) for (;;) { timeout_loop++; - if (analogix_dp_is_video_stream_on(dp) == 0) { + if (analogix_dp_is_video_stream_on(dp)) { done_count++; if (done_count > 10) break; diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index 17347448c6b0..94348c4e3623 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -211,7 +211,7 @@ void analogix_dp_reset_macro(struct analogix_dp_device *dp); void analogix_dp_init_video(struct analogix_dp_device *dp); void analogix_dp_set_video_color_format(struct analogix_dp_device *dp); -int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp); +bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp); void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, enum clock_recovery_m_value_type type, u32 m_value, @@ -220,7 +220,7 @@ void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type); void analogix_dp_enable_video_master(struct analogix_dp_device *dp, bool enable); void analogix_dp_start_video(struct analogix_dp_device *dp); -int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); +bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 6207ded7ffd5..ea8401293a23 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -713,7 +713,7 @@ void analogix_dp_set_video_color_format(struct analogix_dp_device *dp) writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); } -int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) +bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) { u32 reg; @@ -724,7 +724,7 @@ int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) if (!(reg & DET_STA)) { dev_dbg(dp->dev, "Input stream clock not detected.\n"); - return -EINVAL; + return false; } reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); @@ -735,10 +735,10 @@ int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) if (reg & CHA_STA) { dev_dbg(dp->dev, "Input stream clk is changing\n"); - return -EINVAL; + return false; } - return 0; + return true; } void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, @@ -816,7 +816,7 @@ void analogix_dp_start_video(struct analogix_dp_device *dp) writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); } -int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) +bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) { u32 reg; @@ -826,10 +826,10 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); if (!(reg & STRM_VALID)) { dev_dbg(dp->dev, "Input video stream is not detected.\n"); - return -EINVAL; + return false; } - return 0; + return true; } void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp) From a46991b334f663fa0e0e9c051e50587ef977ac8b Mon Sep 17 00:00:00 2001 From: Ryosuke Yasuoka Date: Mon, 1 Jun 2026 16:53:15 +0900 Subject: [PATCH 073/167] drm/virtio: abort virtqueue wait on device removal to avoid hung task virtio_gpu_queue_ctrl_sgs() and virtio_gpu_queue_cursor() use wait_event() without any abort condition when waiting for virtqueue space. If the host device stops processing commands, these waits block indefinitely inside a drm_dev_enter/exit() critical section. Since drm_dev_unplug(), which is called in device removal and system shutdown call path, blocks on synchronize_srcu() until all critical sections complete, device removal and system shutdown also hang. Add a vqs_released flag to virtio_gpu_device and include it in the wait_event() condition. Set the flag and wake up both queues in a new virtio_gpu_release_vqs() helper, called before drm_dev_unplug() in both virtio_gpu_remove() and virtio_gpu_shutdown(). When the flag is set, the wait returns immediately and the command is aborted, following the same cleanup path as drm_dev_enter() failure. Signed-off-by: Ryosuke Yasuoka Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260601-virtio-gpu_wait_event-v3-1-89530517a98a@redhat.com --- drivers/gpu/drm/virtio/virtgpu_drv.c | 15 +++++++++++++++ drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + drivers/gpu/drm/virtio/virtgpu_vq.c | 23 +++++++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c b/drivers/gpu/drm/virtio/virtgpu_drv.c index c04af986e63e..f0fb784c0f6f 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.c +++ b/drivers/gpu/drm/virtio/virtgpu_drv.c @@ -119,10 +119,24 @@ static int virtio_gpu_probe(struct virtio_device *vdev) return ret; } +/* + * Release pending virtqueue waits so the drm_dev_enter/exit() critical + * sections complete before drm_dev_unplug() blocks on synchronize_srcu(). + */ +static void virtio_gpu_release_vqs(struct drm_device *dev) +{ + struct virtio_gpu_device *vgdev = dev->dev_private; + + vgdev->vqs_released = true; + wake_up_all(&vgdev->ctrlq.ack_queue); + wake_up_all(&vgdev->cursorq.ack_queue); +} + static void virtio_gpu_remove(struct virtio_device *vdev) { struct drm_device *dev = vdev->priv; + virtio_gpu_release_vqs(dev); drm_dev_unplug(dev); drm_atomic_helper_shutdown(dev); virtio_gpu_deinit(dev); @@ -133,6 +147,7 @@ static void virtio_gpu_shutdown(struct virtio_device *vdev) { struct drm_device *dev = vdev->priv; + virtio_gpu_release_vqs(dev); /* stop talking to the device */ drm_dev_unplug(dev); } diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 055e5bedc91f..17a6a4d26516 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -240,6 +240,7 @@ struct virtio_gpu_device { struct virtio_gpu_queue ctrlq; struct virtio_gpu_queue cursorq; + bool vqs_released; struct kmem_cache *vbufs; atomic_t pending_commands; diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index d6069c3f9766..68d097ad9d1d 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -410,7 +410,19 @@ static int virtio_gpu_queue_ctrl_sgs(struct virtio_gpu_device *vgdev, if (vq->num_free < elemcnt) { spin_unlock(&vgdev->ctrlq.qlock); virtio_gpu_notify(vgdev); - wait_event(vgdev->ctrlq.ack_queue, vq->num_free >= elemcnt); + wait_event(vgdev->ctrlq.ack_queue, + vq->num_free >= elemcnt || vgdev->vqs_released); + /* + * Set by virtio_gpu_release_vqs() to unblock + * synchronize_srcu() wait in drm_dev_unplug(). + */ + if (vgdev->vqs_released) { + if (fence && vbuf->objs) + virtio_gpu_array_unlock_resv(vbuf->objs); + free_vbuf(vgdev, vbuf); + drm_dev_exit(idx); + return -ENODEV; + } goto again; } @@ -580,7 +592,14 @@ static void virtio_gpu_queue_cursor(struct virtio_gpu_device *vgdev, ret = virtqueue_add_sgs(vq, sgs, outcnt, 0, vbuf, GFP_ATOMIC); if (ret == -ENOSPC) { spin_unlock(&vgdev->cursorq.qlock); - wait_event(vgdev->cursorq.ack_queue, vq->num_free >= outcnt); + wait_event(vgdev->cursorq.ack_queue, + vq->num_free >= outcnt || vgdev->vqs_released); + /* See comment in virtio_gpu_queue_ctrl_sgs(). */ + if (vgdev->vqs_released) { + free_vbuf(vgdev, vbuf); + drm_dev_exit(idx); + return; + } spin_lock(&vgdev->cursorq.qlock); goto retry; } else { From 48b2829c46e8efa4e877bea945b6de972a0c6951 Mon Sep 17 00:00:00 2001 From: Marco Pagani Date: Wed, 20 May 2026 11:33:49 +0200 Subject: [PATCH 074/167] drm/sched: Add test suite for concurrent job submissions Add a new test suite to simulate concurrent job submissions to the DRM scheduler, as this functionality is not covered by current test suites. The new test suite includes two initial test cases: (i) a test case for parallel job submission and (ii) a test case for interleaved job submission and completion. In the first test case, worker threads concurrently submit jobs to the scheduler, and then the timeline is manually advanced to complete them in bulk. In the second test case, worker threads concurrently submit sequences of jobs of different durations to the mock scheduler using a sliding window to better model real-world workloads. The timeline is advanced automatically by the finishing jobs, interleaving submission with completion. Signed-off-by: Marco Pagani Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260520093350.1036001-1-marco.pagani@linux.dev --- drivers/gpu/drm/scheduler/tests/tests_basic.c | 359 ++++++++++++++++++ 1 file changed, 359 insertions(+) diff --git a/drivers/gpu/drm/scheduler/tests/tests_basic.c b/drivers/gpu/drm/scheduler/tests/tests_basic.c index a5a5a35a87b0..da5c4952780f 100644 --- a/drivers/gpu/drm/scheduler/tests/tests_basic.c +++ b/drivers/gpu/drm/scheduler/tests/tests_basic.c @@ -1,7 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Valve Corporation */ +#include #include +#include +#include +#include #include "sched_tests.h" @@ -235,6 +239,360 @@ static void drm_sched_basic_cancel(struct kunit *test) KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED); } +struct sched_concurrent_context { + struct drm_mock_scheduler *sched; + struct workqueue_struct *sub_wq; + struct kunit *test; + struct completion wait_go; +}; + +KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_fini_wrap, drm_mock_sched_fini, + struct drm_mock_scheduler *); + +KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_entity_free_wrap, drm_mock_sched_entity_free, + struct drm_mock_sched_entity *); + +static void complete_destroy_workqueue(void *context) +{ + struct sched_concurrent_context *ctx = context; + + complete_all(&ctx->wait_go); + + destroy_workqueue(ctx->sub_wq); +} + +static int drm_sched_concurrent_init(struct kunit *test) +{ + struct sched_concurrent_context *ctx; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + init_completion(&ctx->wait_go); + + ctx->sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); + + ret = kunit_add_action_or_reset(test, drm_mock_sched_fini_wrap, ctx->sched); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Use an unbounded workqueue to maximize job submission concurrency */ + ctx->sub_wq = alloc_workqueue("drm-sched-submitters-wq", WQ_UNBOUND, + WQ_UNBOUND_MAX_ACTIVE); + KUNIT_ASSERT_NOT_NULL(test, ctx->sub_wq); + + ret = kunit_add_action_or_reset(test, complete_destroy_workqueue, ctx); + KUNIT_ASSERT_EQ(test, ret, 0); + + ctx->test = test; + test->priv = ctx; + + return 0; +} + +struct drm_sched_parallel_params { + const char *description; + unsigned int num_jobs; + unsigned int num_workers; +}; + +static const struct drm_sched_parallel_params drm_sched_parallel_cases[] = { + { + .description = "Parallel submission of multiple jobs per worker", + .num_jobs = 8, + .num_workers = 16, + }, +}; + +static void +drm_sched_parallel_desc(const struct drm_sched_parallel_params *params, char *desc) +{ + strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(drm_sched_parallel, drm_sched_parallel_cases, drm_sched_parallel_desc); + +struct parallel_worker { + struct work_struct work; + struct sched_concurrent_context *ctx; + struct drm_mock_sched_entity *entity; + struct drm_mock_sched_job **jobs; + unsigned int id; +}; + +static void drm_sched_parallel_worker(struct work_struct *work) +{ + const struct drm_sched_parallel_params *params; + struct sched_concurrent_context *test_ctx; + struct parallel_worker *worker; + unsigned int i; + + worker = container_of(work, struct parallel_worker, work); + test_ctx = worker->ctx; + params = test_ctx->test->param_value; + + wait_for_completion(&test_ctx->wait_go); + + kunit_info(test_ctx->test, "Parallel worker %u submitting %u jobs started\n", + worker->id, params->num_jobs); + + for (i = 0; i < params->num_jobs; i++) + drm_mock_sched_job_submit(worker->jobs[i]); +} + +/* + * Spawns workers that submit a sequence of jobs to the mock scheduler. + * Once all jobs are submitted, the timeline is manually advanced. + */ +static void drm_sched_parallel_submit_test(struct kunit *test) +{ + struct sched_concurrent_context *ctx = test->priv; + const struct drm_sched_parallel_params *params = test->param_value; + struct parallel_worker *workers, *worker; + struct drm_mock_sched_job *job; + unsigned int i, j, completed_jobs, total_jobs; + bool done; + int ret; + + KUNIT_ASSERT_GT(test, params->num_workers, 0); + KUNIT_ASSERT_GT(test, params->num_jobs, 0); + + workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, workers); + + /* + * Init workers only after all jobs and entities have been successfully + * allocated. In this way, the cleanup logic for when an assertion fail + * can be simplified. + */ + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + worker->id = i; + worker->ctx = ctx; + worker->entity = drm_mock_sched_entity_new(test, + DRM_SCHED_PRIORITY_NORMAL, + ctx->sched); + + worker->jobs = kunit_kcalloc(test, params->num_jobs, + sizeof(*worker->jobs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, worker->jobs); + + for (j = 0; j < params->num_jobs; j++) { + job = drm_mock_sched_job_new(test, worker->entity); + worker->jobs[j] = job; + } + + ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap, + worker->entity); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + INIT_WORK(&worker->work, drm_sched_parallel_worker); + queue_work(ctx->sub_wq, &worker->work); + } + + complete_all(&ctx->wait_go); + flush_workqueue(ctx->sub_wq); + + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + for (j = 0; j < params->num_jobs; j++) { + job = worker->jobs[j]; + done = drm_mock_sched_job_wait_scheduled(job, HZ); + KUNIT_EXPECT_TRUE(test, done); + } + } + + total_jobs = params->num_workers * params->num_jobs; + completed_jobs = drm_mock_sched_advance(ctx->sched, total_jobs); + KUNIT_EXPECT_EQ(test, completed_jobs, total_jobs); + + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + for (j = 0; j < params->num_jobs; j++) { + job = worker->jobs[j]; + done = drm_mock_sched_job_wait_finished(job, HZ); + KUNIT_EXPECT_TRUE(test, done); + } + } +} + +struct drm_sched_interleaved_params { + const char *description; + unsigned int test_duration_ms; + unsigned int job_base_duration_us; + unsigned int num_workers; + unsigned int num_in_flight_jobs; +}; + +static const struct drm_sched_interleaved_params drm_sched_interleaved_cases[] = { + { + .description = "Interleaved submission of multiple jobs per worker", + .test_duration_ms = 1000, + .job_base_duration_us = 100, + .num_workers = 16, + .num_in_flight_jobs = 8, + }, +}; + +static void +drm_sched_interleaved_desc(const struct drm_sched_interleaved_params *params, char *desc) +{ + strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(drm_sched_interleaved, drm_sched_interleaved_cases, + drm_sched_interleaved_desc); + +struct interleaved_worker { + struct work_struct work; + struct sched_concurrent_context *ctx; + struct drm_mock_sched_entity *entity; + struct drm_mock_sched_job **jobs; + unsigned int id; + unsigned int job_count; + unsigned int job_duration_us; +}; + +static void drm_sched_interleaved_worker(struct work_struct *work) +{ + struct sched_concurrent_context *test_ctx; + const struct drm_sched_interleaved_params *params; + struct interleaved_worker *worker; + unsigned int i, j, max_in_flight_job; + unsigned long timeout; + bool done; + + worker = container_of(work, struct interleaved_worker, work); + test_ctx = worker->ctx; + params = test_ctx->test->param_value; + + wait_for_completion(&test_ctx->wait_go); + + kunit_info(test_ctx->test, "Worker %u submitting %u jobs of %u us started\n", + worker->id, worker->job_count, worker->job_duration_us); + + timeout = msecs_to_jiffies(params->test_duration_ms * 2); + + /* Fill the submission window */ + max_in_flight_job = min(worker->job_count, params->num_in_flight_jobs); + for (i = 0; i < max_in_flight_job; i++) + drm_mock_sched_job_submit(worker->jobs[i]); + + /* Keep the window full by submitting a new job at once until done */ + for (i = 0; i < worker->job_count; i++) { + done = drm_mock_sched_job_wait_finished(worker->jobs[i], timeout); + if (!done) + kunit_info(test_ctx->test, "Job %u of worker %u timed out\n", + i, worker->id); + + j = i + max_in_flight_job; + if (j < worker->job_count) + drm_mock_sched_job_submit(worker->jobs[j]); + } +} + +/* + * Spawns workers that submit a sequence of jobs to the mock scheduler. Job + * durations are chosen as multiples of a base duration value specified as + * a test parameter. Since the scheduler serializes jobs from all workers, + * the total test duration budget is divided into equal shares among workers. + * These shares are then used to compute the number of jobs that each worker + * can submit. + */ +static void drm_sched_interleaved_submit_test(struct kunit *test) +{ + const struct drm_sched_interleaved_params *params = test->param_value; + struct sched_concurrent_context *ctx = test->priv; + struct interleaved_worker *workers, *worker; + struct drm_mock_sched_job *job; + unsigned int worker_share_us; + unsigned int i, j; + bool done; + int ret; + + KUNIT_ASSERT_GT(test, params->num_workers, 0); + KUNIT_ASSERT_GT(test, params->job_base_duration_us, 0); + + workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, workers); + + /* Divide the available test time into equal shares among the workers */ + worker_share_us = (params->test_duration_ms * USEC_PER_MSEC) / + params->num_workers; + + /* + * Init workers only after all jobs and entities have been successfully + * allocated. In this way, the cleanup logic for when an assertion fails + * can be simplified. + */ + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + worker->id = i; + worker->ctx = ctx; + + worker->job_duration_us = params->job_base_duration_us * (i + 1); + worker->job_count = worker_share_us / worker->job_duration_us; + worker->job_count = max(1U, worker->job_count); + + worker->entity = drm_mock_sched_entity_new(test, + DRM_SCHED_PRIORITY_NORMAL, + ctx->sched); + + worker->jobs = kunit_kcalloc(test, worker->job_count, + sizeof(*worker->jobs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, worker->jobs); + + for (j = 0; j < worker->job_count; j++) { + job = drm_mock_sched_job_new(test, worker->entity); + drm_mock_sched_job_set_duration_us(job, worker->job_duration_us); + + worker->jobs[j] = job; + } + + ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap, + worker->entity); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + INIT_WORK(&worker->work, drm_sched_interleaved_worker); + queue_work(ctx->sub_wq, &worker->work); + } + + complete_all(&ctx->wait_go); + flush_workqueue(ctx->sub_wq); + + for (i = 0; i < params->num_workers; i++) { + worker = &workers[i]; + for (j = 0; j < worker->job_count; j++) { + job = worker->jobs[j]; + done = drm_mock_sched_job_is_finished(job); + KUNIT_EXPECT_TRUE(test, done); + } + } +} + +static struct kunit_case drm_sched_concurrent_tests[] = { + KUNIT_CASE_PARAM(drm_sched_parallel_submit_test, drm_sched_parallel_gen_params), + KUNIT_CASE_PARAM(drm_sched_interleaved_submit_test, drm_sched_interleaved_gen_params), + {} +}; + +static struct kunit_suite drm_sched_concurrent = { + .name = "drm_sched_concurrent_tests", + .init = drm_sched_concurrent_init, + .test_cases = drm_sched_concurrent_tests, + .attr = { + .speed = KUNIT_SPEED_SLOW, + }, +}; + static struct kunit_case drm_sched_cancel_tests[] = { KUNIT_CASE(drm_sched_basic_cancel), {} @@ -556,6 +914,7 @@ static struct kunit_suite drm_sched_credits = { }; kunit_test_suites(&drm_sched_basic, + &drm_sched_concurrent, &drm_sched_timeout, &drm_sched_cancel, &drm_sched_priority, From d59676b3ebadc44d6d87b0c6133f169dad9192a2 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 17 May 2026 19:23:53 +0200 Subject: [PATCH 075/167] dma-buf: heaps: use max3 in dma_heap_ioctl Replace two nested max() calls with a single max3() in dma_heap_ioctl(). Reviewed-by: T.J. Mercier Signed-off-by: Thorsten Blum Signed-off-by: Sumit Semwal Link: https://patch.msgid.link/20260517172352.3615-2-thorsten.blum@linux.dev --- drivers/dma-buf/dma-heap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c index a76bf3f8b071..3937dd41bb0f 100644 --- a/drivers/dma-buf/dma-heap.c +++ b/drivers/dma-buf/dma-heap.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -154,7 +155,7 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd, in_size = 0; if ((ucmd & kcmd & IOC_OUT) == 0) out_size = 0; - ksize = max(max(in_size, out_size), drv_size); + ksize = max3(in_size, out_size, drv_size); /* If necessary, allocate buffer for ioctl argument */ if (ksize > sizeof(stack_kdata)) { From 96889ef7dcf70bd7f8d78a6e6f255d9436d59993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Grzelak?= Date: Fri, 22 May 2026 15:55:19 +0200 Subject: [PATCH 076/167] drm/print: describe 6th & 9th bit of drm.debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting 6th or 9th bit of drm.debug change debug logging. Meanwhile `modinfo drm` does not inform about it at all. Add info to MODULE_PARAM_DESC(debug, ...) about setting 6th and 9th bit basing on DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, ...). Match description of corresponding bits with enum drm_debug_category. Include 9th bit in the example with enabling all possible logging provided at comment at include/drm/drm_print.h. Signed-off-by: Michał Grzelak Reviewed-by: Chaitanya Kumar Borah Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260522135520.1862848-2-michal.grzelak@intel.com --- drivers/gpu/drm/drm_print.c | 4 +++- include/drm/drm_print.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_print.c b/drivers/gpu/drm/drm_print.c index ded9461df5f2..86cef1a37678 100644 --- a/drivers/gpu/drm/drm_print.c +++ b/drivers/gpu/drm/drm_print.c @@ -50,8 +50,10 @@ MODULE_PARM_DESC(debug, "Enable debug output, where each bit enables a debug cat "\t\tBit 3 (0x08) will enable PRIME messages (prime code)\n" "\t\tBit 4 (0x10) will enable ATOMIC messages (atomic code)\n" "\t\tBit 5 (0x20) will enable VBL messages (vblank code)\n" +"\t\tBit 6 (0x40) will enable STATE messages (atomic state code)\n" "\t\tBit 7 (0x80) will enable LEASE messages (leasing code)\n" -"\t\tBit 8 (0x100) will enable DP messages (displayport code)"); +"\t\tBit 8 (0x100) will enable DP messages (displayport code)\n" +"\t\tBit 9 (0x200) will enable DRMRES messages (managed resources code)"); #if !defined(CONFIG_DRM_USE_DYNAMIC_DEBUG) module_param_named(debug, __drm_debug, ulong, 0600); diff --git a/include/drm/drm_print.h b/include/drm/drm_print.h index ab017b05e175..2adc5ac688e1 100644 --- a/include/drm/drm_print.h +++ b/include/drm/drm_print.h @@ -87,7 +87,7 @@ extern unsigned long __drm_debug; * - drm.debug=0x2 will enable DRIVER messages * - drm.debug=0x3 will enable CORE and DRIVER messages * - ... - * - drm.debug=0x1ff will enable all messages + * - drm.debug=0x3ff will enable all messages * * An interesting feature is that it's possible to enable verbose logging at * run-time by echoing the debug value in its sysfs node:: From b0d4740dae77c9e7b97344d6d81c8727a6ea8795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Grzelak?= Date: Fri, 22 May 2026 15:55:20 +0200 Subject: [PATCH 077/167] drm/managed: fix drmm_add_action() kernel-doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kernel-doc of drmm_add_action() references @releases which is not on argument list. Swap '@' between 'releases' and 'action' words to fix the documentation. Signed-off-by: Michał Grzelak Reviewed-by: Chaitanya Kumar Borah Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260522135520.1862848-3-michal.grzelak@intel.com --- include/drm/drm_managed.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/drm/drm_managed.h b/include/drm/drm_managed.h index 72bfac002c06..72d0d68be226 100644 --- a/include/drm/drm_managed.h +++ b/include/drm/drm_managed.h @@ -18,7 +18,7 @@ typedef void (*drmres_release_t)(struct drm_device *dev, void *res); * @action: function which should be called when @dev is released * @data: opaque pointer, passed to @action * - * This function adds the @release action with optional parameter @data to the + * This function adds the release @action with optional parameter @data to the * list of cleanup actions for @dev. The cleanup actions will be run in reverse * order in the final drm_dev_put() call for @dev. */ From 6e7eb171ac96fefa32197afa658e7d9da238be89 Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Fri, 29 May 2026 23:52:07 +0200 Subject: [PATCH 078/167] Documentation: drm: Add entry for removing spsc_queue to TODO list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_sched contains a lockless queue (spsc_queue) that seems to be useless and potentially unsound. Add a TODO list entry for replacing spsc_queue with a locked list. Reviewed-by: Christian König Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260529215207.115513-2-phasta@kernel.org --- Documentation/gpu/todo.rst | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index f5a6139356da..e3d169d42f19 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -948,6 +948,47 @@ Contact: Philipp Stanner Level: Intermediate +Replace the lockless queue with a locked list +--------------------------------------------- + +drm_sched is the only user in the entire kernel of a special lockless queue, the +spsc_queue. This queue utilizes: + +- preempt_disable() +- atomic instructions +- memory barriers +- ACCESS_ONCE() + +whereas a conventional spinlock utilizes: + +- preempt_disable() +- 1 atomic instruction for taking / releasing the lock +- memory barriers + +Moreover, drm_sched_entity_push_job(), the only user of spsc_queue_push(), has +to take a lock in some situations anyways and calls to it are often serialized +with a driver lock. + +It is, thus, highly questionable whether the lockless queue grants any advantage +at all. Considering that its internals are not well documented and its correctness +is not formally proven, it seems desirable to replace the queue with a mere list +or hlist that is protected by a spinlock. + +Tasks: + +- Replace the spsc_queue in drm/sched (and those who might access the scheduler's + internal queue) with a spinlock + (h)list. +- Ideally, check with some micro benchmarks and real world tests (preferably + with amdgpu) for relevant performance regressions. +- Remove the spsc_queue from the kernel altogether. + +Contact: + +- Philipp Stanner +- Christian König + +Level: Beginner + Outside DRM =========== From 98a6cabfd805d55a1b2c70b01923ec9b9bbc706a Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 May 2026 16:01:23 +0200 Subject: [PATCH 079/167] lib/fonts: Look up glyph data with font_data_glyph_buf() Add font_data_glyph_buf() to retrieve a character's glyph data or NULL otherwise. Console fonts can currently contain 256 or 512 glyphs. The kernel-internal characters are of type char, unsigned short or unsigned int. Catch all of them by accepting unsigned int. Callers possibly have to cast from signed to unsigned types to reach all glyphs in a font. Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260529140759.529929-2-tzimmermann@suse.de --- include/linux/font.h | 3 +++ lib/fonts/fonts.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/include/linux/font.h b/include/linux/font.h index 6845f02d739a..ea23b727388b 100644 --- a/include/linux/font.h +++ b/include/linux/font.h @@ -101,6 +101,9 @@ font_data_t *font_data_import(const struct console_font *font, unsigned int vpit void font_data_get(font_data_t *fd); bool font_data_put(font_data_t *fd); unsigned int font_data_size(font_data_t *fd); +const unsigned char *font_data_glyph_buf(font_data_t *fd, + unsigned int width, unsigned int vpitch, + unsigned int c); bool font_data_is_equal(font_data_t *lhs, font_data_t *rhs); int font_data_export(font_data_t *fd, struct console_font *font, unsigned int vpitch); diff --git a/lib/fonts/fonts.c b/lib/fonts/fonts.c index f5d5333450a0..4fc66722d00d 100644 --- a/lib/fonts/fonts.c +++ b/lib/fonts/fonts.c @@ -178,6 +178,37 @@ unsigned int font_data_size(font_data_t *fd) } EXPORT_SYMBOL_GPL(font_data_size); +static unsigned int font_data_num_glyphs(font_data_t *fd, unsigned int width, unsigned int height) +{ + return font_data_size(fd) / font_glyph_size(width, height); +} + +/** + * font_data_glyph_buf() - Returns the glyph for a specific character as raw bytes + * @fd: The font data + * @width: The glyph width in bits per scanline + * @vpitch: The number of scanlines per glyph + * @c: The character + * + * Glyphs start at fixed intervals within the font data. font_data_glyph_buf() + * returns the glyph shape of the specified character. If no such glyph + * exists in the font, it returns NULL. + * + * Returns: + * The character's raw glyph shape, or NULL if no glyph exists for the character. The + * provided buffer is read-only. + */ +const unsigned char *font_data_glyph_buf(font_data_t *fd, + unsigned int width, unsigned int vpitch, + unsigned int c) +{ + if (c >= font_data_num_glyphs(fd, width, vpitch)) + return NULL; + + return font_data_buf(fd) + font_glyph_size(width, vpitch) * c; +} +EXPORT_SYMBOL_GPL(font_data_glyph_buf); + /** * font_data_is_equal - Compares font data for equality * @lhs: Left-hand side font data From 26cda6f06513fdf604ed168a88a7a9317d71427a Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 May 2026 16:01:24 +0200 Subject: [PATCH 080/167] drm/client: log: Look up glyph shape with font helper Look up glyph shapes with font_data_glyph_buf(). Handle non-existing glyphs gracefully. Enable extended ASCII by casting to unsigned char. Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260529140759.529929-3-tzimmermann@suse.de --- drivers/gpu/drm/clients/drm_log.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/clients/drm_log.c b/drivers/gpu/drm/clients/drm_log.c index 8d21b785bead..e3e02c84a4cf 100644 --- a/drivers/gpu/drm/clients/drm_log.c +++ b/drivers/gpu/drm/clients/drm_log.c @@ -122,10 +122,12 @@ static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s, iosys_map_incr(&map, r.y1 * fb->pitches[0]); for (i = 0; i < len && i < scanout->columns; i++) { u32 color = (i < prefix_len) ? scanout->prefix_color : scanout->front_color; - src = drm_draw_get_char_bitmap(font, s[i], font_pitch); - drm_log_blit(&map, fb->pitches[0], src, font_pitch, - scanout->scaled_font_h, scanout->scaled_font_w, - px_width, color); + src = font_data_glyph_buf(font->data, font->width, font->height, + (unsigned char)s[i]); + if (src) + drm_log_blit(&map, fb->pitches[0], src, font_pitch, + scanout->scaled_font_h, scanout->scaled_font_w, + px_width, color); iosys_map_incr(&map, scanout->scaled_font_w * px_width); } From 5b508d25d3085863e3a66d01982effe29a8d12c9 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 May 2026 16:01:25 +0200 Subject: [PATCH 081/167] drm/panic: Look up glyph shape with font helper Look up glyph shapes with font_data_glyph_buf(). Handle non-existing glyphs gracefully. Enable extended ASCII by casting to unsigned char. Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260529140759.529929-4-tzimmermann@suse.de --- drivers/gpu/drm/drm_panic.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c index d6d3b8d85dea..e576c4791861 100644 --- a/drivers/gpu/drm/drm_panic.c +++ b/drivers/gpu/drm/drm_panic.c @@ -443,9 +443,11 @@ static void draw_txt_rectangle(struct drm_scanout_buffer *sb, rec.x1 += (drm_rect_width(clip) - (line_len * font->width)) / 2; for (j = 0; j < line_len; j++) { - src = drm_draw_get_char_bitmap(font, msg[i].txt[j], font_pitch); + src = font_data_glyph_buf(font->data, font->width, font->height, + (unsigned char)msg[i].txt[j]); rec.x2 = rec.x1 + font->width; - drm_panic_blit(sb, &rec, src, font_pitch, 1, color); + if (src) + drm_panic_blit(sb, &rec, src, font_pitch, 1, color); rec.x1 += font->width; } } From 86925ba9da20857b7d4525256a405fe789d4aa33 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 May 2026 16:01:26 +0200 Subject: [PATCH 082/167] drm/draw: Remove unused helper drm_draw_get_char_bitmap() Glyph-shape lookup has been integrated into the font-data interface and all callers have been updated. Remove the old helper. Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260529140759.529929-5-tzimmermann@suse.de --- drivers/gpu/drm/drm_draw_internal.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/gpu/drm/drm_draw_internal.h b/drivers/gpu/drm/drm_draw_internal.h index 261967145635..44ddcee4744c 100644 --- a/drivers/gpu/drm/drm_draw_internal.h +++ b/drivers/gpu/drm/drm_draw_internal.h @@ -7,7 +7,6 @@ #ifndef __DRM_DRAW_INTERNAL_H__ #define __DRM_DRAW_INTERNAL_H__ -#include #include struct iosys_map; @@ -18,12 +17,6 @@ static inline bool drm_draw_is_pixel_fg(const u8 *sbuf8, unsigned int spitch, in return (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) != 0; } -static inline const u8 *drm_draw_get_char_bitmap(const struct font_desc *font, - char c, size_t font_pitch) -{ - return font->data + (c * font->height) * font_pitch; -} - bool drm_draw_can_convert_from_xrgb8888(u32 format); u32 drm_draw_color_from_xrgb8888(u32 color, u32 format); From 6f7565f2fc85e9dc0d9d7594bdfc2b03c4e11205 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 4 Jun 2026 10:06:54 +0200 Subject: [PATCH 083/167] dt-bindings: display: panel: samsung,atna33xc20: Add ATNA40HQ08-0 Some variants of the Qualcomm Snapdragon X2 Elite-based Lenovo Yoga Slim 7x Gen11 laptop ship with a Samsung ATNA40HQ08-0 2880x1800 120 Hz OLED panel which seems to be compatible with the other ATNAxxx panels, document it. Note that I stripped the -0 suffix from the compatible, as it seems to be of little importance and some sources omit it. EDID: 00 ff ff ff ff ff ff 00 4c 83 2f 42 2f 00 00 00 00 23 01 04 b5 1e 13 78 03 66 85 ae 51 38 b9 24 0a 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fd 00 1e 78 e6 e6 47 01 0a 20 20 20 20 20 20 00 00 00 fe 00 53 44 43 20 20 20 20 20 20 20 20 20 20 00 00 00 fc 00 41 54 4e 41 34 30 48 51 30 38 2d 30 20 02 c4 70 20 79 02 00 20 00 18 00 12 fb 2f 42 00 00 00 00 00 23 0c 41 54 4e 41 34 30 48 51 30 38 2d 30 21 00 1d ca 0b 5e 07 40 0b 08 07 00 e5 7a 51 85 a3 b9 4a 02 0a 02 45 54 d0 5f 4c 64 00 44 23 78 26 00 09 06 00 00 00 00 00 50 00 00 22 00 14 00 d4 0a 85 3f 0b c7 00 07 00 1f 00 07 07 77 00 4f 00 0f 00 2e 00 06 00 45 d0 5f 4c 64 2b 00 0c 27 00 1e 77 00 00 27 00 1e 3b 00 00 00 00 00 85 90 70 20 79 00 00 22 00 14 00 d4 0a 05 3f 0b c7 00 07 00 1f 00 07 07 f7 07 cf 07 0f 00 81 00 1f 73 1a 00 00 03 5b 1e 78 00 a0 8f 02 6a 02 78 00 00 00 00 8d e3 05 80 00 e6 06 05 01 8f 6a 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 90 Signed-off-by: Konrad Dybcio Acked-by: Krzysztof Kozlowski [dianders: Modified the EDID to be bytes, not 16-bit words] Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260604-topic-yoga_submission-v1-1-57c70c23d0d6@oss.qualcomm.com --- .../devicetree/bindings/display/panel/samsung,atna33xc20.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/samsung,atna33xc20.yaml b/Documentation/devicetree/bindings/display/panel/samsung,atna33xc20.yaml index 1bbe0da3997c..27536eeaed85 100644 --- a/Documentation/devicetree/bindings/display/panel/samsung,atna33xc20.yaml +++ b/Documentation/devicetree/bindings/display/panel/samsung,atna33xc20.yaml @@ -25,6 +25,8 @@ properties: - samsung,atna40ct06 # Samsung 14" WQXGA+ (2880x1800 pixels) eDP AMOLED panel - samsung,atna40cu11 + # Samsung 14" WQXGA+ (2880x1800 pixels) eDP AMOLED panel + - samsung,atna40hq08 # Samsung 14" WQXGA+ (2880×1800 pixels) eDP AMOLED panel - samsung,atna40yk20 # Samsung 14.5" WQXGA+ (2880x1800 pixels) eDP AMOLED panel From a89d9a327d068ef29f03c74ca5d1a6cbb16aca99 Mon Sep 17 00:00:00 2001 From: Chintan Patel Date: Fri, 22 May 2026 20:57:34 -0700 Subject: [PATCH 084/167] drm/panel: novatek-nt36672a: Inline panel init sequences Inline the panel initialization command sequences and remove the table-based command abstraction used by the NT36672A panel driver. Replace the nt36672a_panel_cmd tables and nt36672a_send_cmds() helper with explicit initialization functions using mipi_dsi_dcs_write_seq_multi() and mipi_dsi_dcs_write_var_seq_multi() directly. This improves readability by making the panel programming sequence explicit in code and allows future sharing of common command subsequences between panels. It also removes an unnecessary wrapper around the MIPI DSI helpers. Additionally, compress repeated register writes into small loops where appropriate to reduce duplication in the initialization sequences. Add: tianma_fhd_video_send_init_cmds_1() tianma_fhd_video_send_init_cmds_2() tianma_fhd_video_send_deinit_cmds() Update nt36672a_panel_desc to use function pointers for panel init sequences and invoke them directly from prepare/unprepare paths. Signed-off-by: Chintan Patel Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260523035734.6602-1-chintanlike@gmail.com --- .../gpu/drm/panel/panel-novatek-nt36672a.c | 522 +++++++----------- 1 file changed, 186 insertions(+), 336 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36672a.c b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c index 7e8b5e059575..464d9ce47d87 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt36672a.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c @@ -28,10 +28,6 @@ #include