* [PATCH 1/5] drm/panel: have drm_panel_add/remove manage a list reference
2026-06-26 12:03 [PATCH 0/5] drm/panel: refcounting panel lookups and references Albert Esteve
@ 2026-06-26 12:03 ` Albert Esteve
2026-06-26 12:47 ` Maxime Ripard
2026-06-26 12:03 ` [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel Albert Esteve
` (3 subsequent siblings)
4 siblings, 1 reply; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 12:03 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter
Cc: dri-devel, linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra, Albert Esteve
The global panel_list holds raw pointers to drm_panel objects.
Nothing prevents a panel from being freed while it is still linked
in the list: if a driver's probe calls drm_panel_add() and then
fails at a later step, panel->list remains in panel_list. Any
subsequent call to of_drm_find_panel() that iterates the list will
dereference freed memory.
Have drm_panel_add() acquire a reference via drm_panel_get() before
inserting the panel into the list, and have drm_panel_remove() drop
it via drm_panel_put() after removing the panel from the list. The
global registry now holds a counted reference for as long as the
panel is listed, ensuring the object outlives any concurrent lookup.
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/drm_panel.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 2c5649e433dfb..545fe93dc28fe 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -81,6 +81,7 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev,
*/
void drm_panel_add(struct drm_panel *panel)
{
+ drm_panel_get(panel);
mutex_lock(&panel_lock);
list_add_tail(&panel->list, &panel_list);
mutex_unlock(&panel_lock);
@@ -98,6 +99,7 @@ void drm_panel_remove(struct drm_panel *panel)
mutex_lock(&panel_lock);
list_del_init(&panel->list);
mutex_unlock(&panel_lock);
+ drm_panel_put(panel);
}
EXPORT_SYMBOL(drm_panel_remove);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 1/5] drm/panel: have drm_panel_add/remove manage a list reference
2026-06-26 12:03 ` [PATCH 1/5] drm/panel: have drm_panel_add/remove manage a list reference Albert Esteve
@ 2026-06-26 12:47 ` Maxime Ripard
0 siblings, 0 replies; 17+ messages in thread
From: Maxime Ripard @ 2026-06-26 12:47 UTC (permalink / raw)
To: Albert Esteve
Cc: dri-devel, imx, linux-arm-kernel, linux-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-samsung-soc, linux-stm32,
linux-sunxi, linux-tegra, Alain Volmat, Alexandre Torgue,
Alim Akhtar, Alison Wang, Andrzej Hajda, Andy Yan, Biju Das,
Chen-Yu Tsai, David Airlie, Fabio Estevam, Frank Li,
Geert Uytterhoeven, Heiko Stübner, Inki Dae, Jagan Teki,
Jernej Skrabec, Jessica Zhang, Jingoo Han, Jonas Karlman,
Jonathan Hunter, Jyri Sarha, Kieran Bingham, Krzysztof Kozlowski,
Kyungmin Park, Laurent Pinchart, Laurent Pinchart,
Laurentiu Palcu, Linus Walleij, Luca Ceresoli, Lucas Stach,
Maarten Lankhorst, Magnus Damm, Marek Szyprowski, Marek Vasut,
Maxime Coquelin, Maxime Ripard, Mikko Perttunen, Neil Armstrong,
Paul Cercueil, Paul Kocialkowski, Pengutronix Kernel Team,
Peter Griffin, Philippe Cornu, Raphael Gallais-Pou,
Raphael Gallais-Pou, Robert Foss, Samuel Holland, Sandy Huang,
Sascha Hauer, Seung-Woo Kim, Simona Vetter, Stefan Agner,
Thierry Reding, Thomas Zimmermann, Tomi Valkeinen, Yannick Fertre
On Fri, 26 Jun 2026 14:03:23 +0200, Albert Esteve wrote:
> The global panel_list holds raw pointers to drm_panel objects.
> Nothing prevents a panel from being freed while it is still linked
> in the list: if a driver's probe calls drm_panel_add() and then
> fails at a later step, panel->list remains in panel_list. Any
> subsequent call to of_drm_find_panel() that iterates the list will
>
> [ ... ]
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Thanks!
Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel
2026-06-26 12:03 [PATCH 0/5] drm/panel: refcounting panel lookups and references Albert Esteve
2026-06-26 12:03 ` [PATCH 1/5] drm/panel: have drm_panel_add/remove manage a list reference Albert Esteve
@ 2026-06-26 12:03 ` Albert Esteve
2026-06-26 12:24 ` sashiko-bot
2026-06-26 12:48 ` Maxime Ripard
2026-06-26 12:03 ` [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference Albert Esteve
` (2 subsequent siblings)
4 siblings, 2 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 12:03 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter
Cc: dri-devel, linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra, Albert Esteve
drm_panel_bridge_add_typed() stores a pointer to the drm_panel it
wraps, but never acquires a reference to it. If the panel device
goes away while a panel_bridge still exists, the dangling pointer can
be dereferenced through panel_bridge->panel.
Acquire a reference in drm_panel_bridge_add_typed() with drm_panel_get()
and release it in each teardown path.
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/panel.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 4978ec98a0828..6b98ad19508df 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -294,7 +294,7 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel,
return (void *)panel_bridge;
panel_bridge->connector_type = connector_type;
- panel_bridge->panel = panel;
+ panel_bridge->panel = drm_panel_get(panel);
panel_bridge->bridge.of_node = panel->dev->of_node;
panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
@@ -316,6 +316,7 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed);
void drm_panel_bridge_remove(struct drm_bridge *bridge)
{
struct panel_bridge *panel_bridge;
+ struct drm_panel *panel;
if (!bridge)
return;
@@ -326,10 +327,12 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge)
}
panel_bridge = drm_bridge_to_panel_bridge(bridge);
+ panel = panel_bridge->panel;
drm_bridge_remove(bridge);
/* TODO remove this after reworking panel_bridge lifetime */
- devm_drm_put_bridge(panel_bridge->panel->dev, bridge);
+ devm_drm_put_bridge(panel->dev, bridge);
+ drm_panel_put(panel);
}
EXPORT_SYMBOL(drm_panel_bridge_remove);
@@ -357,11 +360,14 @@ EXPORT_SYMBOL(drm_panel_bridge_set_orientation);
static void devm_drm_panel_bridge_release(struct device *dev, void *res)
{
struct drm_bridge *bridge = *(struct drm_bridge **)res;
+ struct panel_bridge *panel_bridge;
if (!bridge)
return;
+ panel_bridge = drm_bridge_to_panel_bridge(bridge);
drm_bridge_remove(bridge);
+ drm_panel_put(panel_bridge->panel);
}
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel
2026-06-26 12:03 ` [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel Albert Esteve
@ 2026-06-26 12:24 ` sashiko-bot
2026-06-26 12:48 ` Maxime Ripard
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-26 12:24 UTC (permalink / raw)
To: Albert Esteve; +Cc: wsa+renesas, imx, dri-devel, linux-sunxi, Frank.Li
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Accessing `panel_bridge->panel` after calling `drm_bridge_remove()` causes a use-after-free if the panel device was removed first.
Pre-existing issues:
- [High] Passing `panel->dev` to `devm_drm_put_bridge()` can cause a use-after-free or a WARN_ON if the panel's struct device has already been freed.
--
commit 0ea9791b2d1689f026240930cee1476a24170351
Author: Albert Esteve <aesteve@redhat.com>
drm/bridge/panel: hold a reference to the wrapped panel
drm_panel_bridge_add_typed() stores a pointer to the drm_panel it
wraps, but never acquires a reference to it. If the panel device
goes away while a panel_bridge still exists, the dangling pointer can
be dereferenced through panel_bridge->panel.
Acquire a reference in drm_panel_bridge_add_typed() with drm_panel_get()
and release it in each teardown path.
> diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
> index 4978ec98a0828..6b98ad19508df 100644
> --- a/drivers/gpu/drm/bridge/panel.c
> +++ b/drivers/gpu/drm/bridge/panel.c
[ ... ]
> @@ -316,6 +316,7 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed);
> void drm_panel_bridge_remove(struct drm_bridge *bridge)
> {
> struct panel_bridge *panel_bridge;
> + struct drm_panel *panel;
>
> if (!bridge)
> return;
> @@ -326,10 +327,12 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge)
> }
>
> panel_bridge = drm_bridge_to_panel_bridge(bridge);
> + panel = panel_bridge->panel;
>
> drm_bridge_remove(bridge);
> /* TODO remove this after reworking panel_bridge lifetime */
> - devm_drm_put_bridge(panel_bridge->panel->dev, bridge);
> + devm_drm_put_bridge(panel->dev, bridge);
[Severity: High]
This isn't a bug introduced by this patch, but could this pass a freed or
unbound device pointer to devm_drm_put_bridge()?
If the panel device is unbound and unregistered, its struct device might be
freed if no other references are held. Even though the struct drm_panel is
kept alive by drm_panel_get(), it does not inherently hold a reference to
panel->dev.
If the host device later removes the bridge via drm_panel_bridge_remove(),
passing panel->dev to devm_drm_put_bridge() would dereference a dangling
pointer, or trigger a WARN_ON if the device was kept alive but unbound.
> + drm_panel_put(panel);
> }
> EXPORT_SYMBOL(drm_panel_bridge_remove);
[ ... ]
> @@ -357,11 +360,14 @@ EXPORT_SYMBOL(drm_panel_bridge_set_orientation);
> static void devm_drm_panel_bridge_release(struct device *dev, void *res)
> {
> struct drm_bridge *bridge = *(struct drm_bridge **)res;
> + struct panel_bridge *panel_bridge;
>
> if (!bridge)
> return;
>
> + panel_bridge = drm_bridge_to_panel_bridge(bridge);
> drm_bridge_remove(bridge);
> + drm_panel_put(panel_bridge->panel);
[Severity: High]
Does this cause a use-after-free of panel_bridge?
If the panel device is unbound before the host device, its devres actions
drop the bridge's initial reference. Later, when the host device unbinds,
devm_drm_panel_bridge_release() calls drm_bridge_remove().
Because that was the final reference, drm_bridge_remove() frees the
panel_bridge structure. The immediate next line dereferences
panel_bridge->panel.
Should we cache the panel pointer in a local variable before calling
drm_bridge_remove(), similar to what was done in drm_panel_bridge_remove()?
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626-drm_refcount_wiring-v1-0-cca1a7b3bdef@redhat.com?part=2
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel
2026-06-26 12:03 ` [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel Albert Esteve
2026-06-26 12:24 ` sashiko-bot
@ 2026-06-26 12:48 ` Maxime Ripard
1 sibling, 0 replies; 17+ messages in thread
From: Maxime Ripard @ 2026-06-26 12:48 UTC (permalink / raw)
To: Albert Esteve
Cc: dri-devel, imx, linux-arm-kernel, linux-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-samsung-soc, linux-stm32,
linux-sunxi, linux-tegra, Alain Volmat, Alexandre Torgue,
Alim Akhtar, Alison Wang, Andrzej Hajda, Andy Yan, Biju Das,
Chen-Yu Tsai, David Airlie, Fabio Estevam, Frank Li,
Geert Uytterhoeven, Heiko Stübner, Inki Dae, Jagan Teki,
Jernej Skrabec, Jessica Zhang, Jingoo Han, Jonas Karlman,
Jonathan Hunter, Jyri Sarha, Kieran Bingham, Krzysztof Kozlowski,
Kyungmin Park, Laurent Pinchart, Laurent Pinchart,
Laurentiu Palcu, Linus Walleij, Luca Ceresoli, Lucas Stach,
Maarten Lankhorst, Magnus Damm, Marek Szyprowski, Marek Vasut,
Maxime Coquelin, Maxime Ripard, Mikko Perttunen, Neil Armstrong,
Paul Cercueil, Paul Kocialkowski, Pengutronix Kernel Team,
Peter Griffin, Philippe Cornu, Raphael Gallais-Pou,
Raphael Gallais-Pou, Robert Foss, Samuel Holland, Sandy Huang,
Sascha Hauer, Seung-Woo Kim, Simona Vetter, Stefan Agner,
Thierry Reding, Thomas Zimmermann, Tomi Valkeinen, Yannick Fertre
On Fri, 26 Jun 2026 14:03:24 +0200, Albert Esteve wrote:
> drm_panel_bridge_add_typed() stores a pointer to the drm_panel it
> wraps, but never acquires a reference to it. If the panel device
> goes away while a panel_bridge still exists, the dangling pointer can
> be dereferenced through panel_bridge->panel.
>
>
> [ ... ]
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Thanks!
Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference
2026-06-26 12:03 [PATCH 0/5] drm/panel: refcounting panel lookups and references Albert Esteve
2026-06-26 12:03 ` [PATCH 1/5] drm/panel: have drm_panel_add/remove manage a list reference Albert Esteve
2026-06-26 12:03 ` [PATCH 2/5] drm/bridge/panel: hold a reference to the wrapped panel Albert Esteve
@ 2026-06-26 12:03 ` Albert Esteve
2026-06-26 12:50 ` Maxime Ripard
2026-06-26 12:03 ` [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths Albert Esteve
2026-06-26 12:03 ` [PATCH 5/5] drm: release panel reference after panel bridge creation Albert Esteve
4 siblings, 1 reply; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 12:03 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter
Cc: dri-devel, linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra, Albert Esteve
Callers of of_drm_find_panel() receive a pointer with no reference
held, creating a window where the panel device can be unregistered
and freed between the lookup and first use (e.g., drm_panel_prepare()).
find_panel_by_fwnode() is the fwnode counterpart of of_drm_find_panel().
drm_panel_add_follower() worked around the missing panel kref by calling
get_device() on the panel's underlying struct device. However, get_device()
only prevents the device kobject from being freed. It does not prevent the
panel's kzalloc()'d container memory from being released when the kref
reaches zero.
Fix both lookup functions by acquiring a reference with drm_panel_get()
before returning, under panel_lock. Callers are now responsible for calling
drm_panel_put() when they no longer need the pointer.
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/drm_panel.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 545fe93dc28fe..a00ae98ed0956 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -458,14 +458,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc);
#ifdef CONFIG_OF
/**
- * of_drm_find_panel - look up a panel using a device tree node
+ * of_drm_find_panel - look up and reference a panel by device tree node
* @np: device tree node of the panel
*
* Searches the set of registered panels for one that matches the given device
- * tree node. If a matching panel is found, return a pointer to it.
+ * tree node. If a matching panel is found, the panel's reference count is
+ * incremented before returning a pointer to it. The caller must call
+ * drm_panel_put() when it no longer needs the panel pointer.
*
- * Return: A pointer to the panel registered for the specified device tree
- * node or an ERR_PTR() if no panel matching the device tree node can be found.
+ * Return: A reference-counted pointer to the panel registered for the specified
+ * device tree node or an ERR_PTR() if no panel matching the device tree node
+ * can be found.
*
* Possible error codes returned by this function:
*
@@ -484,6 +487,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np)
list_for_each_entry(panel, &panel_list, list) {
if (panel->dev->of_node == np) {
+ drm_panel_get(panel);
mutex_unlock(&panel_lock);
return panel;
}
@@ -538,7 +542,13 @@ int of_drm_get_panel_orientation(const struct device_node *np,
EXPORT_SYMBOL(of_drm_get_panel_orientation);
#endif
-/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */
+/*
+ * Find panel by fwnode, returning a counted reference.
+ *
+ * Behaves identically to of_drm_find_panel(). On success the returned
+ * pointer has been passed through drm_panel_get(); the caller must call
+ * drm_panel_put() when done with it.
+ */
static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
{
struct drm_panel *panel;
@@ -550,6 +560,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode
list_for_each_entry(panel, &panel_list, list) {
if (dev_fwnode(panel->dev) == fwnode) {
+ drm_panel_get(panel);
mutex_unlock(&panel_lock);
return panel;
}
@@ -686,6 +697,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower)
mutex_unlock(&panel->follower_lock);
put_device(panel->dev);
+ drm_panel_put(panel);
}
EXPORT_SYMBOL(drm_panel_remove_follower);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference
2026-06-26 12:03 ` [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference Albert Esteve
@ 2026-06-26 12:50 ` Maxime Ripard
2026-06-26 15:11 ` Albert Esteve
0 siblings, 1 reply; 17+ messages in thread
From: Maxime Ripard @ 2026-06-26 12:50 UTC (permalink / raw)
To: Albert Esteve
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 3911 bytes --]
On Fri, Jun 26, 2026 at 02:03:25PM +0200, Albert Esteve wrote:
> Callers of of_drm_find_panel() receive a pointer with no reference
> held, creating a window where the panel device can be unregistered
> and freed between the lookup and first use (e.g., drm_panel_prepare()).
>
> find_panel_by_fwnode() is the fwnode counterpart of of_drm_find_panel().
> drm_panel_add_follower() worked around the missing panel kref by calling
> get_device() on the panel's underlying struct device. However, get_device()
> only prevents the device kobject from being freed. It does not prevent the
> panel's kzalloc()'d container memory from being released when the kref
> reaches zero.
>
> Fix both lookup functions by acquiring a reference with drm_panel_get()
> before returning, under panel_lock. Callers are now responsible for calling
> drm_panel_put() when they no longer need the pointer.
>
> Signed-off-by: Albert Esteve <aesteve@redhat.com>
> ---
> drivers/gpu/drm/drm_panel.c | 22 +++++++++++++++++-----
> 1 file changed, 17 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
> index 545fe93dc28fe..a00ae98ed0956 100644
> --- a/drivers/gpu/drm/drm_panel.c
> +++ b/drivers/gpu/drm/drm_panel.c
> @@ -458,14 +458,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc);
>
> #ifdef CONFIG_OF
> /**
> - * of_drm_find_panel - look up a panel using a device tree node
> + * of_drm_find_panel - look up and reference a panel by device tree node
> * @np: device tree node of the panel
> *
> * Searches the set of registered panels for one that matches the given device
> - * tree node. If a matching panel is found, return a pointer to it.
> + * tree node. If a matching panel is found, the panel's reference count is
> + * incremented before returning a pointer to it. The caller must call
> + * drm_panel_put() when it no longer needs the panel pointer.
> *
> - * Return: A pointer to the panel registered for the specified device tree
> - * node or an ERR_PTR() if no panel matching the device tree node can be found.
> + * Return: A reference-counted pointer to the panel registered for the specified
> + * device tree node or an ERR_PTR() if no panel matching the device tree node
> + * can be found.
> *
> * Possible error codes returned by this function:
> *
> @@ -484,6 +487,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np)
>
> list_for_each_entry(panel, &panel_list, list) {
> if (panel->dev->of_node == np) {
> + drm_panel_get(panel);
> mutex_unlock(&panel_lock);
> return panel;
> }
> @@ -538,7 +542,13 @@ int of_drm_get_panel_orientation(const struct device_node *np,
> EXPORT_SYMBOL(of_drm_get_panel_orientation);
> #endif
>
> -/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */
> +/*
> + * Find panel by fwnode, returning a counted reference.
> + *
> + * Behaves identically to of_drm_find_panel(). On success the returned
> + * pointer has been passed through drm_panel_get(); the caller must call
> + * drm_panel_put() when done with it.
> + */
> static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
> {
> struct drm_panel *panel;
> @@ -550,6 +560,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode
>
> list_for_each_entry(panel, &panel_list, list) {
> if (dev_fwnode(panel->dev) == fwnode) {
> + drm_panel_get(panel);
> mutex_unlock(&panel_lock);
> return panel;
> }
This part should probably be in a separate patch
> @@ -686,6 +697,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower)
> mutex_unlock(&panel->follower_lock);
>
> put_device(panel->dev);
> + drm_panel_put(panel);
> }
> EXPORT_SYMBOL(drm_panel_remove_follower);
together with this one?
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference
2026-06-26 12:50 ` Maxime Ripard
@ 2026-06-26 15:11 ` Albert Esteve
0 siblings, 0 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 15:11 UTC (permalink / raw)
To: Maxime Ripard
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
On Fri, Jun 26, 2026 at 2:50 PM Maxime Ripard <mripard@kernel.org> wrote:
>
> On Fri, Jun 26, 2026 at 02:03:25PM +0200, Albert Esteve wrote:
> > Callers of of_drm_find_panel() receive a pointer with no reference
> > held, creating a window where the panel device can be unregistered
> > and freed between the lookup and first use (e.g., drm_panel_prepare()).
> >
> > find_panel_by_fwnode() is the fwnode counterpart of of_drm_find_panel().
> > drm_panel_add_follower() worked around the missing panel kref by calling
> > get_device() on the panel's underlying struct device. However, get_device()
> > only prevents the device kobject from being freed. It does not prevent the
> > panel's kzalloc()'d container memory from being released when the kref
> > reaches zero.
> >
> > Fix both lookup functions by acquiring a reference with drm_panel_get()
> > before returning, under panel_lock. Callers are now responsible for calling
> > drm_panel_put() when they no longer need the pointer.
> >
> > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > ---
> > drivers/gpu/drm/drm_panel.c | 22 +++++++++++++++++-----
> > 1 file changed, 17 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
> > index 545fe93dc28fe..a00ae98ed0956 100644
> > --- a/drivers/gpu/drm/drm_panel.c
> > +++ b/drivers/gpu/drm/drm_panel.c
> > @@ -458,14 +458,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc);
> >
> > #ifdef CONFIG_OF
> > /**
> > - * of_drm_find_panel - look up a panel using a device tree node
> > + * of_drm_find_panel - look up and reference a panel by device tree node
> > * @np: device tree node of the panel
> > *
> > * Searches the set of registered panels for one that matches the given device
> > - * tree node. If a matching panel is found, return a pointer to it.
> > + * tree node. If a matching panel is found, the panel's reference count is
> > + * incremented before returning a pointer to it. The caller must call
> > + * drm_panel_put() when it no longer needs the panel pointer.
> > *
> > - * Return: A pointer to the panel registered for the specified device tree
> > - * node or an ERR_PTR() if no panel matching the device tree node can be found.
> > + * Return: A reference-counted pointer to the panel registered for the specified
> > + * device tree node or an ERR_PTR() if no panel matching the device tree node
> > + * can be found.
> > *
> > * Possible error codes returned by this function:
> > *
> > @@ -484,6 +487,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np)
> >
> > list_for_each_entry(panel, &panel_list, list) {
> > if (panel->dev->of_node == np) {
> > + drm_panel_get(panel);
> > mutex_unlock(&panel_lock);
> > return panel;
> > }
> > @@ -538,7 +542,13 @@ int of_drm_get_panel_orientation(const struct device_node *np,
> > EXPORT_SYMBOL(of_drm_get_panel_orientation);
> > #endif
> >
> > -/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */
> > +/*
> > + * Find panel by fwnode, returning a counted reference.
> > + *
> > + * Behaves identically to of_drm_find_panel(). On success the returned
> > + * pointer has been passed through drm_panel_get(); the caller must call
> > + * drm_panel_put() when done with it.
> > + */
> > static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
> > {
> > struct drm_panel *panel;
> > @@ -550,6 +560,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode
> >
> > list_for_each_entry(panel, &panel_list, list) {
> > if (dev_fwnode(panel->dev) == fwnode) {
> > + drm_panel_get(panel);
> > mutex_unlock(&panel_lock);
> > return panel;
> > }
>
> This part should probably be in a separate patch
Yes. This is another place where I hesitated on organization, as it is
very similar to of_drm_find_panel() fix. But find_panel_by_fwnode() is
much more self-contained (it is declared static to begin with). So it
makes sense to split them. I will do so in the next version.
>
> > @@ -686,6 +697,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower)
> > mutex_unlock(&panel->follower_lock);
> >
> > put_device(panel->dev);
> > + drm_panel_put(panel);
> > }
> > EXPORT_SYMBOL(drm_panel_remove_follower);
>
> together with this one?
>
> Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths
2026-06-26 12:03 [PATCH 0/5] drm/panel: refcounting panel lookups and references Albert Esteve
` (2 preceding siblings ...)
2026-06-26 12:03 ` [PATCH 3/5] drm/panel: make *find_panel*() return a counted reference Albert Esteve
@ 2026-06-26 12:03 ` Albert Esteve
2026-06-26 12:23 ` sashiko-bot
2026-06-26 12:53 ` Maxime Ripard
2026-06-26 12:03 ` [PATCH 5/5] drm: release panel reference after panel bridge creation Albert Esteve
4 siblings, 2 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 12:03 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter
Cc: dri-devel, linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra, Albert Esteve
of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
counted reference that the caller must release with drm_panel_put().
For bridge drivers that immediately wrap the panel in a panel_bridge
(which acquires its own reference), release the lookup reference right
after the bridge creation call.
For analogix-anx6345, which stores the panel for direct use, release
the reference in the i2c remove path.
For platform drivers using analogix_dp_core with a component lifecycle
(exynos_dp, rockchip analogix_dp), release the lookup reference in the
platform remove() function. The panel_bridge created during bind() holds
a separate reference that devm cleanup releases after remove() returns.
Also fix devm_drm_of_get_bridge() and drmm_of_get_bridge() in
bridge/panel.c itself: both call drm_of_find_panel_or_bridge() and
then pass the panel to devm/drmm_panel_bridge_add(), which acquires
its own reference via drm_panel_bridge_add_typed(). The lookup
reference was never released; add drm_panel_put() after each bridge
creation call.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 3 +++
drivers/gpu/drm/bridge/panel.c | 8 ++++++--
drivers/gpu/drm/exynos/exynos_dp.c | 10 ++++++++++
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++++++
drivers/gpu/drm/logicvc/logicvc_interface.c | 12 ++++++++++++
drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 11 +++++++++++
drivers/gpu/drm/sti/sti_dvo.c | 3 +++
drivers/gpu/drm/stm/lvds.c | 3 +++
drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 +++++++++++++
drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 +++++++++++++
drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 2 ++
drivers/gpu/drm/tegra/dsi.c | 1 +
drivers/gpu/drm/tegra/output.c | 3 +++
14 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
index f3fe47b12edca..1fe11b075f860 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
@@ -756,6 +756,9 @@ static void anx6345_i2c_remove(struct i2c_client *client)
{
struct anx6345 *anx6345 = i2c_get_clientdata(client);
+ if (anx6345->panel)
+ drm_panel_put(anx6345->panel);
+
drm_bridge_remove(&anx6345->bridge);
unregister_i2c_dummy_clients(anx6345);
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 6b98ad19508df..04b53ae698e5b 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -513,8 +513,10 @@ struct drm_bridge *devm_drm_of_get_bridge(struct device *dev,
if (ret)
return ERR_PTR(ret);
- if (panel)
+ if (panel) {
bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
+ }
return bridge;
}
@@ -547,8 +549,10 @@ struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm,
if (ret)
return ERR_PTR(ret);
- if (panel)
+ if (panel) {
bridge = drmm_panel_bridge_add(drm, panel);
+ drm_panel_put(panel);
+ }
return bridge;
}
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b805403281504..14f5b1b452506 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -193,6 +193,16 @@ static int exynos_dp_probe(struct platform_device *pdev)
static void exynos_dp_remove(struct platform_device *pdev)
{
+ struct exynos_dp_device *dp = platform_get_drvdata(pdev);
+
+ /*
+ * Release the probe-time reference from of_drm_find_panel(). If bind
+ * ran, the panel_bridge holds a second reference that devm cleanup
+ * will release when the bridge is destroyed after remove() returns.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
+
component_del(&pdev->dev, &exynos_dp_ops);
}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
index 0dc36df6ada34..9d15a0035ea99 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
@@ -245,5 +245,8 @@ int exynos_dpi_remove(struct drm_encoder *encoder)
exynos_dpi_disable(&ctx->encoder);
+ if (ctx->panel)
+ drm_panel_put(ctx->panel);
+
return 0;
}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
index 84eff7519e322..ec71fbbb0eb89 100644
--- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
@@ -109,6 +109,13 @@ static int fsl_dcu_attach_panel(struct fsl_dcu_drm_device *fsl_dev,
return ret;
}
+static void fsl_dcu_panel_put_action(void *data)
+{
+ struct drm_panel *panel = data;
+
+ drm_panel_put(panel);
+}
+
int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
{
struct device_node *panel_node;
@@ -124,6 +131,12 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
if (IS_ERR(fsl_dev->connector.panel))
return PTR_ERR(fsl_dev->connector.panel);
+ ret = devm_add_action_or_reset(fsl_dev->dev,
+ fsl_dcu_panel_put_action,
+ fsl_dev->connector.panel);
+ if (ret)
+ return ret;
+
return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel);
}
@@ -132,6 +145,11 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
return ret;
if (panel) {
+ ret = devm_add_action_or_reset(fsl_dev->dev,
+ fsl_dcu_panel_put_action, panel);
+ if (ret)
+ return ret;
+
fsl_dev->connector.panel = panel;
return fsl_dcu_attach_panel(fsl_dev, panel);
}
diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.c b/drivers/gpu/drm/logicvc/logicvc_interface.c
index 689049d395c0d..81f760dc07f8d 100644
--- a/drivers/gpu/drm/logicvc/logicvc_interface.c
+++ b/drivers/gpu/drm/logicvc/logicvc_interface.c
@@ -28,6 +28,11 @@
#define logicvc_interface_from_drm_connector(c) \
container_of(c, struct logicvc_interface, drm_connector)
+static void logicvc_panel_put_action(void *data)
+{
+ drm_panel_put(data);
+}
+
static void logicvc_encoder_enable(struct drm_encoder *drm_encoder)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev);
@@ -160,6 +165,13 @@ int logicvc_interface_init(struct logicvc_drm *logicvc)
if (ret == -EPROBE_DEFER)
goto error_early;
+ if (interface->drm_panel) {
+ ret = devm_add_action_or_reset(dev, logicvc_panel_put_action,
+ interface->drm_panel);
+ if (ret)
+ goto error_early;
+ }
+
ret = drm_encoder_init(drm_dev, &interface->drm_encoder,
&logicvc_encoder_funcs, encoder_type, NULL);
if (ret) {
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index 06072efd7fca3..4b2795a6caf8c 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -27,6 +27,7 @@
#include <drm/drm_bridge_connector.h>
#include <drm/bridge/analogix_dp.h>
#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
@@ -472,6 +473,16 @@ static int rockchip_dp_probe(struct platform_device *pdev)
static void rockchip_dp_remove(struct platform_device *pdev)
{
+ struct rockchip_dp_device *dp = platform_get_drvdata(pdev);
+
+ /*
+ * Release the probe-time reference from of_drm_find_panel(). If bind
+ * ran, the panel_bridge holds a second reference that devm cleanup
+ * will release when the bridge is destroyed after remove() returns.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
+
component_del(&pdev->dev, &rockchip_dp_component_ops);
}
diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c
index 7484d3c3f4ed5..64a9da0362fb8 100644
--- a/drivers/gpu/drm/sti/sti_dvo.c
+++ b/drivers/gpu/drm/sti/sti_dvo.c
@@ -492,6 +492,9 @@ static void sti_dvo_unbind(struct device *dev,
{
struct sti_dvo *dvo = dev_get_drvdata(dev);
+ if (dvo->panel)
+ drm_panel_put(dvo->panel);
+
drm_bridge_remove(&dvo->bridge);
}
diff --git a/drivers/gpu/drm/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c
index 50a878688e477..77735e26c56e3 100644
--- a/drivers/gpu/drm/stm/lvds.c
+++ b/drivers/gpu/drm/stm/lvds.c
@@ -1189,6 +1189,9 @@ static void lvds_remove(struct platform_device *pdev)
{
struct stm_lvds *lvds = platform_get_drvdata(pdev);
+ if (lvds->panel)
+ drm_panel_put(lvds->panel);
+
lvds_pixel_clk_unregister(lvds);
drm_bridge_remove(&lvds->lvds_bridge);
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c
index 6716e895ae8a4..e1b342c922224 100644
--- a/drivers/gpu/drm/sun4i/sun4i_lvds.c
+++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c
@@ -18,6 +18,11 @@
#include "sun4i_tcon.h"
#include "sun4i_lvds.h"
+static void sun4i_panel_put_action(void *data)
+{
+ drm_panel_put(data);
+}
+
struct sun4i_lvds {
struct drm_connector connector;
struct drm_encoder encoder;
@@ -113,6 +118,14 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
return 0;
}
+ if (lvds->panel) {
+ ret = devm_add_action_or_reset(tcon->dev,
+ sun4i_panel_put_action,
+ lvds->panel);
+ if (ret)
+ return ret;
+ }
+
drm_encoder_helper_add(&lvds->encoder,
&sun4i_lvds_enc_helper_funcs);
ret = drm_simple_encoder_init(drm, &lvds->encoder,
diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index dfb6acc42f02e..0066bec5a9e5a 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -43,6 +43,11 @@ drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder)
encoder);
}
+static void sun4i_panel_put_action(void *data)
+{
+ drm_panel_put(data);
+}
+
static int sun4i_rgb_get_modes(struct drm_connector *connector)
{
struct sun4i_rgb *rgb =
@@ -205,6 +210,14 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
return 0;
}
+ if (rgb->panel) {
+ ret = devm_add_action_or_reset(tcon->dev,
+ sun4i_panel_put_action,
+ rgb->panel);
+ if (ret)
+ return ret;
+ }
+
drm_encoder_helper_add(&rgb->encoder,
&sun4i_rgb_enc_helper_funcs);
ret = drm_simple_encoder_init(drm, &rgb->encoder,
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index c35b70d83e53b..1e8bc12fb6d04 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
@@ -985,6 +985,8 @@ static int sun6i_dsi_detach(struct mipi_dsi_host *host,
{
struct sun6i_dsi *dsi = host_to_sun6i_dsi(host);
+ if (dsi->panel)
+ drm_panel_put(dsi->panel);
dsi->panel = NULL;
dsi->device = NULL;
diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c
index 7f25c50621c94..57a016f47434d 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -1516,6 +1516,7 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host,
struct tegra_output *output = &dsi->output;
if (output->panel && &device->dev == output->panel->dev) {
+ drm_panel_put(output->panel);
output->panel = NULL;
if (output->connector.dev)
diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c
index 49e4f63a5550d..90db39dbdd332 100644
--- a/drivers/gpu/drm/tegra/output.c
+++ b/drivers/gpu/drm/tegra/output.c
@@ -195,6 +195,9 @@ int tegra_output_probe(struct tegra_output *output)
void tegra_output_remove(struct tegra_output *output)
{
+ if (output->panel)
+ drm_panel_put(output->panel);
+
if (output->hpd_gpio)
free_irq(output->hpd_irq, output);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths
2026-06-26 12:03 ` [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths Albert Esteve
@ 2026-06-26 12:23 ` sashiko-bot
2026-06-26 12:53 ` Maxime Ripard
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-26 12:23 UTC (permalink / raw)
To: Albert Esteve; +Cc: linux-sunxi, Frank.Li, dri-devel, wsa+renesas, imx
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Dropping the panel reference before removing the bridge creates a Use-After-Free window during driver removal.
- [Medium] Panel references acquired during probe/attach are not released on error paths, causing memory leaks.
- [Medium] Panel reference leaks when wrapping a panel in a bridge without dropping the lookup reference.
--
commit 621578b5ce602b599c8bef0b18966f05081671b7
Author: Albert Esteve <aesteve@redhat.com>
drm/bridge: release panel reference on all lookup exit paths
of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
counted reference that the caller must release with drm_panel_put().
For bridge drivers that immediately wrap the panel in a panel_bridge
(which acquires its own reference), release the lookup reference right
after the bridge creation call.
[Severity: Medium]
Since the lookup functions now return a counted reference, does this
change introduce memory leaks on driver probe error paths?
If a driver successfully acquires the panel reference during probe or
attach, but a subsequent initialization step fails (like clock
registration, component addition, or memory allocation), it appears the
reference is leaked if drm_panel_put is not called before returning the
error.
For example, in exynos_dp_probe, if component_add fails, the panel
reference is not released. Similar leaks appear to happen on error
paths in analogix_dp_aux_done_probing, sun6i_dsi_attach,
tegra_output_probe, and lvds_probe.
[Severity: Medium]
Additionally, are there missing conversions for drivers that wrap the
panel in a bridge?
The commit message notes that bridge drivers immediately wrapping the
panel must release the lookup reference right after the bridge creation
call, since the bridge acquires its own reference.
However, it looks like several drivers were missed, such as
lvds_codec_probe, fsl_ldb_probe, lt9211_parse_dt, ssd2825_probe,
tc358767_probe, and tc358768_probe. In these functions, the panel is
wrapped with devm_drm_panel_bridge_add or drm_panel_bridge_add_typed,
but the initial lookup reference is never dropped.
> diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
> index f3fe47b12edca..1fe11b075f860 100644
> --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
> +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
> @@ -756,6 +756,9 @@ static void anx6345_i2c_remove(struct i2c_client *client)
> {
> struct anx6345 *anx6345 = i2c_get_clientdata(client);
>
> + if (anx6345->panel)
> + drm_panel_put(anx6345->panel);
> +
> drm_bridge_remove(&anx6345->bridge);
[Severity: High]
Does this create a use-after-free window?
By dropping the last reference to the panel here, the panel object might
be freed. However, the bridge is removed after this step. During this
window, the bridge remains globally visible and registered.
If a concurrent drm commit or modeset (like an atomic disable) executes
on the display pipeline before drm_bridge_remove is called, it could
invoke the bridge callbacks which would dereference the already-freed
panel pointer.
Would it be safer to unregister the bridge before dropping the reference
to the internal structures it relies on?
[ ... ]
> diff --git a/drivers/gpu/drm/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c
> index 50a878688e477..77735e26c56e3 100644
> --- a/drivers/gpu/drm/stm/lvds.c
> +++ b/drivers/gpu/drm/stm/lvds.c
> @@ -1189,6 +1189,9 @@ static void lvds_remove(struct platform_device *pdev)
> {
> struct stm_lvds *lvds = platform_get_drvdata(pdev);
>
> + if (lvds->panel)
> + drm_panel_put(lvds->panel);
> +
> lvds_pixel_clk_unregister(lvds);
>
> drm_bridge_remove(&lvds->lvds_bridge);
[Severity: High]
Could this also trigger a use-after-free for the panel?
Similar to the anx6345 driver, the panel reference is dropped before the
bridge is removed. A concurrent atomic disable could execute in this
window and dereference the freed panel inside the bridge callbacks.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626-drm_refcount_wiring-v1-0-cca1a7b3bdef@redhat.com?part=4
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths
2026-06-26 12:03 ` [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths Albert Esteve
2026-06-26 12:23 ` sashiko-bot
@ 2026-06-26 12:53 ` Maxime Ripard
2026-06-26 13:11 ` Albert Esteve
1 sibling, 1 reply; 17+ messages in thread
From: Maxime Ripard @ 2026-06-26 12:53 UTC (permalink / raw)
To: Albert Esteve
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 1466 bytes --]
On Fri, Jun 26, 2026 at 02:03:26PM +0200, Albert Esteve wrote:
> of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
> counted reference that the caller must release with drm_panel_put().
>
> For bridge drivers that immediately wrap the panel in a panel_bridge
> (which acquires its own reference), release the lookup reference right
> after the bridge creation call.
>
> For analogix-anx6345, which stores the panel for direct use, release
> the reference in the i2c remove path.
>
> For platform drivers using analogix_dp_core with a component lifecycle
> (exynos_dp, rockchip analogix_dp), release the lookup reference in the
> platform remove() function. The panel_bridge created during bind() holds
> a separate reference that devm cleanup releases after remove() returns.
>
> Also fix devm_drm_of_get_bridge() and drmm_of_get_bridge() in
> bridge/panel.c itself: both call drm_of_find_panel_or_bridge() and
> then pass the panel to devm/drmm_panel_bridge_add(), which acquires
> its own reference via drm_panel_bridge_add_typed(). The lookup
> reference was never released; add drm_panel_put() after each bridge
> creation call.
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Albert Esteve <aesteve@redhat.com>
I think this one should be either split into one patch per driver, or
merged with the of_drm_find_panel patch. I'm still not quite sure which
would be the best, maybe the latter?
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths
2026-06-26 12:53 ` Maxime Ripard
@ 2026-06-26 13:11 ` Albert Esteve
0 siblings, 0 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 13:11 UTC (permalink / raw)
To: Maxime Ripard
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
On Fri, Jun 26, 2026 at 2:53 PM Maxime Ripard <mripard@kernel.org> wrote:
>
> On Fri, Jun 26, 2026 at 02:03:26PM +0200, Albert Esteve wrote:
> > of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
> > counted reference that the caller must release with drm_panel_put().
> >
> > For bridge drivers that immediately wrap the panel in a panel_bridge
> > (which acquires its own reference), release the lookup reference right
> > after the bridge creation call.
> >
> > For analogix-anx6345, which stores the panel for direct use, release
> > the reference in the i2c remove path.
> >
> > For platform drivers using analogix_dp_core with a component lifecycle
> > (exynos_dp, rockchip analogix_dp), release the lookup reference in the
> > platform remove() function. The panel_bridge created during bind() holds
> > a separate reference that devm cleanup releases after remove() returns.
> >
> > Also fix devm_drm_of_get_bridge() and drmm_of_get_bridge() in
> > bridge/panel.c itself: both call drm_of_find_panel_or_bridge() and
> > then pass the panel to devm/drmm_panel_bridge_add(), which acquires
> > its own reference via drm_panel_bridge_add_typed(). The lookup
> > reference was never released; add drm_panel_put() after each bridge
> > creation call.
> >
> > Assisted-by: Claude:claude-opus-4-6
> > Signed-off-by: Albert Esteve <aesteve@redhat.com>
>
> I think this one should be either split into one patch per driver, or
> merged with the of_drm_find_panel patch. I'm still not quite sure which
> would be the best, maybe the latter?
I have spent some time myself thinking about how to approach this.
Initially I thought about doing one patch per driver as you suggested,
but since there are many similar fixes that are mostly one-liners, In
the end I decided that grouping them would make the review easier and
result in a less inflated series. Maybe merging with of_drm_find_panel
patch makes sense to avoid having one patch introducing a transient
reference leak (even if it is fixed right after and is bisectable).
But that will also create a giant patch (harder to review?). Another
option could be to merge patches 4 and 5, which basically update all
callers, and keep the API update separated. Either way, I am happy
with whatever you decide is better. I will take note of this for v2.
>
> Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 5/5] drm: release panel reference after panel bridge creation
2026-06-26 12:03 [PATCH 0/5] drm/panel: refcounting panel lookups and references Albert Esteve
` (3 preceding siblings ...)
2026-06-26 12:03 ` [PATCH 4/5] drm/bridge: release panel reference on all lookup exit paths Albert Esteve
@ 2026-06-26 12:03 ` Albert Esteve
2026-06-26 12:28 ` sashiko-bot
2026-06-26 12:59 ` Maxime Ripard
4 siblings, 2 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 12:03 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter
Cc: dri-devel, linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra, Albert Esteve
of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
counted reference. In drivers that immediately wrap the panel in a
bridge via devm_drm_panel_bridge_add() or equivalent, the bridge
acquires its own reference, so the caller's lookup reference must be
released right afterwards.
Also handle the cases where a panel is found but cannot be used,
dropping the reference immediately in those paths.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++-
drivers/gpu/drm/mcde/mcde_drv.c | 1 +
drivers/gpu/drm/mcde/mcde_dsi.c | 1 +
drivers/gpu/drm/mxsfb/mxsfb_drv.c | 1 +
drivers/gpu/drm/omapdrm/dss/output.c | 1 +
drivers/gpu/drm/pl111/pl111_drv.c | 1 +
drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c | 1 +
drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c | 1 +
drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c | 1 +
drivers/gpu/drm/rockchip/rockchip_lvds.c | 1 +
drivers/gpu/drm/rockchip/rockchip_rgb.c | 1 +
drivers/gpu/drm/stm/ltdc.c | 1 +
drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++
drivers/gpu/drm/tidss/tidss_kms.c | 16 +++++++++++-----
drivers/gpu/drm/tve200/tve200_drv.c | 1 +
16 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.c b/drivers/gpu/drm/imx/dcss/dcss-kms.c
index 50bd7f36d36dd..01e0c10b6ea1a 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-kms.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-kms.c
@@ -77,6 +77,9 @@ static int dcss_kms_bridge_connector_init(struct dcss_kms_dev *kms)
if (ret)
return ret;
+ if (panel)
+ drm_panel_put(panel);
+
if (!bridge) {
dev_err(ddev->dev, "No bridge found %d.\n", ret);
return -ENODEV;
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
index 42c86f195c66b..1887e01d29701 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -1297,9 +1297,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
goto err_drvdata;
}
- if (panel)
+ if (panel) {
bridge = devm_drm_panel_bridge_add_typed(dev, panel,
DRM_MODE_CONNECTOR_DPI);
+ drm_panel_put(panel);
+ }
ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
NULL, DRM_MODE_ENCODER_DPI, NULL);
diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c
index 5f2c462bad7e1..53275b575f0cb 100644
--- a/drivers/gpu/drm/mcde/mcde_drv.c
+++ b/drivers/gpu/drm/mcde/mcde_drv.c
@@ -153,6 +153,7 @@ static int mcde_modeset_init(struct drm_device *drm)
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_DPI);
+ drm_panel_put(panel);
if (IS_ERR(bridge)) {
dev_err(drm->dev,
"Could not connect panel bridge\n");
diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
index 47d45897ed069..d9a454f226f79 100644
--- a/drivers/gpu/drm/mcde/mcde_dsi.c
+++ b/drivers/gpu/drm/mcde/mcde_dsi.c
@@ -1124,6 +1124,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master,
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_DSI);
+ drm_panel_put(panel);
if (IS_ERR(bridge)) {
dev_err(dev, "error adding panel bridge\n");
return PTR_ERR(bridge);
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
index 0b756da2fec22..bfcdc0c237ee1 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -128,6 +128,7 @@ static int mxsfb_attach_bridge(struct mxsfb_drm_private *mxsfb)
if (panel) {
bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel,
DRM_MODE_CONNECTOR_DPI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c
index ca891aba38209..6e9bc605ee22f 100644
--- a/drivers/gpu/drm/omapdrm/dss/output.c
+++ b/drivers/gpu/drm/omapdrm/dss/output.c
@@ -43,6 +43,7 @@ int omapdss_device_init_output(struct omap_dss_device *out,
struct drm_bridge *bridge;
bridge = drm_panel_bridge_add(out->panel);
+ drm_panel_put(out->panel);
if (IS_ERR(bridge)) {
dev_err(out->dev,
"unable to create panel bridge (%ld)\n",
diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c
index ac7b1d12a0f59..8ec659b3c08eb 100644
--- a/drivers/gpu/drm/pl111/pl111_drv.c
+++ b/drivers/gpu/drm/pl111/pl111_drv.c
@@ -145,6 +145,7 @@ static int pl111_modeset_init(struct drm_device *dev)
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_Unknown);
+ drm_panel_put(panel);
if (IS_ERR(bridge)) {
ret = PTR_ERR(bridge);
goto finish;
diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c
index db2088529b480..d8e7e9877ba86 100644
--- a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c
+++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c
@@ -69,6 +69,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel,
DRM_MODE_CONNECTOR_DPI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(no_free_ptr(bridge));
diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c
index 154410745a74b..cc2996f044721 100644
--- a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c
+++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c
@@ -791,6 +791,7 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
if (lvds->panel) {
lvds->next_bridge = devm_drm_panel_bridge_add(lvds->dev,
lvds->panel);
+ drm_panel_put(lvds->panel);
if (IS_ERR_OR_NULL(lvds->next_bridge)) {
ret = -EINVAL;
goto done;
diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c
index f50d166b764f5..3d0999e4fcfdf 100644
--- a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c
+++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c
@@ -90,6 +90,7 @@ int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu,
bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel,
DRM_MODE_CONNECTOR_DPI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(no_free_ptr(bridge));
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 7a0c4fa29f2f0..f754445d2631b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -605,6 +605,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
if (lvds->panel) {
lvds->bridge = drm_panel_bridge_add_typed(lvds->panel, DRM_MODE_CONNECTOR_LVDS);
+ drm_panel_put(lvds->panel);
if (IS_ERR(lvds->bridge)) {
ret = PTR_ERR(lvds->bridge);
goto err_free_encoder;
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
index add3123e5ce70..ea66c70013787 100644
--- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
@@ -139,6 +139,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_LVDS);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return ERR_CAST(bridge);
}
diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
index 95fcfa48d8be3..daf198edb42f5 100644
--- a/drivers/gpu/drm/stm/ltdc.c
+++ b/drivers/gpu/drm/stm/ltdc.c
@@ -1982,6 +1982,7 @@ int ltdc_load(struct drm_device *ddev)
if (panel) {
bridge = drmm_panel_bridge_add(ddev, panel);
+ drm_panel_put(panel);
if (IS_ERR(bridge)) {
drm_err(ddev, "panel-bridge endpoint %d\n", i);
ret = PTR_ERR(bridge);
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 960e83c8291da..d4c1723c5e3d8 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -1326,6 +1326,8 @@ static int sun4i_tcon_probe(struct platform_device *pdev)
ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
if (ret == -EPROBE_DEFER)
return ret;
+ if (panel)
+ drm_panel_put(panel);
}
return component_add(&pdev->dev, &sun4i_tcon_ops);
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
index 1512ee2574b66..70c14c3be10d5 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -162,6 +162,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
if (panel) {
u32 conn_type;
+ int ret;
dev_dbg(dev, "Setting up panel for port %d\n", i);
@@ -176,7 +177,8 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
break;
default:
WARN_ON(1);
- return -EINVAL;
+ ret = -EINVAL;
+ goto put_panel;
}
if (panel->connector_type != conn_type) {
@@ -184,16 +186,20 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
"%s: Panel %s has incompatible connector type for vp%d (%d != %d)\n",
__func__, dev_name(panel->dev), i,
panel->connector_type, conn_type);
- return -EINVAL;
+ ret = -EINVAL;
+ goto put_panel;
}
bridge = devm_drm_panel_bridge_add(dev, panel);
- if (IS_ERR(bridge)) {
+ ret = PTR_ERR_OR_ZERO(bridge);
+ if (ret)
dev_err(dev,
"failed to set up panel bridge for port %d\n",
i);
- return PTR_ERR(bridge);
- }
+put_panel:
+ drm_panel_put(panel);
+ if (ret)
+ return ret;
}
pipes[num_pipes].hw_videoport = i;
diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c
index 562f3f11812a3..f858c58ccb994 100644
--- a/drivers/gpu/drm/tve200/tve200_drv.c
+++ b/drivers/gpu/drm/tve200/tve200_drv.c
@@ -84,6 +84,7 @@ static int tve200_modeset_init(struct drm_device *dev)
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_Unknown);
+ drm_panel_put(panel);
if (IS_ERR(bridge)) {
ret = PTR_ERR(bridge);
goto out_bridge;
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 5/5] drm: release panel reference after panel bridge creation
2026-06-26 12:03 ` [PATCH 5/5] drm: release panel reference after panel bridge creation Albert Esteve
@ 2026-06-26 12:28 ` sashiko-bot
2026-06-26 12:59 ` Maxime Ripard
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-26 12:28 UTC (permalink / raw)
To: Albert Esteve; +Cc: linux-sunxi, dri-devel, imx, Frank.Li, wsa+renesas
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [Medium] Memory leak of DRM panel reference when multiple endpoints define panels in pl111_modeset_init().
Pre-existing issues:
- [High] Uninitialized `tmp_bridge` can be used and dereferenced if `drm_of_find_panel_or_bridge` returns `-ENODEV`.
- [High] Passing an `ERR_PTR` to `drm_panel_bridge_remove()` in `tve200_modeset_init()` error path causes a kernel panic.
- [High] Missing `IS_ERR()` check on bridge returned by `devm_drm_panel_bridge_add_typed()` leads to `ERR_PTR` dereference in `ingenic_drm_bind()`.
- [High] Overwriting `bridge->funcs` corrupts external bridges and defeats `drm_panel_bridge_remove()` cleanup in `rockchip_lvds_bind()`.
--
commit ffb6fad5e4cf513ed7c3fe05c38be4669593628f
Author: Albert Esteve <aesteve@redhat.com>
drm: release panel reference after panel bridge creation
This commit updates drivers to release the panel reference after wrapping
it in a panel bridge, as the panel bridge now holds its own reference. It
also drops unused references on error paths.
> diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> index 42c86f195c66b..1887e01d29701 100644
> --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
> @@ -1297,9 +1297,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
> goto err_drvdata;
> }
>
> - if (panel)
> + if (panel) {
> bridge = devm_drm_panel_bridge_add_typed(dev, panel,
> DRM_MODE_CONNECTOR_DPI);
> + drm_panel_put(panel);
> + }
[Severity: High]
This is a pre-existing issue, but does this missing error check lead to a
panic?
devm_drm_panel_bridge_add_typed() returns an ERR_PTR on failure. By
not checking IS_ERR(), the error pointer is stored and later passed to
drm_bridge_attach(), which will try to dereference it since it only checks
for NULL.
>
> ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
> NULL, DRM_MODE_ENCODER_DPI, NULL);
[ ... ]
> diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c
> index ac7b1d12a0f59..8ec659b3c08eb 100644
> --- a/drivers/gpu/drm/pl111/pl111_drv.c
> +++ b/drivers/gpu/drm/pl111/pl111_drv.c
> @@ -145,6 +145,7 @@ static int pl111_modeset_init(struct drm_device *dev)
> if (panel) {
> bridge = drm_panel_bridge_add_typed(panel,
> DRM_MODE_CONNECTOR_Unknown);
> + drm_panel_put(panel);
[Severity: Medium]
Could this leak panel references if multiple endpoints define panels?
Earlier in pl111_modeset_init(), it iterates through endpoints:
for_each_endpoint_of_node(np, remote) {
struct drm_panel *tmp_panel;
...
if (tmp_panel) {
...
panel = tmp_panel;
}
}
If the loop iterates multiple times and finds more than one panel, the
panel variable is overwritten without calling drm_panel_put() on the
previously found panel. Since this patch makes the panels return counted
references, does this introduce a leak for the overwritten panels?
[Severity: High]
This is a pre-existing issue, but could an uninitialized tmp_bridge cause a
crash here? In the same loop:
for_each_endpoint_of_node(np, remote) {
struct drm_panel *tmp_panel;
struct drm_bridge *tmp_bridge;
ret = drm_of_find_panel_or_bridge(..., &tmp_panel, &tmp_bridge);
If drm_of_find_panel_or_bridge() returns -ENODEV, the tmp_bridge parameter
is untouched. The code then evaluates if (tmp_bridge), which checks
uninitialized stack memory. If it evaluates to true, the garbage pointer is
assigned to the bridge variable and eventually dereferenced.
> if (IS_ERR(bridge)) {
> ret = PTR_ERR(bridge);
> goto finish;
[ ... ]
> diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> index 7a0c4fa29f2f0..f754445d2631b 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> @@ -605,6 +605,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
>
> if (lvds->panel) {
> lvds->bridge = drm_panel_bridge_add_typed(lvds->panel, DRM_MODE_CONNECTOR_LVDS);
> + drm_panel_put(lvds->panel);
> if (IS_ERR(lvds->bridge)) {
> ret = PTR_ERR(lvds->bridge);
[Severity: High]
This is a pre-existing issue, but does overwriting the bridge functions
cause memory leaks or panel failures? Later in rockchip_lvds_bind(), the
code does:
lvds->bridge->funcs = &rockchip_lvds_bridge_funcs;
Since lvds->bridge points to an external bridge or panel bridge created by
drm_panel_bridge_add_typed(), overwriting its function pointers modifies an
object owned by another driver.
Does this permanently disable the bridge's original operations? Additionally,
would drm_panel_bridge_remove() in the error path skip freeing the bridge
since it checks if the funcs pointer matches the default panel bridge
functions?
> goto err_free_encoder;
[ ... ]
> diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c
> index 562f3f11812a3..f858c58ccb994 100644
> --- a/drivers/gpu/drm/tve200/tve200_drv.c
> +++ b/drivers/gpu/drm/tve200/tve200_drv.c
> @@ -84,6 +84,7 @@ static int tve200_modeset_init(struct drm_device *dev)
> if (panel) {
> bridge = drm_panel_bridge_add_typed(panel,
> DRM_MODE_CONNECTOR_Unknown);
> + drm_panel_put(panel);
> if (IS_ERR(bridge)) {
> ret = PTR_ERR(bridge);
> goto out_bridge;
[Severity: High]
This is a pre-existing issue, but can the error path cause a panic?
If drm_panel_bridge_add_typed() fails and returns an ERR_PTR, we jump to
out_bridge:
out_bridge:
if (panel)
drm_panel_bridge_remove(bridge);
Since panel is not NULL, drm_panel_bridge_remove(bridge) is called with the
ERR_PTR. The removal function checks if (!bridge) which is false for an
ERR_PTR, and then immediately dereferences bridge->funcs.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626-drm_refcount_wiring-v1-0-cca1a7b3bdef@redhat.com?part=5
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 5/5] drm: release panel reference after panel bridge creation
2026-06-26 12:03 ` [PATCH 5/5] drm: release panel reference after panel bridge creation Albert Esteve
2026-06-26 12:28 ` sashiko-bot
@ 2026-06-26 12:59 ` Maxime Ripard
2026-06-26 15:05 ` Albert Esteve
1 sibling, 1 reply; 17+ messages in thread
From: Maxime Ripard @ 2026-06-26 12:59 UTC (permalink / raw)
To: Albert Esteve
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 1268 bytes --]
On Fri, Jun 26, 2026 at 02:03:27PM +0200, Albert Esteve wrote:
> of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
> counted reference. In drivers that immediately wrap the panel in a
> bridge via devm_drm_panel_bridge_add() or equivalent, the bridge
> acquires its own reference, so the caller's lookup reference must be
> released right afterwards.
>
> Also handle the cases where a panel is found but cannot be used,
> dropping the reference immediately in those paths.
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Albert Esteve <aesteve@redhat.com>
drm_of_find_panel_or_bridge() does indeed return a refcounted pointer
now, but afaik the doc wasn't updated to reflect that.
More importantly, I feel like with both of_drm_find_panel and
drm_of_find_panel_or_bridge we update a path that is considered legacy
anyway now, and we should rather focus on providing a safe alternative.
But none of the functions you updated are unsafe, so it won't be more
unsafe, or provide any illusion of safety to the caller. Idk.
Either way, this should all be on its way out if Luca creates a bridge
for every panel, and we'll consolidate on bridges only, so maybe it's
not such a big deal to merge this patch.
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 5/5] drm: release panel reference after panel bridge creation
2026-06-26 12:59 ` Maxime Ripard
@ 2026-06-26 15:05 ` Albert Esteve
0 siblings, 0 replies; 17+ messages in thread
From: Albert Esteve @ 2026-06-26 15:05 UTC (permalink / raw)
To: Maxime Ripard
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst,
Thomas Zimmermann, David Airlie, Simona Vetter, Andrzej Hajda,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Inki Dae, Jagan Teki, Marek Szyprowski,
Laurentiu Palcu, Lucas Stach, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Paul Cercueil,
Linus Walleij, Marek Vasut, Stefan Agner, Tomi Valkeinen,
Laurent Pinchart, Kieran Bingham, Geert Uytterhoeven, Magnus Damm,
Biju Das, Sandy Huang, Heiko Stübner, Andy Yan,
Yannick Fertre, Raphael Gallais-Pou, Philippe Cornu,
Maxime Coquelin, Alexandre Torgue, Chen-Yu Tsai, Samuel Holland,
Jyri Sarha, Jingoo Han, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Alim Akhtar, Alison Wang,
Paul Kocialkowski, Alain Volmat, Raphael Gallais-Pou,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, dri-devel,
linux-kernel, imx, linux-arm-kernel, linux-mips,
linux-renesas-soc, linux-rockchip, linux-stm32, linux-sunxi,
linux-samsung-soc, linux-tegra
On Fri, Jun 26, 2026 at 3:00 PM Maxime Ripard <mripard@kernel.org> wrote:
>
> On Fri, Jun 26, 2026 at 02:03:27PM +0200, Albert Esteve wrote:
> > of_drm_find_panel() and drm_of_find_panel_or_bridge() now return a
> > counted reference. In drivers that immediately wrap the panel in a
> > bridge via devm_drm_panel_bridge_add() or equivalent, the bridge
> > acquires its own reference, so the caller's lookup reference must be
> > released right afterwards.
> >
> > Also handle the cases where a panel is found but cannot be used,
> > dropping the reference immediately in those paths.
> >
> > Assisted-by: Claude:claude-opus-4-6
> > Signed-off-by: Albert Esteve <aesteve@redhat.com>
>
> drm_of_find_panel_or_bridge() does indeed return a refcounted pointer
> now, but afaik the doc wasn't updated to reflect that.
True, I'll fix that in the next version.
>
> More importantly, I feel like with both of_drm_find_panel and
> drm_of_find_panel_or_bridge we update a path that is considered legacy
> anyway now, and we should rather focus on providing a safe alternative.
Oh, I missed that this code path is considered legacy.
>
> But none of the functions you updated are unsafe, so it won't be more
> unsafe, or provide any illusion of safety to the caller. Idk.
>
> Either way, this should all be on its way out if Luca creates a bridge
> for every panel, and we'll consolidate on bridges only, so maybe it's
> not such a big deal to merge this patch.
I see. Given what you wrote, I think it'd make sense to correct them
while this code isn't completely dead.
BR,
Albert.
>
> Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread