Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Svyatoslav Ryhel" <clamor95@gmail.com>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v3 4/7] drm/tegra: Add support for 8-bit CPU interface
Date: Mon, 19 Oct 2026 10:20:51 +0000	[thread overview]
Message-ID: <sashiko-outbox-176973@kernel.org> (raw)
In-Reply-To: <20261019095705.91062-5-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] drm/tegra: dbi: use-after-free and ERR_PTR deref of priv->data_gpios
- [High] drm/tegra: dbi: duplicate panel enable/disable and broken disable order
- [High] drm/tegra: dbi: wrong V_PULSE position regs in tegra_dbi_atomic_enable()
- [High] drm/tegra: dc: unregistering dbi device on probe deferral blocks reprobe
- [Medium] drm/tegra: dbi: drm_panel reference leak in tegra_dbi_probe()
- [Medium] drm/tegra: dbi: missing MODULE_ALIAS() in tegra_dbi_driver
--

commit 3433ae394a211380cb312bda5b319e1df0574de3
Author: Svyatoslav Ryhel <clamor95@gmail.com>

drm/tegra: Add support for 8-bit CPU interface

This commit adds a DRM bridge driver for the Tegra20/30 8-bit CPU (MIPI DBI
Type B) display controller interface and registers its platform device from
the Tegra DC probe path.

> diff --git a/drivers/gpu/drm/tegra/dbi.c b/drivers/gpu/drm/tegra/dbi.c
> new file mode 100644
> index 0000000000000..4b3966dea0438
> --- /dev/null
> +++ b/drivers/gpu/drm/tegra/dbi.c
> @@ -0,0 +1,415 @@

[ ... ]

> +static void tegra_dbi_write(struct tegra_dbi_priv *priv, u8 type, u8 value)
> +{
> +	DECLARE_BITMAP(value_bitmap, 8);
> +
> +	value_bitmap[0] = value;
> +
> +	gpiod_set_value(priv->dc_gpio, type);
> +
> +	gpiod_set_value(priv->cs_gpio, 0);
> +	gpiod_set_value(priv->rw_gpio, 0);
> +
> +	gpiod_set_array_value(priv->data_gpios->ndescs, priv->data_gpios->desc,
> +			      priv->data_gpios->info, value_bitmap);
> +
> +	gpiod_set_value(priv->cs_gpio, 1);
> +	gpiod_set_value(priv->rw_gpio, 1);
> +
> +	udelay(10);
> +}
> +
> +static int tegra_dbi_dbi_command(struct mipi_dbi *dbi, u8 *cmd, u8 *param, size_t num)
> +{
> +	struct tegra_dbi_priv *priv = mipi_dbi_to_tegra_dbi(dbi);
> +	u8 command = *cmd;
> +
> +	if (!priv->data_gpios)
> +		return 0;
> +
> +	tegra_dbi_write(priv, TEGRA_DBI_COMMAND, command);
> +
> +	for (int i = 0; i < num; i++)
> +		tegra_dbi_write(priv, TEGRA_DBI_DATA, param[i]);
> +
> +	return 0;
> +}
> +
> +static int tegra_dbi_prepare_gpios(struct tegra_dbi_priv *priv)
> +{
> +	struct device *dev = priv->dev;
> +	int ret;
> +
> +	if (priv->prepared)
> +		return 0;

[ ... ]

> +	priv->data_gpios = gpiod_get_array(dev, "data", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->data_gpios)) {
> +		ret = PTR_ERR(priv->data_gpios);
> +		dev_err(dev, "Failed to get data gpios %d\n", ret);
> +		goto free_dc;
> +	}
> +
> +	if (priv->data_gpios && priv->data_gpios->ndescs > TEGRA_DBI_DATA_PINS_MAX) {
> +		ret = -EINVAL;
> +		dev_err(dev, "Too many data gpios\n");
> +		goto free_data;
> +	}
> +
> +	priv->prepared = true;
> +
> +	return 0;
> +
> +free_data:
> +	gpiod_put_array(priv->data_gpios);
> +free_dc:
> +	gpiod_put(priv->dc_gpio);
> +free_rw:
> +	gpiod_put(priv->rw_gpio);
> +free_cs:
> +	gpiod_put(priv->cs_gpio);
> +
> +	return ret;
> +}

