Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Laurentiu Palcu" <laurentiu.palcu@oss.nxp.com>
Cc: linux-clk@vger.kernel.org, imx@lists.linux.dev,
	devicetree@vger.kernel.org, dri-devel@lists.freedesktop.org,
	robh@kernel.org, conor+dt@kernel.org, Frank.Li@kernel.org
Subject: Re: [PATCH v11 4/7] drm/imx: Add support for i.MX94 DCIF
Date: Tue, 10 Nov 2026 15:14:01 +0000	[thread overview]
Message-ID: <sashiko-outbox-207242@kernel.org> (raw)
In-Reply-To: <20261110-dcif-upstreaming-v11-4-9e13c85ae0d8@oss.nxp.com>

Thank you for your contribution! Sashiko AI review found 8 potential
issues to consider:
- [High] drm/imx/dcif: unclocked MMIO access when runtime PM resume
  fails
- [Medium] drm/imx: dcif: missing vblank reference in
  dcif_crc_source_enable()
- [Medium] drm/imx/dcif: unmasked interrupt status handling in
  dcif_irq_handler()
- [Medium] drm/imx: dcif: unchecked runtime PM resume in
  dcif_hw_probe_init()
- [Medium] drm/imx: dcif: unbalanced clock disable if
  dcif_runtime_resume() fails
- [Low] drm/imx: dcif: signed overflow in dcif_crc_source_parse() ROI
  check
- [Low] drm/imx/dcif: split __free() declaration in
  dcif_crtc_query_output_bus_format()
- [Low] drm/imx: dcif: missing fb->pitches[0] bounds check in
  dcif_plane_atomic_check()
--

commit b9251fe09a901a215b6a40b8b790485d134d37e2
Author: Sandor Yu <sandor.yu@nxp.com>

drm/imx: Add support for i.MX94 DCIF

This commit adds DRM/KMS driver support for the NXP i.MX94 Display Control
Interface (DCIF), including CRTC, primary and overlay planes, and CRC
generation.

> diff --git a/drivers/gpu/drm/imx/dcif/dcif-crc.c b/drivers/gpu/drm/imx/dcif/dcif-crc.c
> new file mode 100644
> index 0000000000000..132e40d3f3d45
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-crc.c
[ ... ]
> +void dcif_crc_source_enable(struct dcif_dev *dcif, enum dcif_crc_source source,
> +			    struct drm_rect *roi, int ncrc)
> +{
> +	if (ncrc >= MAX_DCIF_CRC_NUM)
> +		return;
> +
> +	if (source == DCIF_CRC_SRC_NONE)
> +		return;
> +
> +	if (dcif->crc_is_enabled)
> +		return;
> +
> +	dcif_crc_config(dcif, roi, ncrc);
> +
> +	regmap_set_bits(dcif->regmap, DCIF_CRC_CTRL,
> +			DCIF_CRC_CTRL_CRC_MODE | DCIF_CRC_CTRL_CRC_SHADOW_LOAD_EN |
> +			DCIF_CRC_CTRL_CRC_TRIG);
> +
> +	scoped_guard(spinlock_irqsave, &dcif->drm.event_lock)
> +		dcif->crc_is_enabled = true;
> +}
> +
> +void dcif_crc_source_disable(struct dcif_dev *dcif, int ncrc)
> +{
> +	if (!dcif->crc_is_enabled)
> +		return;
> +
> +	if (ncrc >= MAX_DCIF_CRC_NUM)
> +		return;
> +
> +	regmap_clear_bits(dcif->regmap, DCIF_CRC_CTRL, DCIF_CRC_CTRL_CRC_EN(ncrc));
> +
> +	scoped_guard(spinlock_irqsave, &dcif->drm.event_lock)
> +		dcif->crc_is_enabled = false;
> +}

[Severity: Medium]
Should dcif_crc_source_enable() and dcif_crc_source_disable() acquire and
release a vblank reference with drm_crtc_vblank_get() and
drm_crtc_vblank_put()?

dcif_irq_handler() only samples DCIF_CRC_VAL_R(0) and calls
drm_crtc_add_crc_entry() when DCIF_INT0_VS_BLANK fires, and that interrupt
is only unmasked while a vblank reference is held.  On a steady display
without continuous page flips, the vblank disable timer will mask
DCIF_INT0_VS_BLANK after the atomic commit that enables CRC finishes,
causing reads from debugfs crc/data to block indefinitely.

