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.
@@ -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)) {
@@ -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);
@@ -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);
}
@@ -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);
@@ -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;
}
@@ -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
@@ -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);
}
@@ -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);
@@ -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);
@@ -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);
@@ -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
@@ -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;
}
@@ -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);
}
@@ -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;
}
@@ -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);
}
@@ -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;
@@ -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);
@@ -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) {
@@ -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");
@@ -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);
@@ -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);
}
@@ -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",
@@ -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;
@@ -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));
@@ -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;
@@ -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));
@@ -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);
}
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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);
@@ -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);
@@ -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,
@@ -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,
@@ -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);
@@ -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;
@@ -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)
@@ -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);
@@ -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;
@@ -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;