[Severity: Critical]
Can tegra_dbi_dbi_command() -> tegra_dbi_write() dereference a freed or
ERR_PTR priv->data_gpios pointer?

In tegra_dbi_atomic_enable() and tegra_dbi_atomic_disable(), the driver
releases priv->dc_gpio, priv->rw_gpio, and priv->cs_gpio via gpiod_put(),
frees priv->data_gpios via gpiod_put_array() (which calls kfree(descs)),
and sets priv->prepared = false without holding priv->dbi.cmdlock or
resetting the pointers to NULL.

Similarly, in tegra_dbi_prepare_gpios(), if gpiod_get_array() fails,
priv->data_gpios is left holding a non-NULL ERR_PTR(), and if
priv->data_gpios->ndescs > TEGRA_DBI_DATA_PINS_MAX, gpiod_put_array() frees
priv->data_gpios without clearing the pointer.

Because tegra_dbi_dbi_command() checks:

    if (!priv->data_gpios)
        return 0;

instead of checking priv->prepared, any MIPI DBI command sent while
priv->prepared is false (such as during modeset disable when
drm_atomic_bridge_chain_disable() runs panel_bridge_atomic_disable() ->
drm_panel_disable() before tegra_dbi_atomic_disable()) will pass a dangling
pointer or ERR_PTR to tegra_dbi_write(), dereferencing priv->data_gpios and
passing released GPIO descriptors to gpiod_set_value().

> +static void tegra_dbi_atomic_enable(struct drm_bridge *bridge,
> +				    struct drm_atomic_commit *state)
> +{
> +	struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> +	struct tegra_dc *dc = priv->dc;
> +	u32 value;
> +	int ret;

[ ... ]

> +	tegra_dc_writel(dc, V_PULSE1_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0);
> +	tegra_dc_writel(dc, PULSE_POLARITY_LOW, DC_DISP_V_PULSE1_CONTROL);
> +
> +	tegra_dc_writel(dc, PULSE_END(1), DC_DISP_V_PULSE0_POSITION_A);
> +	tegra_dc_writel(dc, 0, DC_DISP_V_PULSE0_POSITION_B);
> +	tegra_dc_writel(dc, 0, DC_DISP_V_PULSE0_POSITION_C);

[Severity: High]
Should these writes in tegra_dbi_atomic_enable() target
DC_DISP_V_PULSE1_POSITION_A, DC_DISP_V_PULSE1_POSITION_B, and
DC_DISP_V_PULSE1_POSITION_C instead of the V_PULSE0 position registers?

The surrounding code enables V_PULSE1_ENABLE, configures
DC_DISP_V_PULSE1_CONTROL, selects DC_SIGNAL_VPULSE1 in
DC_DISP_INIT_SEQ_CONTROL, and routes LDC_OUTPUT_SELECT_V_PULSE1, but leaves
DC_DISP_V_PULSE1_POSITION_A/B/C unconfigured while overwriting V_PULSE0.

[ ... ]

> +	ret = tegra_dbi_prepare_gpios(priv);
> +	if (ret)
> +		return;
> +
> +	if (priv->output.panel)
> +		drm_panel_enable(priv->output.panel);
> +
> +	gpiod_set_value(priv->cs_gpio, 0);
> +
> +	gpiod_put(priv->dc_gpio);
> +	gpiod_put(priv->rw_gpio);
> +	gpiod_put(priv->cs_gpio);
> +
> +	gpiod_put_array(priv->data_gpios);
> +	priv->prepared = false;
> +}
> +
> +static void tegra_dbi_atomic_disable(struct drm_bridge *bridge,
> +				     struct drm_atomic_commit *state)
> +{
> +	struct tegra_dbi_priv *priv = bridge_to_tegra_dbi(bridge);
> +	int ret;
> +
> +	ret = tegra_dbi_prepare_gpios(priv);
> +	if (ret)
> +		return;
> +
> +	if (priv->output.panel)
> +		drm_panel_disable(priv->output.panel);
> +
> +	gpiod_set_value(priv->cs_gpio, 0);
> +
> +	gpiod_put(priv->dc_gpio);
> +	gpiod_put(priv->rw_gpio);
> +	gpiod_put(priv->cs_gpio);
> +
> +	gpiod_put_array(priv->data_gpios);
> +	priv->prepared = false;
> +}