[ ... ]

> +		while ((opt = strsep(&options, ",")) != NULL) {
> +			if (i > 3)
> +				return -EINVAL;
> +
> +			ret = kstrtouint(opt, 10, &params[i]);
> +			if (ret < 0)
> +				return ret;
> +
> +			i++;
> +		}
> +
> +		if (i != 4)
> +			return -EINVAL;
> +
> +		roi->x1 = params[0];
> +		roi->y1 = params[1];
> +		roi->x2 = params[2];
> +		roi->y2 = params[3];
> +
> +		if (!drm_rect_visible(roi))
> +			return -EINVAL;

[Severity: Low]
Can unsigned values in params[] wrap to negative ints in struct drm_rect
and bypass the ROI bounds checks?

For example, "roi:10,0,2147483648,10" sets roi->x1 = 10 and
roi->x2 = INT_MIN, which wraps in drm_rect_visible() to a positive width
and then passes dcif_crtc_check_crc_roi() because roi->x1 >= 0 and
roi->x2 <= m->hdisplay, programming truncated 12-bit coordinates into
DCIF_CRC_POS_R and DCIF_CRC_SIZE_R.

> diff --git a/drivers/gpu/drm/imx/dcif/dcif-crtc.c b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
> new file mode 100644
> index 0000000000000..d5285105fa4df
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
[ ... ]
> +static int dcif_crtc_query_output_bus_format(struct drm_crtc *crtc,
> +					     struct drm_crtc_state *crtc_state)
> +{
> +	struct dcif_crtc_state *dcif_state = to_dcif_crtc_state(crtc_state);
> +	struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
> +	struct drm_bridge_state *bridge_state;
> +	u32 bus_format;
> +
> +	dcif_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
> +	dcif_state->bus_flags = 0;
> +
> +	bridge = dcif_crtc_get_bridge(crtc, crtc_state);

[Severity: Low]
This isn't a bug, but per Cleanup API conventions, bridge should be defined
and initialized in a single statement rather than declared with
__free(drm_bridge_put) = NULL at the top of the function and assigned
later.

[ ... ]

> +static void dcif_crtc_atomic_enable(struct drm_crtc *crtc,
> +				    struct drm_atomic_commit *state)
> +{
> +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +	struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
> +	struct drm_display_mode *adj = &crtc_state->adjusted_mode;
> +	struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
> +	struct drm_device *drm = crtc->dev;
> +	int ret;
> +
> +	dev_dbg(drm->dev, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
> +
> +	/* enable power when we start to set mode for CRTC */
> +	ret = pm_runtime_resume_and_get(drm->dev);
> +	if (ret < 0) {
> +		drm_err(drm, "failed to resume DCIF, ret = %d\n", ret);
> +		return;
> +	}
> +	dcif->crtc_pm_enabled = true;
[ ... ]
> +}
> +
> +static void dcif_crtc_atomic_disable(struct drm_crtc *crtc,
> +				     struct drm_atomic_commit *state)
> +{
> +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +	struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
> +	struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
> +	struct drm_device *drm = crtc->dev;
> +
> +	if (dcif->has_crc && dcif_crtc_state->crc.source != DCIF_CRC_SRC_NONE)
> +		dcif_crc_source_disable(dcif, 0);
> +
> +	dcif_disable_controller(dcif);
> +	dcif_disable_plane_panic(dcif);
> +
> +	drm_crtc_vblank_off(crtc);
> +
> +	if (dcif->crtc_pm_enabled) {
> +		dcif->crtc_pm_enabled = false;
> +		pm_runtime_put_sync(drm->dev);
> +	}

[Severity: High]
If pm_runtime_resume_and_get() fails in dcif_crtc_atomic_enable(), won't
subsequent callbacks still access DCIF MMIO registers while the clocks and
power domain are off?

After dcif_crtc_atomic_enable() returns early with dcif->crtc_pm_enabled
set to false, drm_atomic_helper_commit_tail_rpm() still runs
drm_atomic_helper_commit_planes() because crtc_state->active is true,
invoking dcif_plane_atomic_update() and dcif_crtc_atomic_flush() without
checking dcif->crtc_pm_enabled.  Later, dcif_crtc_atomic_disable() also
calls dcif_crc_source_disable(), dcif_disable_controller(), and
dcif_disable_plane_panic() before checking dcif->crtc_pm_enabled, causing
unclocked MMIO accesses and an external abort on ARM64.

[ ... ]

> +irqreturn_t dcif_irq_handler(int irq, void *data)
> +{
> +	struct drm_device *drm = data;
> +	struct dcif_dev *dcif = to_dcif_dev(drm);
> +	int domain = dcif->cpu_domain;
> +	u32 stat0, stat1, crc;
> +
> +	regmap_read(dcif->regmap, DCIF_IS0(domain), &stat0);
> +	regmap_read(dcif->regmap, DCIF_IS1(domain), &stat1);
> +	regmap_write(dcif->regmap, DCIF_IS0(domain), stat0);
> +	regmap_write(dcif->regmap, DCIF_IS1(domain), stat1);
> +
> +	if (stat0 & DCIF_INT0_VS_BLANK) {
> +		drm_crtc_handle_vblank(&dcif->crtc);
> +
> +		scoped_guard(spinlock_irqsave, &drm->event_lock) {
> +			if (dcif->crc_is_enabled) {
> +				regmap_read(dcif->regmap, DCIF_CRC_VAL_R(0), &crc);
> +				drm_crtc_add_crc_entry(&dcif->crtc, false, 0, &crc);
> +				dev_dbg(drm->dev, "crc=0x%x\n",  crc);
> +			}
> +		}
> +	}
> +
> +	if (stat1 & (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1)) {
> +		u32 panic = stat1 & (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1);
> +
> +		dev_dbg_ratelimited(drm->dev, "FIFO panic on %s\n",
> +				    panic == (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1) ?
> +				    "layers 0 & 1" : panic == DCIF_INT1_FIFO_PANIC0 ? "layer 0" :
> +				    "layer 1");
> +	}
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
Should dcif_irq_handler() mask stat0 and stat1 with DCIF_IE0(domain) and
DCIF_IE1(domain), and return IRQ_NONE when no enabled interrupt is
pending?

DCIF_IS0(domain) latches DCIF_INT0_VS_BLANK on every vertical blank even
when it is masked in DCIF_IE0(domain), which is why
dcif_crtc_enable_vblank() clears DCIF_IS0(domain) before enabling it.
Because FIFO panic interrupts remain enabled in DCIF_IE1(domain) whenever
the CRTC is active, a FIFO panic interrupt firing mid-frame while vblanks
are disabled will see the latched DCIF_INT0_VS_BLANK bit in stat0 and
spuriously call drm_crtc_handle_vblank() and drm_crtc_add_crc_entry().

> diff --git a/drivers/gpu/drm/imx/dcif/dcif-drv.c b/drivers/gpu/drm/imx/dcif/dcif-drv.c
> new file mode 100644
> index 0000000000000..3b1b9dbf0c6b0
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-drv.c
[ ... ]
> +static void dcif_hw_probe_init(struct dcif_dev *dcif)
> +{
> +	struct drm_device *drm = &dcif->drm;
> +	u32 val, vmin, vmaj;
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(drm->dev);
> +	if (ret < 0) {
> +		drm_err(drm, "failed to resume DCIF: %d\n", ret);
> +		return;
> +	}
[ ... ]
> +static int dcif_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	dcif_hw_probe_init(dcif);
> +
> +	ret = dcif_kms_prepare(dcif);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_request_irq(drm->dev, dcif->irq[dcif->cpu_domain],
> +			       dcif_irq_handler, 0, drm->driver->name, drm);

[Severity: Medium]
Should dcif_hw_probe_init() return an error code so dcif_probe() can abort
when pm_runtime_resume_and_get() fails?

If runtime resume fails here, dcif_hw_probe_init() returns early without
reading DCIF_VER, masking bootloader interrupts, or stopping the
controller, and dcif_probe() continues on to request the IRQ and register
the DRM device with uninitialized hardware state.  Also, calling
dcif_hw_probe_init() before dcif_kms_prepare() powers on and disables the
hardware before devm_drm_of_get_bridge() checks for probe deferral and
before dcif_kms_link_suppliers() sets up the supplier device links.

[ ... ]

> +static int dcif_suspend(struct device *dev)
> +{
> +	struct dcif_dev *dcif = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = drm_mode_config_helper_suspend(&dcif->drm);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (pm_runtime_suspended(dev))
> +		return 0;
> +
> +	return dcif_runtime_suspend(dev);
> +}
> +
> +static int dcif_resume(struct device *dev)
> +{
> +	struct dcif_dev *dcif = dev_get_drvdata(dev);
> +	int ret;
> +
> +	if (!pm_runtime_suspended(dev)) {
> +		ret = dcif_runtime_resume(dev);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return drm_mode_config_helper_resume(&dcif->drm);
> +}

[Severity: Medium]
If dcif_runtime_resume() fails in dcif_resume(), won't the runtime PM core
call dcif_runtime_suspend() a second time on already disabled clocks?

When the CRTC is active before system suspend, device_prepare() increments
the runtime PM usage count so drm_mode_config_helper_suspend() ->
pm_runtime_put_sync() leaves the device RPM_ACTIVE, and dcif_suspend()
then calls dcif_runtime_suspend() directly without updating the runtime PM
status.  If dcif_runtime_resume() fails during system resume, the clocks
remain disabled while runtime_status is still RPM_ACTIVE, so
device_complete() -> pm_runtime_put() will invoke dcif_runtime_suspend() ->
clk_bulk_disable_unprepare() again.  Using pm_runtime_force_suspend() and
pm_runtime_force_resume() would keep the runtime PM state in sync with the
clocks.

> diff --git a/drivers/gpu/drm/imx/dcif/dcif-plane.c b/drivers/gpu/drm/imx/dcif/dcif-plane.c
> new file mode 100644
> index 0000000000000..9f54f1b2d8d17
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-plane.c
[ ... ]
> +static int dcif_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state)
> +{
> +	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
> +	struct dcif_dev *dcif = plane_to_dcif_dev(plane);
> +	struct drm_framebuffer *fb = new_plane_state->fb;
> +	struct drm_crtc_state *crtc_state;
> +	int ret;
> +
> +	if (!fb)
> +		return 0;
[ ... ]
> +	if (new_plane_state->fb->format->has_alpha &&
> +	    new_plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE &&
> +	    new_plane_state->alpha != DRM_BLEND_ALPHA_OPAQUE)
> +		return -EINVAL;
> +
> +	return 0;
> +}

[Severity: Low]
Should dcif_plane_atomic_check() reject framebuffers with
fb->pitches[0] > 0xffff?

DCIF_CTRLDESC3_PITCH() is a 16-bit field (GENMASK(15, 0)), so a
framebuffer stride >= 65536 bytes is silently truncated in
dcif_plane_atomic_update(), corrupting scanout.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-dcif-upstreaming-v11-0-9e13c85ae0d8@oss.nxp.com?part=4

  reply	other threads:[~2026-11-10 15:14 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 14:27 [PATCH v11 0/7] Add support for i.MX94 DCIF Laurentiu Palcu
2026-11-10 14:27 ` [PATCH v11 1/7] dt-bindings: display: fsl,ldb: Add i.MX94 LDB Laurentiu Palcu
2026-11-10 14:27 ` [PATCH v11 2/7] drm/bridge: fsl-ldb: Add support for i.MX94 Laurentiu Palcu
2026-11-10 14:27 ` [PATCH v11 3/7] dt-bindings: display: imx: Add i.MX94 DCIF Laurentiu Palcu
2026-11-10 14:27 ` [PATCH v11 4/7] drm/imx: Add support for " Laurentiu Palcu
2026-11-10 15:14   ` sashiko-bot [this message]
2026-11-10 14:27 ` [PATCH v11 5/7] dt-bindings: clock: nxp,imx95-blk-ctl: Add LDB bridge child node Laurentiu Palcu
2026-11-10 14:46   ` sashiko-bot
2026-11-10 20:07   ` Rob Herring (Arm)
2026-11-10 14:27 ` [PATCH v11 6/7] arm64: dts: imx94: Add display pipeline nodes Laurentiu Palcu
2026-11-10 14:27 ` [PATCH v11 7/7] arm64: dts: imx943-evk: Add display support using IT6263 Laurentiu Palcu

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-207242@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=Frank.Li@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=imx@lists.linux.dev \
    --cc=laurentiu.palcu@oss.nxp.com \
    --cc=linux-clk@vger.kernel.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