* [PATCH v3 0/4] drm/panel: refcounting panel lookups and references
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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 drm_panel subsystem provides kref-based reference counting [1]
(drm_panel_get/put) but almost nothing in the tree actually uses it.
This results in a systemic use-after-free pattern throughout the codebase.
This series aims to close all those issues.
Patches 1-2: fix the infrastructure. drm_panel_add/remove now keep
a counted reference for the list entry. drm_panel_bridge_add_typed()
now holds a counted reference for the lifetime of the panel_bridge.
Patch 3: change the semantics of of_drm_find_panel(). It now acquires
a reference before returning, under panel_lock. All in-tree callers
of of_drm_find_panel() and drm_of_find_panel_or_bridge() are updated.
Two patterns are common in these fixes:
- Bridge-wrapping: the panel is passed to devm_drm_panel_bridge_add()
or equivalent, which acquires its own reference. The caller (including
devm_drm_of_get_bridge() and drmm_of_get_bridge()) releases its lookup
reference immediately after.
- Store-and-use: the panel pointer is kept in a driver struct and
used directly for the device lifetime. The reference is released in the
remove/unbind path, or via devm_add_action_or_reset() where no explicit
teardown function exists.
Patch 4: extend the same fix to find_panel_by_fwnode(), a static helper
used internally by drm_panel_add_follower(). Since it has no external
callers, the fix is self-contained: drm_panel_remove_follower() is
updated to call drm_panel_put() to balance the reference.
In order to catch all places in the tree that required releasing the
reference, the search was assisted by an AI model. Specifically, a
Coccinelle script was designed by the agent to address the trivial changes
(not included in the series). Although a few required manual fixes, with goto
labels or bracket additions. Additionally, the model helped to discern implicit
teardown paths that were addressed with devm_add_action_or_reset() calls.
Thus, these commits have the Assisted-by label following the project guidelines.
No functional change is intended for any driver. The reference
counting only affects object lifetime; panel operations are unaffected.
[1] https://lore.kernel.org/all/20250331-b4-panel-refcounting-v4-0-dad50c60c6c9@redhat.com/
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
Changes in v3:
- Squashed patches 3 and 5
- Fix probe failure paths that leaked reference
- Fix UAF and other smaller issues found by Sashiko
- Add comment in tegra-dsi explaining why panel is always
NULL in that path and so no early drm_panel_put() is required
- Link to v2: https://lore.kernel.org/r/20260713-drm_refcount_wiring-v2-0-d3bb61f4bd4d@redhat.com
Changes in v2:
- Squash of_drm_find_panel() API change with its caller fixes
- Split find_panel_by_fwnode() into its own commit
- Update kernel-doc for drm_of_find_panel_or_bridge()
- Link to v1: https://lore.kernel.org/r/20260626-drm_refcount_wiring-v1-0-cca1a7b3bdef@redhat.com
---
Albert Esteve (4):
drm/panel: have drm_panel_add/remove manage a list reference
drm/bridge/panel: hold a reference to the wrapped panel
drm/panel: of_drm_find_panel() return a counted reference
drm/panel: find_panel_by_fwnode() return a counted reference
drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 12 +++++++++++
drivers/gpu/drm/bridge/fsl-ldb.c | 1 +
drivers/gpu/drm/bridge/lontium-lt9211.c | 1 +
drivers/gpu/drm/bridge/lvds-codec.c | 1 +
drivers/gpu/drm/bridge/panel.c | 20 ++++++++++++++----
drivers/gpu/drm/bridge/samsung-dsim.c | 1 +
drivers/gpu/drm/bridge/ssd2825.c | 1 +
drivers/gpu/drm/bridge/tc358767.c | 2 ++
drivers/gpu/drm/bridge/tc358768.c | 1 +
drivers/gpu/drm/bridge/waveshare-dsi.c | 1 +
drivers/gpu/drm/drm_of.c | 3 ++-
drivers/gpu/drm/drm_panel.c | 24 +++++++++++++++++-----
drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++++++++++-
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++++
drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++-
drivers/gpu/drm/logicvc/logicvc_interface.c | 12 +++++++++++
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/analogix_dp-rockchip.c | 11 ++++++++++
drivers/gpu/drm/rockchip/rockchip_lvds.c | 4 ++++
drivers/gpu/drm/rockchip/rockchip_rgb.c | 3 +++
drivers/gpu/drm/sti/sti_dvo.c | 3 +++
drivers/gpu/drm/stm/ltdc.c | 1 +
drivers/gpu/drm/stm/lvds.c | 12 ++++++++---
drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++
drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 6 +++++-
drivers/gpu/drm/tegra/dsi.c | 5 +++++
drivers/gpu/drm/tegra/output.c | 24 +++++++++++++++++++---
drivers/gpu/drm/tidss/tidss_kms.c | 16 ++++++++++-----
drivers/gpu/drm/tve200/tve200_drv.c | 1 +
40 files changed, 225 insertions(+), 24 deletions(-)
---
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
change-id: 20260513-drm_refcount_wiring-4e5e757e9047
Best regards,
--
Albert Esteve <aesteve@redhat.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 0/4] drm/panel: refcounting panel lookups and references
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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 drm_panel subsystem provides kref-based reference counting [1]
(drm_panel_get/put) but almost nothing in the tree actually uses it.
This results in a systemic use-after-free pattern throughout the codebase.
This series aims to close all those issues.
Patches 1-2: fix the infrastructure. drm_panel_add/remove now keep
a counted reference for the list entry. drm_panel_bridge_add_typed()
now holds a counted reference for the lifetime of the panel_bridge.
Patch 3: change the semantics of of_drm_find_panel(). It now acquires
a reference before returning, under panel_lock. All in-tree callers
of of_drm_find_panel() and drm_of_find_panel_or_bridge() are updated.
Two patterns are common in these fixes:
- Bridge-wrapping: the panel is passed to devm_drm_panel_bridge_add()
or equivalent, which acquires its own reference. The caller (including
devm_drm_of_get_bridge() and drmm_of_get_bridge()) releases its lookup
reference immediately after.
- Store-and-use: the panel pointer is kept in a driver struct and
used directly for the device lifetime. The reference is released in the
remove/unbind path, or via devm_add_action_or_reset() where no explicit
teardown function exists.
Patch 4: extend the same fix to find_panel_by_fwnode(), a static helper
used internally by drm_panel_add_follower(). Since it has no external
callers, the fix is self-contained: drm_panel_remove_follower() is
updated to call drm_panel_put() to balance the reference.
In order to catch all places in the tree that required releasing the
reference, the search was assisted by an AI model. Specifically, a
Coccinelle script was designed by the agent to address the trivial changes
(not included in the series). Although a few required manual fixes, with goto
labels or bracket additions. Additionally, the model helped to discern implicit
teardown paths that were addressed with devm_add_action_or_reset() calls.
Thus, these commits have the Assisted-by label following the project guidelines.
No functional change is intended for any driver. The reference
counting only affects object lifetime; panel operations are unaffected.
[1] https://lore.kernel.org/all/20250331-b4-panel-refcounting-v4-0-dad50c60c6c9@redhat.com/
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
Changes in v3:
- Squashed patches 3 and 5
- Fix probe failure paths that leaked reference
- Fix UAF and other smaller issues found by Sashiko
- Add comment in tegra-dsi explaining why panel is always
NULL in that path and so no early drm_panel_put() is required
- Link to v2: https://lore.kernel.org/r/20260713-drm_refcount_wiring-v2-0-d3bb61f4bd4d@redhat.com
Changes in v2:
- Squash of_drm_find_panel() API change with its caller fixes
- Split find_panel_by_fwnode() into its own commit
- Update kernel-doc for drm_of_find_panel_or_bridge()
- Link to v1: https://lore.kernel.org/r/20260626-drm_refcount_wiring-v1-0-cca1a7b3bdef@redhat.com
---
Albert Esteve (4):
drm/panel: have drm_panel_add/remove manage a list reference
drm/bridge/panel: hold a reference to the wrapped panel
drm/panel: of_drm_find_panel() return a counted reference
drm/panel: find_panel_by_fwnode() return a counted reference
drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 12 +++++++++++
drivers/gpu/drm/bridge/fsl-ldb.c | 1 +
drivers/gpu/drm/bridge/lontium-lt9211.c | 1 +
drivers/gpu/drm/bridge/lvds-codec.c | 1 +
drivers/gpu/drm/bridge/panel.c | 20 ++++++++++++++----
drivers/gpu/drm/bridge/samsung-dsim.c | 1 +
drivers/gpu/drm/bridge/ssd2825.c | 1 +
drivers/gpu/drm/bridge/tc358767.c | 2 ++
drivers/gpu/drm/bridge/tc358768.c | 1 +
drivers/gpu/drm/bridge/waveshare-dsi.c | 1 +
drivers/gpu/drm/drm_of.c | 3 ++-
drivers/gpu/drm/drm_panel.c | 24 +++++++++++++++++-----
drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++++++++++-
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++++
drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++-
drivers/gpu/drm/logicvc/logicvc_interface.c | 12 +++++++++++
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/analogix_dp-rockchip.c | 11 ++++++++++
drivers/gpu/drm/rockchip/rockchip_lvds.c | 4 ++++
drivers/gpu/drm/rockchip/rockchip_rgb.c | 3 +++
drivers/gpu/drm/sti/sti_dvo.c | 3 +++
drivers/gpu/drm/stm/ltdc.c | 1 +
drivers/gpu/drm/stm/lvds.c | 12 ++++++++---
drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++
drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 6 +++++-
drivers/gpu/drm/tegra/dsi.c | 5 +++++
drivers/gpu/drm/tegra/output.c | 24 +++++++++++++++++++---
drivers/gpu/drm/tidss/tidss_kms.c | 16 ++++++++++-----
drivers/gpu/drm/tve200/tve200_drv.c | 1 +
40 files changed, 225 insertions(+), 24 deletions(-)
---
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
change-id: 20260513-drm_refcount_wiring-4e5e757e9047
Best regards,
--
Albert Esteve <aesteve@redhat.com>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 1/4] drm/panel: have drm_panel_add/remove manage a list reference
2026-07-17 14:02 ` Albert Esteve
@ 2026-07-17 14:02 ` Albert Esteve
-1 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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.
Reviewed-by: Maxime Ripard <mripard@kernel.org>
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.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 1/4] drm/panel: have drm_panel_add/remove manage a list reference
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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.
Reviewed-by: Maxime Ripard <mripard@kernel.org>
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.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/4] drm/bridge/panel: hold a reference to the wrapped panel
2026-07-17 14:02 ` Albert Esteve
@ 2026-07-17 14:02 ` Albert Esteve
-1 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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.
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/panel.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 4978ec98a0828..3bfa18167dd26 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,16 @@ 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;
+ struct drm_panel *panel;
if (!bridge)
return;
+ panel_bridge = drm_bridge_to_panel_bridge(bridge);
+ panel = panel_bridge->panel;
drm_bridge_remove(bridge);
+ drm_panel_put(panel);
}
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/4] drm/bridge/panel: hold a reference to the wrapped panel
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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.
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/panel.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 4978ec98a0828..3bfa18167dd26 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,16 @@ 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;
+ struct drm_panel *panel;
if (!bridge)
return;
+ panel_bridge = drm_bridge_to_panel_bridge(bridge);
+ panel = panel_bridge->panel;
drm_bridge_remove(bridge);
+ drm_panel_put(panel);
}
/**
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/4] drm/panel: of_drm_find_panel() return a counted reference
2026-07-17 14:02 ` Albert Esteve
@ 2026-07-17 14:02 ` Albert Esteve
-1 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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() and drm_of_find_panel_or_bridge()
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()).
Fix the lookup function 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.
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, update a second batch of drivers calling
of_drm_find_panel() or drm_of_find_panel_or_bridge() to release the
lookup reference after wrapping the panel in a bridge, and 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
Acked-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 12 +++++++++++
drivers/gpu/drm/bridge/fsl-ldb.c | 1 +
drivers/gpu/drm/bridge/lontium-lt9211.c | 1 +
drivers/gpu/drm/bridge/lvds-codec.c | 1 +
drivers/gpu/drm/bridge/panel.c | 8 ++++++--
drivers/gpu/drm/bridge/samsung-dsim.c | 1 +
drivers/gpu/drm/bridge/ssd2825.c | 1 +
drivers/gpu/drm/bridge/tc358767.c | 2 ++
drivers/gpu/drm/bridge/tc358768.c | 1 +
drivers/gpu/drm/bridge/waveshare-dsi.c | 1 +
drivers/gpu/drm/drm_of.c | 3 ++-
drivers/gpu/drm/drm_panel.c | 12 +++++++----
drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++++++++++-
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++++
drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++-
drivers/gpu/drm/logicvc/logicvc_interface.c | 12 +++++++++++
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/analogix_dp-rockchip.c | 11 ++++++++++
drivers/gpu/drm/rockchip/rockchip_lvds.c | 4 ++++
drivers/gpu/drm/rockchip/rockchip_rgb.c | 3 +++
drivers/gpu/drm/sti/sti_dvo.c | 3 +++
drivers/gpu/drm/stm/ltdc.c | 1 +
drivers/gpu/drm/stm/lvds.c | 12 ++++++++---
drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++
drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 6 +++++-
drivers/gpu/drm/tegra/dsi.c | 5 +++++
drivers/gpu/drm/tegra/output.c | 24 +++++++++++++++++++---
drivers/gpu/drm/tidss/tidss_kms.c | 16 ++++++++++-----
drivers/gpu/drm/tve200/tve200_drv.c | 1 +
40 files changed, 204 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
index f3fe47b12edca..7330ccb2c74cf 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
@@ -658,6 +658,11 @@ static bool anx6345_get_chip_id(struct anx6345 *anx6345)
return false;
}
+static void anx6345_panel_put_action(void *data)
+{
+ drm_panel_put(data);
+}
+
static int anx6345_i2c_probe(struct i2c_client *client)
{
struct anx6345 *anx6345;
@@ -686,6 +691,13 @@ static int anx6345_i2c_probe(struct i2c_client *client)
if (err)
DRM_DEBUG("No panel found\n");
+ if (anx6345->panel) {
+ err = devm_add_action_or_reset(dev, anx6345_panel_put_action,
+ anx6345->panel);
+ if (err)
+ return err;
+ }
+
/* 1.2V digital core power regulator */
anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12");
if (IS_ERR(anx6345->dvdd12)) {
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
index 9bfaa3f933709..27d7ba252927b 100644
--- a/drivers/gpu/drm/bridge/fsl-ldb.c
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -348,6 +348,7 @@ static int fsl_ldb_probe(struct platform_device *pdev)
fsl_ldb->use_termination_resistor = true;
fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(fsl_ldb->panel_bridge))
return PTR_ERR(fsl_ldb->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
index 870175a848390..ae4aeba032a33 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9211.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
@@ -660,6 +660,7 @@ static int lt9211_parse_dt(struct lt9211 *ctx)
return ret;
if (panel) {
panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
}
diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c
index e6a7147e141b6..72ac427e7b88b 100644
--- a/drivers/gpu/drm/bridge/lvds-codec.c
+++ b/drivers/gpu/drm/bridge/lvds-codec.c
@@ -154,6 +154,7 @@ static int lvds_codec_probe(struct platform_device *pdev)
lvds_codec->panel_bridge =
devm_drm_panel_bridge_add_typed(dev, panel,
lvds_codec->connector_type);
+ drm_panel_put(panel);
if (IS_ERR(lvds_codec->panel_bridge))
return PTR_ERR(lvds_codec->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 3bfa18167dd26..218445528a57a 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -515,8 +515,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;
}
@@ -549,8 +551,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/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c
index 9ee0515074c78..0cd2a7e195061 100644
--- a/drivers/gpu/drm/bridge/samsung-dsim.c
+++ b/drivers/gpu/drm/bridge/samsung-dsim.c
@@ -1934,6 +1934,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
panel = of_drm_find_panel(remote);
if (!IS_ERR(panel)) {
next_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(next_bridge)) {
ret = PTR_ERR(next_bridge);
next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR
diff --git a/drivers/gpu/drm/bridge/ssd2825.c b/drivers/gpu/drm/bridge/ssd2825.c
index 54ca6bd6883ae..ac6d9b9d1f9e1 100644
--- a/drivers/gpu/drm/bridge/ssd2825.c
+++ b/drivers/gpu/drm/bridge/ssd2825.c
@@ -301,6 +301,7 @@ static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_d
if (panel) {
bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index 7188935fdb826..29c0a58b907cc 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -2329,6 +2329,7 @@ static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc)
if (panel) {
bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
@@ -2359,6 +2360,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc)
struct drm_bridge *panel_bridge;
panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c
index 0f2820b373029..560cf2ab12079 100644
--- a/drivers/gpu/drm/bridge/tc358768.c
+++ b/drivers/gpu/drm/bridge/tc358768.c
@@ -479,6 +479,7 @@ static int tc358768_dsi_host_attach(struct mipi_dsi_host *host,
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_DSI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c
index ded57f298d640..650d9be7ae661 100644
--- a/drivers/gpu/drm/bridge/waveshare-dsi.c
+++ b/drivers/gpu/drm/bridge/waveshare-dsi.c
@@ -163,6 +163,7 @@ static int ws_bridge_probe(struct i2c_client *i2c)
return dev_err_probe(dev, ret, "Failed to find remote panel\n");
ws->next_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(ws->next_bridge))
return PTR_ERR(ws->next_bridge);
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
index d03ada82eac96..1873a84514a68 100644
--- a/drivers/gpu/drm/drm_of.c
+++ b/drivers/gpu/drm/drm_of.c
@@ -225,7 +225,8 @@ EXPORT_SYMBOL_GPL(drm_of_encoder_active_endpoint);
* @np: device tree node containing encoder output ports
* @port: port in the device tree node
* @endpoint: endpoint in the device tree node
- * @panel: pointer to hold returned drm_panel, must not be NULL
+ * @panel: pointer to hold returned drm_panel, must not be NULL. On success
+ * the caller must call drm_panel_put() when done with the panel
* @bridge: pointer to hold returned drm_bridge
*
* Given a DT node's port and endpoint number, find the connected node and
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 545fe93dc28fe..72cf86253c0cd 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;
}
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b805403281504..a418011f7d4d6 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -182,8 +182,15 @@ static int exynos_dp_probe(struct platform_device *pdev)
out:
dp->adp = analogix_dp_probe(dev, &dp->plat_data);
- if (IS_ERR(dp->adp))
+ if (IS_ERR(dp->adp)) {
+ /*
+ * The driver core does not invoke remove() for failed probes,
+ * so release the probe-time panel reference here.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
return PTR_ERR(dp->adp);
+ }
if (dp->plat_data.panel || dp->plat_data.next_bridge)
return component_add(&pdev->dev, &exynos_dp_ops);
@@ -193,6 +200,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/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/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/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 9b8fbda85d28a..7cb19c15bf39e 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -127,6 +127,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/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/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 7a0c4fa29f2f0..d99dc708572e1 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -605,6 +605,8 @@ 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);
+ lvds->panel = NULL;
if (IS_ERR(lvds->bridge)) {
ret = PTR_ERR(lvds->bridge);
goto err_free_encoder;
@@ -641,6 +643,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
err_free_encoder:
drm_encoder_cleanup(encoder);
err_put_remote:
+ if (lvds->panel)
+ drm_panel_put(lvds->panel);
of_node_put(remote);
err_put_port:
of_node_put(port);
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
index add3123e5ce70..c908ea4cfa67a 100644
--- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
@@ -131,6 +131,8 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
if (ret < 0) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize encoder: %d\n", ret);
+ if (panel)
+ drm_panel_put(panel);
return ERR_PTR(ret);
}
@@ -139,6 +141,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/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/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/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c
index 50a878688e477..f887a708afb73 100644
--- a/drivers/gpu/drm/stm/lvds.c
+++ b/drivers/gpu/drm/stm/lvds.c
@@ -1068,20 +1068,20 @@ static int lvds_probe(struct platform_device *pdev)
if (IS_ERR(lvds->base)) {
ret = PTR_ERR(lvds->base);
dev_err(dev, "Unable to get regs %d\n", ret);
- return ret;
+ goto err_put_panel;
}
lvds->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(lvds->pclk)) {
ret = PTR_ERR(lvds->pclk);
dev_err(dev, "Unable to get peripheral clock: %d\n", ret);
- return ret;
+ goto err_put_panel;
}
ret = clk_prepare_enable(lvds->pclk);
if (ret) {
dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__);
- return ret;
+ goto err_put_panel;
}
rstc = devm_reset_control_get_exclusive(dev, NULL);
@@ -1181,6 +1181,9 @@ static int lvds_probe(struct platform_device *pdev)
err_lvds_probe:
clk_disable_unprepare(lvds->pclk);
+err_put_panel:
+ if (lvds->panel)
+ drm_panel_put(lvds->panel);
return ret;
}
@@ -1189,6 +1192,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/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/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index c35b70d83e53b..09c2a48b59128 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
@@ -967,8 +967,10 @@ static int sun6i_dsi_attach(struct mipi_dsi_host *host,
if (IS_ERR(panel))
return PTR_ERR(panel);
- if (!dsi->drm || !dsi->drm->registered)
+ if (!dsi->drm || !dsi->drm->registered) {
+ drm_panel_put(panel);
return -EPROBE_DEFER;
+ }
dsi->panel = panel;
dsi->device = device;
@@ -985,6 +987,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..500d7afd86db1 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -1498,6 +1498,10 @@ static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
if (!dsi->master) {
struct tegra_output *output = &dsi->output;
+ /*
+ * tegra_output_probe() never populates a panel for DSI
+ * outputs, so output->panel is always NULL here.
+ */
output->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(output->panel))
output->panel = NULL;
@@ -1516,6 +1520,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..d0ba3bd0aab8f 100644
--- a/drivers/gpu/drm/tegra/output.c
+++ b/drivers/gpu/drm/tegra/output.c
@@ -117,11 +117,19 @@ int tegra_output_probe(struct tegra_output *output)
*/
WARN_ON(output->panel || output->bridge);
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
output->panel = of_drm_find_panel(panel);
of_node_put(panel);
- if (IS_ERR(output->panel))
- return PTR_ERR(output->panel);
+ if (IS_ERR(output->panel)) {
+ err = PTR_ERR(output->panel);
+ output->panel = NULL;
+ return err;
+ }
}
ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
@@ -131,7 +139,7 @@ int tegra_output_probe(struct tegra_output *output)
if (!output->ddc) {
err = -EPROBE_DEFER;
- return err;
+ goto put_i2c;
}
}
@@ -185,6 +193,11 @@ int tegra_output_probe(struct tegra_output *output)
return 0;
put_i2c:
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
if (output->ddc)
i2c_put_adapter(output->ddc);
@@ -195,6 +208,11 @@ int tegra_output_probe(struct tegra_output *output)
void tegra_output_remove(struct tegra_output *output)
{
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
if (output->hpd_gpio)
free_irq(output->hpd_irq, output);
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.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/4] drm/panel: of_drm_find_panel() return a counted reference
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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() and drm_of_find_panel_or_bridge()
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()).
Fix the lookup function 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.
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, update a second batch of drivers calling
of_drm_find_panel() or drm_of_find_panel_or_bridge() to release the
lookup reference after wrapping the panel in a bridge, and 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
Acked-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 12 +++++++++++
drivers/gpu/drm/bridge/fsl-ldb.c | 1 +
drivers/gpu/drm/bridge/lontium-lt9211.c | 1 +
drivers/gpu/drm/bridge/lvds-codec.c | 1 +
drivers/gpu/drm/bridge/panel.c | 8 ++++++--
drivers/gpu/drm/bridge/samsung-dsim.c | 1 +
drivers/gpu/drm/bridge/ssd2825.c | 1 +
drivers/gpu/drm/bridge/tc358767.c | 2 ++
drivers/gpu/drm/bridge/tc358768.c | 1 +
drivers/gpu/drm/bridge/waveshare-dsi.c | 1 +
drivers/gpu/drm/drm_of.c | 3 ++-
drivers/gpu/drm/drm_panel.c | 12 +++++++----
drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++++++++++-
drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++
drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++++
drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++-
drivers/gpu/drm/logicvc/logicvc_interface.c | 12 +++++++++++
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/analogix_dp-rockchip.c | 11 ++++++++++
drivers/gpu/drm/rockchip/rockchip_lvds.c | 4 ++++
drivers/gpu/drm/rockchip/rockchip_rgb.c | 3 +++
drivers/gpu/drm/sti/sti_dvo.c | 3 +++
drivers/gpu/drm/stm/ltdc.c | 1 +
drivers/gpu/drm/stm/lvds.c | 12 ++++++++---
drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 ++++++++++++
drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++
drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 6 +++++-
drivers/gpu/drm/tegra/dsi.c | 5 +++++
drivers/gpu/drm/tegra/output.c | 24 +++++++++++++++++++---
drivers/gpu/drm/tidss/tidss_kms.c | 16 ++++++++++-----
drivers/gpu/drm/tve200/tve200_drv.c | 1 +
40 files changed, 204 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
index f3fe47b12edca..7330ccb2c74cf 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
@@ -658,6 +658,11 @@ static bool anx6345_get_chip_id(struct anx6345 *anx6345)
return false;
}
+static void anx6345_panel_put_action(void *data)
+{
+ drm_panel_put(data);
+}
+
static int anx6345_i2c_probe(struct i2c_client *client)
{
struct anx6345 *anx6345;
@@ -686,6 +691,13 @@ static int anx6345_i2c_probe(struct i2c_client *client)
if (err)
DRM_DEBUG("No panel found\n");
+ if (anx6345->panel) {
+ err = devm_add_action_or_reset(dev, anx6345_panel_put_action,
+ anx6345->panel);
+ if (err)
+ return err;
+ }
+
/* 1.2V digital core power regulator */
anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12");
if (IS_ERR(anx6345->dvdd12)) {
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
index 9bfaa3f933709..27d7ba252927b 100644
--- a/drivers/gpu/drm/bridge/fsl-ldb.c
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -348,6 +348,7 @@ static int fsl_ldb_probe(struct platform_device *pdev)
fsl_ldb->use_termination_resistor = true;
fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(fsl_ldb->panel_bridge))
return PTR_ERR(fsl_ldb->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
index 870175a848390..ae4aeba032a33 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9211.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
@@ -660,6 +660,7 @@ static int lt9211_parse_dt(struct lt9211 *ctx)
return ret;
if (panel) {
panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
}
diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c
index e6a7147e141b6..72ac427e7b88b 100644
--- a/drivers/gpu/drm/bridge/lvds-codec.c
+++ b/drivers/gpu/drm/bridge/lvds-codec.c
@@ -154,6 +154,7 @@ static int lvds_codec_probe(struct platform_device *pdev)
lvds_codec->panel_bridge =
devm_drm_panel_bridge_add_typed(dev, panel,
lvds_codec->connector_type);
+ drm_panel_put(panel);
if (IS_ERR(lvds_codec->panel_bridge))
return PTR_ERR(lvds_codec->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 3bfa18167dd26..218445528a57a 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -515,8 +515,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;
}
@@ -549,8 +551,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/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c
index 9ee0515074c78..0cd2a7e195061 100644
--- a/drivers/gpu/drm/bridge/samsung-dsim.c
+++ b/drivers/gpu/drm/bridge/samsung-dsim.c
@@ -1934,6 +1934,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
panel = of_drm_find_panel(remote);
if (!IS_ERR(panel)) {
next_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(next_bridge)) {
ret = PTR_ERR(next_bridge);
next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR
diff --git a/drivers/gpu/drm/bridge/ssd2825.c b/drivers/gpu/drm/bridge/ssd2825.c
index 54ca6bd6883ae..ac6d9b9d1f9e1 100644
--- a/drivers/gpu/drm/bridge/ssd2825.c
+++ b/drivers/gpu/drm/bridge/ssd2825.c
@@ -301,6 +301,7 @@ static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_d
if (panel) {
bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index 7188935fdb826..29c0a58b907cc 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -2329,6 +2329,7 @@ static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc)
if (panel) {
bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
@@ -2359,6 +2360,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc)
struct drm_bridge *panel_bridge;
panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c
index 0f2820b373029..560cf2ab12079 100644
--- a/drivers/gpu/drm/bridge/tc358768.c
+++ b/drivers/gpu/drm/bridge/tc358768.c
@@ -479,6 +479,7 @@ static int tc358768_dsi_host_attach(struct mipi_dsi_host *host,
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_DSI);
+ drm_panel_put(panel);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c
index ded57f298d640..650d9be7ae661 100644
--- a/drivers/gpu/drm/bridge/waveshare-dsi.c
+++ b/drivers/gpu/drm/bridge/waveshare-dsi.c
@@ -163,6 +163,7 @@ static int ws_bridge_probe(struct i2c_client *i2c)
return dev_err_probe(dev, ret, "Failed to find remote panel\n");
ws->next_bridge = devm_drm_panel_bridge_add(dev, panel);
+ drm_panel_put(panel);
if (IS_ERR(ws->next_bridge))
return PTR_ERR(ws->next_bridge);
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
index d03ada82eac96..1873a84514a68 100644
--- a/drivers/gpu/drm/drm_of.c
+++ b/drivers/gpu/drm/drm_of.c
@@ -225,7 +225,8 @@ EXPORT_SYMBOL_GPL(drm_of_encoder_active_endpoint);
* @np: device tree node containing encoder output ports
* @port: port in the device tree node
* @endpoint: endpoint in the device tree node
- * @panel: pointer to hold returned drm_panel, must not be NULL
+ * @panel: pointer to hold returned drm_panel, must not be NULL. On success
+ * the caller must call drm_panel_put() when done with the panel
* @bridge: pointer to hold returned drm_bridge
*
* Given a DT node's port and endpoint number, find the connected node and
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 545fe93dc28fe..72cf86253c0cd 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;
}
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b805403281504..a418011f7d4d6 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -182,8 +182,15 @@ static int exynos_dp_probe(struct platform_device *pdev)
out:
dp->adp = analogix_dp_probe(dev, &dp->plat_data);
- if (IS_ERR(dp->adp))
+ if (IS_ERR(dp->adp)) {
+ /*
+ * The driver core does not invoke remove() for failed probes,
+ * so release the probe-time panel reference here.
+ */
+ if (dp->plat_data.panel)
+ drm_panel_put(dp->plat_data.panel);
return PTR_ERR(dp->adp);
+ }
if (dp->plat_data.panel || dp->plat_data.next_bridge)
return component_add(&pdev->dev, &exynos_dp_ops);
@@ -193,6 +200,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/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/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/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 9b8fbda85d28a..7cb19c15bf39e 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -127,6 +127,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/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/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 7a0c4fa29f2f0..d99dc708572e1 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -605,6 +605,8 @@ 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);
+ lvds->panel = NULL;
if (IS_ERR(lvds->bridge)) {
ret = PTR_ERR(lvds->bridge);
goto err_free_encoder;
@@ -641,6 +643,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
err_free_encoder:
drm_encoder_cleanup(encoder);
err_put_remote:
+ if (lvds->panel)
+ drm_panel_put(lvds->panel);
of_node_put(remote);
err_put_port:
of_node_put(port);
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
index add3123e5ce70..c908ea4cfa67a 100644
--- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
@@ -131,6 +131,8 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
if (ret < 0) {
DRM_DEV_ERROR(drm_dev->dev,
"failed to initialize encoder: %d\n", ret);
+ if (panel)
+ drm_panel_put(panel);
return ERR_PTR(ret);
}
@@ -139,6 +141,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/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/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/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c
index 50a878688e477..f887a708afb73 100644
--- a/drivers/gpu/drm/stm/lvds.c
+++ b/drivers/gpu/drm/stm/lvds.c
@@ -1068,20 +1068,20 @@ static int lvds_probe(struct platform_device *pdev)
if (IS_ERR(lvds->base)) {
ret = PTR_ERR(lvds->base);
dev_err(dev, "Unable to get regs %d\n", ret);
- return ret;
+ goto err_put_panel;
}
lvds->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(lvds->pclk)) {
ret = PTR_ERR(lvds->pclk);
dev_err(dev, "Unable to get peripheral clock: %d\n", ret);
- return ret;
+ goto err_put_panel;
}
ret = clk_prepare_enable(lvds->pclk);
if (ret) {
dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__);
- return ret;
+ goto err_put_panel;
}
rstc = devm_reset_control_get_exclusive(dev, NULL);
@@ -1181,6 +1181,9 @@ static int lvds_probe(struct platform_device *pdev)
err_lvds_probe:
clk_disable_unprepare(lvds->pclk);
+err_put_panel:
+ if (lvds->panel)
+ drm_panel_put(lvds->panel);
return ret;
}
@@ -1189,6 +1192,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/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/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index c35b70d83e53b..09c2a48b59128 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
@@ -967,8 +967,10 @@ static int sun6i_dsi_attach(struct mipi_dsi_host *host,
if (IS_ERR(panel))
return PTR_ERR(panel);
- if (!dsi->drm || !dsi->drm->registered)
+ if (!dsi->drm || !dsi->drm->registered) {
+ drm_panel_put(panel);
return -EPROBE_DEFER;
+ }
dsi->panel = panel;
dsi->device = device;
@@ -985,6 +987,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..500d7afd86db1 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -1498,6 +1498,10 @@ static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
if (!dsi->master) {
struct tegra_output *output = &dsi->output;
+ /*
+ * tegra_output_probe() never populates a panel for DSI
+ * outputs, so output->panel is always NULL here.
+ */
output->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(output->panel))
output->panel = NULL;
@@ -1516,6 +1520,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..d0ba3bd0aab8f 100644
--- a/drivers/gpu/drm/tegra/output.c
+++ b/drivers/gpu/drm/tegra/output.c
@@ -117,11 +117,19 @@ int tegra_output_probe(struct tegra_output *output)
*/
WARN_ON(output->panel || output->bridge);
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
output->panel = of_drm_find_panel(panel);
of_node_put(panel);
- if (IS_ERR(output->panel))
- return PTR_ERR(output->panel);
+ if (IS_ERR(output->panel)) {
+ err = PTR_ERR(output->panel);
+ output->panel = NULL;
+ return err;
+ }
}
ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
@@ -131,7 +139,7 @@ int tegra_output_probe(struct tegra_output *output)
if (!output->ddc) {
err = -EPROBE_DEFER;
- return err;
+ goto put_i2c;
}
}
@@ -185,6 +193,11 @@ int tegra_output_probe(struct tegra_output *output)
return 0;
put_i2c:
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
if (output->ddc)
i2c_put_adapter(output->ddc);
@@ -195,6 +208,11 @@ int tegra_output_probe(struct tegra_output *output)
void tegra_output_remove(struct tegra_output *output)
{
+ if (output->panel) {
+ drm_panel_put(output->panel);
+ output->panel = NULL;
+ }
+
if (output->hpd_gpio)
free_irq(output->hpd_irq, output);
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.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 4/4] drm/panel: find_panel_by_fwnode() return a counted reference
2026-07-17 14:02 ` Albert Esteve
@ 2026-07-17 14:02 ` Albert Esteve
-1 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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
find_panel_by_fwnode() is the fwnode-based counterpart to
of_drm_find_panel(), used internally by drm_panel_add_follower().
Like of_drm_find_panel(), it returned an unrefcounted pointer,
leaving a window where the panel could be freed between the lookup
and first use.
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.
Apply the same fix: call drm_panel_get() under panel_lock before
returning. Since find_panel_by_fwnode() now transfers a counted
reference to drm_panel_add_follower(), drm_panel_remove_follower()
must balance it with a matching drm_panel_put().
Acked-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/drm_panel.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 72cf86253c0cd..a00ae98ed0956 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -542,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;
@@ -554,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;
}
@@ -690,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.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 4/4] drm/panel: find_panel_by_fwnode() return a counted reference
@ 2026-07-17 14:02 ` Albert Esteve
0 siblings, 0 replies; 14+ messages in thread
From: Albert Esteve @ 2026-07-17 14:02 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
find_panel_by_fwnode() is the fwnode-based counterpart to
of_drm_find_panel(), used internally by drm_panel_add_follower().
Like of_drm_find_panel(), it returned an unrefcounted pointer,
leaving a window where the panel could be freed between the lookup
and first use.
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.
Apply the same fix: call drm_panel_get() under panel_lock before
returning. Since find_panel_by_fwnode() now transfers a counted
reference to drm_panel_add_follower(), drm_panel_remove_follower()
must balance it with a matching drm_panel_put().
Acked-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
---
drivers/gpu/drm/drm_panel.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 72cf86253c0cd..a00ae98ed0956 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -542,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;
@@ -554,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;
}
@@ -690,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.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 4/4] drm/panel: find_panel_by_fwnode() return a counted reference
2026-07-17 14:02 ` Albert Esteve
(?)
@ 2026-07-17 14:14 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-17 14:14 UTC (permalink / raw)
To: Albert Esteve; +Cc: Frank.Li, dri-devel, imx, linux-sunxi, wsa+renesas
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] TOCTOU race condition in `drm_panel_add_follower` leading to Use-After-Free of `panel->dev`.
--
--- Patch [4]: [PATCH v3 4/4] drm/panel: find_panel_by_fwnode() return a counted reference ---
commit 2c073e53f181794d94244eff0882b1f1a661d995
Author: Albert Esteve <aesteve@redhat.com>
drm/panel: find_panel_by_fwnode() return a counted reference
This patch updates find_panel_by_fwnode() to return a reference-counted
drm_panel pointer, preventing the panel's container memory from being
released prematurely. It also updates drm_panel_remove_follower() to
balance the reference with drm_panel_put().
> diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
> index 72cf86253c0cd..a00ae98ed0956 100644
> --- a/drivers/gpu/drm/drm_panel.c
> +++ b/drivers/gpu/drm/drm_panel.c
[ ... ]
> @@ -554,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);
[Severity: High]
This is a pre-existing issue, but does releasing the panel_lock here leave
a TOCTOU race window where panel->dev can be freed before it's used by the
caller?
If the panel's device is unregistered concurrently (e.g., via sysfs unbind
of its parent), the following sequence seems possible:
CPU1 in find_panel_by_fwnode():
drm_panel_get(panel);
mutex_unlock(&panel_lock);
return panel;
CPU2 concurrently unregisters the device:
drm_panel_remove() runs, removing the panel from panel_list.
The device's reference count drops to zero and the struct device behind
panel->dev is freed.
CPU1 in drm_panel_add_follower():
get_device(panel->dev);
Since panel->dev now points to a freed device, could this cause a
use-after-free?
> return panel;
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-drm_refcount_wiring-v3-0-023900c32e01@redhat.com?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/4] drm/bridge/panel: hold a reference to the wrapped panel
2026-07-17 14:02 ` Albert Esteve
(?)
@ 2026-07-17 14:15 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-17 14:15 UTC (permalink / raw)
To: Albert Esteve; +Cc: imx, wsa+renesas, Frank.Li, linux-sunxi, dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Releasing the panel reference in the unregister paths instead of a .destroy callback introduces a Use-After-Free.
Pre-existing issues:
- [High] Accessing `panel->dev` in `drm_panel_bridge_remove()` can cause a Use-After-Free of a `struct device` if the panel device has been unregistered.
--
--- Patch [2]: [PATCH v3 2/4] drm/bridge/panel: hold a reference to the wrapped panel ---
commit ac2f873b583c71488e3debe5277a8e3347eee2cd
Author: Albert Esteve <aesteve@redhat.com>
drm/bridge/panel: hold a reference to the wrapped panel
This commit acquires a reference to the drm_panel in
drm_panel_bridge_add_typed() to prevent dangling pointers. It then
releases the reference in each teardown path.
> diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
> index 4978ec98a0828..3bfa18167dd26 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 is a pre-existing issue, but could accessing panel->dev here lead to a
use-after-free if the panel device has already been unregistered?
While drm_panel_get() keeps the drm_panel alive, it doesn't appear to acquire
a reference to the underlying struct device. If the device is unbound and
freed, panel->dev becomes a dangling pointer.
Could devm_drm_put_bridge() then dereference it when traversing the devres
list?
> + drm_panel_put(panel);
[Severity: High]
Does this drop the panel reference prematurely and cause a regression?
Since drm_bridge objects are refcounted, calling drm_bridge_remove() only
unregisters the bridge and drops the list reference. The bridge could remain
alive if an active DRM pipeline holds a reference.
If the panel is freed here, could subsequent DRM operations on the bridge
dereference panel_bridge->panel and trigger a use-after-free?
Would it be safer to move the drm_panel_put() to a new .destroy callback in
panel_bridge_bridge_funcs, which is invoked by __drm_bridge_free() when the
bridge's refcount reaches zero?
> }
> EXPORT_SYMBOL(drm_panel_bridge_remove);
>
> @@ -357,11 +360,16 @@ 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;
> + struct drm_panel *panel;
>
> if (!bridge)
> return;
>
> + panel_bridge = drm_bridge_to_panel_bridge(bridge);
> + panel = panel_bridge->panel;
> drm_bridge_remove(bridge);
> + drm_panel_put(panel);
[Severity: High]
Similarly, if the bridge is kept alive by a reference elsewhere, does dropping
the panel reference here risk a use-after-free regression in bridge callbacks
later on?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-drm_refcount_wiring-v3-0-023900c32e01@redhat.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/4] drm/panel: of_drm_find_panel() return a counted reference
2026-07-17 14:02 ` Albert Esteve
(?)
@ 2026-07-17 14:24 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-17 14:24 UTC (permalink / raw)
To: Albert Esteve; +Cc: linux-sunxi, Frank.Li, wsa+renesas, dri-devel, imx
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
New issues:
- [Critical] Using `devm_add_action_or_reset()` to manage panel references inside component bind callbacks causes reference leaks, devres accumulation, and double-free on rebind.
- [High] Panel reference acquired in `tegra_output_probe()` is leaked in `tegra_dc_rgb_init()`.
- [High] Panel reference is leaked if `component_add()` or `analogix_dp_finish_probe()` fails in `exynos_dp_probe()`.
- [High] Panel reference is leaked if `component_add()` fails in `analogix_dp_aux_done_probing()`.
- [High] Panel reference is leaked if `component_add()` fails in Exynos FIMD / DECON probe error paths.
- [Medium] Panel reference is leaked when looping over multiple endpoints in `pl111_modeset_init()`.
Pre-existing issues:
- [High] An `ERR_PTR` is passed to `drm_panel_bridge_remove()` in the error path of `tve200_modeset_init()`, causing a kernel panic.
- [High] The panel bridge, encoder, and internal panel reference are leaked if encoder attachment fails in `rockchip_rgb_init()`.
- [High] Dereferencing `ERR_PTR` causes a kernel panic in the Ingenic DRM driver.
--
--- Patch [3]: [PATCH v3 3/4] drm/panel: of_drm_find_panel() return a counted reference ---
commit 5ff0041b676bfa0b0285edf9631f1c23402a5814
Author: Albert Esteve <aesteve@redhat.com>
Subject: drm/panel: of_drm_find_panel() return a counted reference
Callers of of_drm_find_panel() and drm_of_find_panel_or_bridge() 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. Fix the lookup
function 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.
Link: [not provided in metadata]
> diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
> index b805403281504..a418011f7d4d6 100644
> --- a/drivers/gpu/drm/exynos/exynos_dp.c
> +++ b/drivers/gpu/drm/exynos/exynos_dp.c
[ ... ]
> @@ -182,8 +182,15 @@ static int exynos_dp_probe(struct platform_device *pdev)
>
> out:
> dp->adp = analogix_dp_probe(dev, &dp->plat_data);
> - if (IS_ERR(dp->adp))
> + if (IS_ERR(dp->adp)) {
> + /*
> + * The driver core does not invoke remove() for failed probes,
> + * so release the probe-time panel reference here.
> + */
> + if (dp->plat_data.panel)
> + drm_panel_put(dp->plat_data.panel);
> return PTR_ERR(dp->adp);
> + }
>
> if (dp->plat_data.panel || dp->plat_data.next_bridge)
> return component_add(&pdev->dev, &exynos_dp_ops);
> else
> return analogix_dp_finish_probe(dp->adp);
[Severity: High]
Does this leak the panel reference if component_add() or
analogix_dp_finish_probe() fails?
The failure path for analogix_dp_probe() correctly drops the reference, but
the subsequent returns do not seem to handle failures. Because failed probes
do not trigger the remove() callback, the reference acquired via
of_drm_find_panel() might remain permanently leaked in those 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);
> + }
>
> ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
> NULL, DRM_MODE_ENCODER_DPI, NULL);
[Severity: High]
This is a pre-existing issue, but does this code cause a kernel panic if
devm_drm_panel_bridge_add_typed() fails?
When the bridge allocation fails, it returns an ERR_PTR. This value is
eventually assigned to ib->next_bridge and passed to drm_bridge_attach().
Since drm_bridge_attach() checks for NULL but not IS_ERR(), it will
dereference the error pointer.
> 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;
[Severity: Medium]
Will this overwrite and leak panel references when looping over multiple
endpoints?
In pl111_modeset_init(), for_each_endpoint_of_node() iterates over endpoints
and calls drm_of_find_panel_or_bridge() on each. If multiple panels are
found, the assignment:
panel = tmp_panel;
overwrites the previously acquired pointer without calling drm_panel_put()
on the old one, leaking all but the last panel found.
> diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
> index add3123e5ce70..c908ea4cfa67a 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
[ ... ]
> @@ -139,6 +141,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);
> }
[Severity: High]
This is a pre-existing issue, but does the error path fail to clean up the
bridge created by drm_panel_bridge_add_typed()?
If a panel is found, a bridge is allocated and a panel reference is acquired.
If subsequent calls like drm_bridge_attach() or drm_bridge_connector_init()
fail, the code jumps to err_free_encoder. This cleans up the encoder but
appears to miss calling drm_panel_bridge_remove() to free the bridge and drop
the panel reference.
> 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
[ ... ]
> @@ -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;
> + }
> +
[Severity: Critical]
Can using devm_add_action_or_reset() here cause lifecycle mismatches?
This code is called during sun4i_tcon_bind(), which is a component bind
callback. Devres actions are tied to the device's probe/remove lifecycle,
not the component bind/unbind lifecycle.
When the component unbinds, the action will not execute, leaking the panel
reference. If the component rebinds, it adds another action. When the device
is finally removed, the accumulated actions will execute multiple times,
causing a double-free or refcount underflow on the panel.
> 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
[ ... ]
> @@ -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;
> + }
> +
[Severity: Critical]
Can using devm_add_action_or_reset() here also cause lifecycle mismatches for
the same reasons as in sun4i_lvds.c?
> 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 does this lead to a kernel panic in the
error path?
If drm_panel_bridge_add_typed() fails, it returns an ERR_PTR. The code then
jumps to out_bridge and unconditionally calls drm_panel_bridge_remove(bridge).
Since drm_panel_bridge_remove() only checks if (!bridge) before dereferencing
the pointer, passing an ERR_PTR will cause a crash.
Additional observations about panel references in related files:
[Severity: High]
Could the panel reference acquired in tegra_output_probe() be leaked in
tegra_dc_rgb_init()?
When tegra_dc_rgb_init() (in drivers/gpu/drm/tegra/rgb.c) wraps the panel in a
bridge using devm_drm_panel_bridge_add(), it sets output->panel = NULL. This
seems to break the cleanup path in tegra_output_remove(), which checks
if (output->panel) before calling drm_panel_put(), resulting in the probe-time
reference being permanently leaked.
[Severity: High]
Can the panel reference leak in the error paths of decon_probe() and
fimd_probe()?
The exynos_dpi_probe() function acquires a panel reference and relies on the
callers to explicitly call exynos_dpi_remove() to release it. However, if
component_add() fails in exynos7_drm_decon.c:decon_probe() or
exynos_drm_fimd.c:fimd_probe(), they branch to an error path that skips
calling exynos_dpi_remove(), potentially leaking the reference.
[Severity: High]
Looking at analogix_dp_aux_done_probing() in analogix_dp_core.c, does this
code leak the panel reference if component_add() fails?
A reference is acquired via drm_of_find_panel_or_bridge(), but if
component_add() fails, the error is propagated back without dropping the
reference.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-drm_refcount_wiring-v3-0-023900c32e01@redhat.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/4] drm/panel: have drm_panel_add/remove manage a list reference
2026-07-17 14:02 ` Albert Esteve
(?)
@ 2026-07-17 14:31 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-17 14:31 UTC (permalink / raw)
To: Albert Esteve; +Cc: dri-devel, imx, wsa+renesas, Frank.Li, linux-sunxi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Modifying drm_panel_add() to take a reference replaces a list-iteration crash with a memory leak and a devres Use-After-Free for drivers that fail to call drm_panel_remove().
Pre-existing issues:
- [High] Lookup functions return raw pointers without acquiring references, failing to protect the caller from concurrent panel removal.
- [High] Use-After-Free in the drm_panel_follower API when a panel is unbound.
--
--- Patch [1]: [PATCH v3 1/4] drm/panel: have drm_panel_add/remove manage a list reference ---
commit 2e9c2d76403b7b7e3caee78c96bffbb1f61d8df7
Author: Albert Esteve <aesteve@redhat.com>
drm/panel: have drm_panel_add/remove manage a list reference
The commit adds a reference acquisition to drm_panel_add() and a release
to drm_panel_remove() to prevent the panel from being freed while still
linked in panel_list. The global registry now holds a counted reference
for as long as the panel is listed.
> 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);
[Severity: High]
Does taking a reference here replace a list-iteration crash with a memory
leak and a devres use-after-free for drivers that fail to call
drm_panel_remove()?
If a driver calls drm_panel_add() but fails to call drm_panel_remove() on
unbind, the new drm_panel_get() reference prevents the struct drm_panel from
being freed, leaving it in panel_list. However, the driver's devres teardown
still executes, freeing all underlying hardware resources like clocks,
regulators, and GPIOs.
If another driver later discovers this 'zombie' panel via
of_drm_find_panel() and invokes its callbacks, will the panel driver access
freed devres resources, causing a use-after-free or hardware lockup?
> mutex_lock(&panel_lock);
> list_add_tail(&panel->list, &panel_list);
> mutex_unlock(&panel_lock);
> }
This isn't a bug introduced by this patch, but lookup functions return raw
pointers without acquiring references, failing to protect the caller from
concurrent panel removal:
drivers/gpu/drm/drm_panel.c:of_drm_find_panel() {
...
list_for_each_entry(panel, &panel_list, list) {
if (panel->dev->of_node == np) {
mutex_unlock(&panel_lock);
return panel;
}
}
...
}
[Severity: High]
While the commit message claims holding a reference in the registry ensures
the object outlives concurrent lookups, does this only protect the list
iteration? Once the mutex is unlocked, if a concurrent thread executes
drm_panel_remove() (e.g. due to driver unbind), could it drop the registry's
reference and potentially free the panel memory while the caller is actively
using the unprotected pointer?
This is also a pre-existing issue, but there appears to be a use-after-free
in the follower API when a panel is unbound:
drivers/gpu/drm/drm_panel.c:drm_panel_add_follower() {
...
panel = find_panel_by_dev(follower_dev);
if (IS_ERR(panel))
return PTR_ERR(panel);
get_device(panel->dev);
follower->panel = panel;
mutex_lock(&panel->follower_lock);
...
}
[Severity: High]
Does acquiring a reference to the panel's underlying device via
get_device(panel->dev) fail to keep the devres-allocated struct drm_panel
alive? Because the panel is allocated via devm_drm_panel_alloc(), its memory
is tied to the driver's devres lifecycle, not the struct device refcount.
If the panel driver unbinds, devres frees the struct drm_panel memory
regardless of the device refcount, leaving the follower driver with a dangling
follower->panel pointer.
When the follower unbinds, it calls drm_panel_remove_follower():
drivers/gpu/drm/drm_panel.c:drm_panel_remove_follower() {
struct drm_panel *panel = follower->panel;
int ret;
mutex_lock(&panel->follower_lock);
...
}
Won't attempting to lock the freed memory (mutex_lock(&panel->follower_lock))
cause a direct use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-drm_refcount_wiring-v3-0-023900c32e01@redhat.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-17 14:31 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 14:02 [PATCH v3 0/4] drm/panel: refcounting panel lookups and references Albert Esteve
2026-07-17 14:02 ` Albert Esteve
2026-07-17 14:02 ` [PATCH v3 1/4] drm/panel: have drm_panel_add/remove manage a list reference Albert Esteve
2026-07-17 14:02 ` Albert Esteve
2026-07-17 14:31 ` sashiko-bot
2026-07-17 14:02 ` [PATCH v3 2/4] drm/bridge/panel: hold a reference to the wrapped panel Albert Esteve
2026-07-17 14:02 ` Albert Esteve
2026-07-17 14:15 ` sashiko-bot
2026-07-17 14:02 ` [PATCH v3 3/4] drm/panel: of_drm_find_panel() return a counted reference Albert Esteve
2026-07-17 14:02 ` Albert Esteve
2026-07-17 14:24 ` sashiko-bot
2026-07-17 14:02 ` [PATCH v3 4/4] drm/panel: find_panel_by_fwnode() " Albert Esteve
2026-07-17 14:02 ` Albert Esteve
2026-07-17 14:14 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.