[Severity: High]
Does wrapping the panel with devm_drm_panel_bridge_add_typed() in
tegra_dbi_probe() and attaching it in tegra_dbi_attach() cause duplicate
drm_panel_enable() and drm_panel_disable() calls and broken disable
ordering?

Because priv->output.bridge is attached to encoder->bridge_chain after
priv->bridge:

1. On enable, drm_atomic_bridge_chain_pre_enable() calls
panel_bridge_atomic_pre_enable() -> drm_panel_prepare() before
tegra_dbi_atomic_enable() acquires the DBI GPIOs. Then
drm_atomic_bridge_chain_enable() runs tegra_dbi_atomic_enable(), which calls
drm_panel_enable(priv->output.panel), and immediately afterward runs
panel_bridge_atomic_enable() -> drm_panel_enable(), triggering:

    dev_warn(panel->dev, "Skipping enable of already enabled panel\n");

2. On disable, drm_atomic_bridge_chain_disable() walks the bridge chain in
reverse order. It runs panel_bridge_atomic_disable() -> drm_panel_disable()
before tegra_dbi_atomic_disable() has prepared the DBI GPIOs, and sets
panel->enabled = false. When tegra_dbi_atomic_disable() then runs, prepares
the GPIOs, and calls drm_panel_disable(priv->output.panel), drm_panel_disable()
sees !panel->enabled, logs:

    dev_warn(panel->dev, "Skipping disable of already disabled panel\n");

and returns without invoking panel->funcs->disable().

[ ... ]

> +static int tegra_dbi_probe(struct platform_device *pdev)
> +{

[ ... ]

> +	ret = devm_of_platform_populate(dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to probe children\n");
> +
> +	/* Initialize MIPI DBI interface */
> +	mutex_init(&priv->dbi.cmdlock);
> +	priv->dbi.command = tegra_dbi_dbi_command;
> +
> +	ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &bridge);
> +	if (ret)
> +		return ret;
> +
> +	if (panel) {
> +		bridge = devm_drm_panel_bridge_add_typed(dev, panel,
> +							 DRM_MODE_CONNECTOR_DPI);
> +		if (IS_ERR(bridge))
> +			return PTR_ERR(bridge);
> +	}

[Severity: Medium]
Does tegra_dbi_probe() leak the drm_panel reference acquired by
drm_of_find_panel_or_bridge()?

drm_of_find_panel_or_bridge() -> of_drm_find_panel() increments the panel
refcount via drm_panel_get(), and devm_drm_panel_bridge_add_typed() takes an
additional devres-managed reference on &panel->bridge, so the reference
from drm_of_find_panel_or_bridge() is never dropped with drm_panel_put().

[ ... ]

> +static struct platform_driver tegra_dbi_driver = {
> +	.driver.name = "tegra-dbi",
> +	.probe = tegra_dbi_probe,
> +};
> +module_platform_driver(tegra_dbi_driver);
> +
> +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> +MODULE_DESCRIPTION("Nvidia Tegra20/30 MIPI-DBI Type B driver");
> +MODULE_LICENSE("GPL");

[Severity: Medium]
Should tegra_dbi_driver include MODULE_ALIAS("platform:tegra-dbi") (or a
platform_device_id table) so the module auto-loads when CONFIG_DRM_TEGRA_DBI=m?

In tegra_dc_probe(), the device is registered with .name = "tegra-dbi" and
.of_node_reused = true, which causes platform_uevent() to emit
MODALIAS=platform:tegra-dbi rather than an OF modalias.

> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
> index 5c67928bcabfa..8f12d30cd2d8b 100644
> --- a/drivers/gpu/drm/tegra/dc.c
> +++ b/drivers/gpu/drm/tegra/dc.c

[ ... ]

> @@ -3382,6 +3389,28 @@ static int tegra_dc_probe(struct platform_device *pdev)
>  		}
>  	}
>  
> +	dbi_np = of_get_available_child_by_name(pdev->dev.of_node, "dbi");
> +	if (dbi_np) {
> +		struct platform_device_info dbi_info = {
> +			.parent = &pdev->dev,
> +			.name = "tegra-dbi",
> +			.id = PLATFORM_DEVID_AUTO,
> +			.fwnode = of_fwnode_handle(dbi_np),
> +			.of_node_reused = true,
> +		};
> +
> +		dc->dbi = platform_device_register_full(&dbi_info);
> +		of_node_put(dbi_np);
> +		if (IS_ERR(dc->dbi))
> +			return dev_err_probe(&pdev->dev, PTR_ERR(dc->dbi),
> +					     "Failed to create DBI device\n");
> +
> +		err = devm_add_action_or_reset(&pdev->dev, tegra_dc_release_dbi_pdev,
> +					       dc->dbi);
> +		if (err)
> +			return err;
> +	}
> +
>  	err = tegra_dc_rgb_probe(dc);
>  	if (err < 0 && err != -ENODEV)
>  		return dev_err_probe(&pdev->dev, err,

[Severity: High]
What happens when tegra_dc_rgb_probe() or tegra_dbi_probe() returns
-EPROBE_DEFER?

In tegra_dc_probe(), dc->dbi is registered right before tegra_dc_rgb_probe()
(which calls tegra_output_probe() -> drm_of_find_panel_or_bridge() to look up
the bridge on the dbi node). Similarly, tegra_dbi_probe() calls
devm_of_platform_populate(dev) and immediately requires the child panel in
drm_of_find_panel_or_bridge() before calling devm_drm_bridge_add().

If tegra-dbi or the child panel driver has not bound yet (for example, when
CONFIG_DRM_TEGRA_DBI=m, the panel driver is a module, or a panel dependency
defers), tegra_dbi_probe() unpopulates the child panel via devres and
tegra_dc_probe() returns -EPROBE_DEFER.

When tegra_dc_probe() fails with -EPROBE_DEFER, devres_release_all(&pdev->dev)
runs tegra_dc_release_dbi_pdev() -> platform_device_unregister(dc->dbi).
With both the tegra-dbi device and its child panel device removed from the
platform bus, what will match and bind to trigger a deferred reprobe of
tegra_dc?

> 

完美! Let's double-check all constraints:
- Plain text only, no markdown, no backticks, no ALL CAPS in commentary.
- Wrapped at 78 characters.
- Ends with a blank line.
- No line numbers mentioned in commentary.
- Severity tags `[Severity: ...]` on their own line right above each inline comment.
- All 6 findings (VF1, VF2, VF3, VF4, VF5, PVF1) included. Everything is clean and accurate. Ready to output!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019095705.91062-1-clamor95@gmail.com?part=4

  reply	other threads:[~2026-10-19 10:20 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  9:56 [PATCH v3 0/7] drm/tegra: Add support for Tegra20/Tegra30 8-bit CPU interface Svyatoslav Ryhel
2026-10-19  9:56 ` [PATCH v3 1/7] drm/tegra: dc: Expand available registers layouts Svyatoslav Ryhel
2026-10-19  9:57 ` [PATCH v3 2/7] drm/tegra: rgb: Parameterize configuration based on bus flags Svyatoslav Ryhel
2026-10-19 10:15   ` sashiko-bot
2026-10-19  9:57 ` [PATCH v3 3/7] dt-bindings: display: tegra: Document 8-bit CPU parallel interface Svyatoslav Ryhel
2026-10-19 10:09   ` sashiko-bot
2026-10-20 15:12   ` Rob Herring (Arm)
2026-10-19  9:57 ` [PATCH v3 4/7] drm/tegra: Add support for 8-bit CPU interface Svyatoslav Ryhel
2026-10-19 10:20   ` sashiko-bot [this message]
2026-10-19  9:57 ` [PATCH v3 5/7] dt-bindings: display: panel: Document Hitachi TX10D07VM0BAA and LG LH400WV3 panels Svyatoslav Ryhel
2026-10-19  9:57 ` [PATCH v3 6/7] drm/panel: Add Hitachi TX10D07VM0BAA MIPI DBI Type B panel driver Svyatoslav Ryhel
2026-10-19 10:09   ` sashiko-bot
2026-10-19  9:57 ` [PATCH v3 7/7] drm/panel: Add LG LH400WV3-SD04 " Svyatoslav Ryhel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-176973@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=clamor95@gmail.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox