From: sashiko-bot@kernel.org
To: "Ze Huang" <ze.huang@oss.qualcomm.com>
Cc: dri-devel@lists.freedesktop.org, Frank.Li@kernel.org,
imx@lists.linux.dev
Subject: Re: [PATCH v2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers
Date: Thu, 16 Jul 2026 09:14:58 +0000 [thread overview]
Message-ID: <20260716091459.1D8101F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260716-drm-simple-kms-removal-v2-1-7e4a6b8528e6@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] Missing framebuffer validation in atomic paths leads to an unconditional NULL pointer dereference when a plane is configured without a framebuffer.
- [High] `imx_lcdc_plane_helper_atomic_check` fetches a potentially NULL CRTC state, causing a kernel panic in core helpers.
- [High] Redundant clock enable calls cause a persistent reference count leak for `clk_per` on every display modeset.
Pre-existing issues:
- [Critical] The driver accesses peripheral MMIO registers and leaves interrupts unmasked while the AHB and IPG bus clocks are disabled.
- [High] The CRTC atomic check unconditionally overwrites the `mode_changed` flag, potentially discarding modeset requests initiated by the DRM core.
- [High] VBLANK completion events are signaled prematurely during display disable/modeset sequences.
--
--- Patch [1]: [PATCH v2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers ---
commit 78bda5593353310f7405860b2b4d7ea3060e0afa
Author: Ze Huang <ze.huang@oss.qualcomm.com>
Subject: drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers
This commit converts the i.MX LCDC driver to explicit primary plane, CRTC,
and encoder objects, removing the dependency on the simple-KMS framework.
It preserves most of the previous callbacks by rewiring them into the
standard atomic helper paths.
Link: https://patch.msgid.link/20260705-drm-simple-kms-removal-v1-0-b4e1ca053623@oss.qualcomm.com
> diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c
> index c67fe80b8115e..5c858fc81a78c 100644
> --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c
> +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c
[ ... ]
> -static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe,
> - struct drm_crtc_state *crtc_state,
> - struct drm_plane_state *plane_state)
> +static void imx_lcdc_crtc_helper_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *commit)
> {
> int ret;
> int clk_div;
> int bpp;
> - struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev);
> - struct drm_display_mode *mode = &pipe->crtc.mode;
> + struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(crtc->dev);
> + struct drm_crtc_state *cstate = drm_atomic_get_new_crtc_state(commit, crtc);
> + struct drm_plane_state *plane_state = lcdc->plane.state;
[Severity: High]
Does this code result in a NULL pointer dereference when a primary plane is
configured without a framebuffer?
If userspace submits an atomic commit that enables the CRTC but sets the
primary plane's FB_ID to 0, plane_state->fb will be NULL.
Since drm_atomic_helper_check_crtc_primary_plane only verifies that the
primary plane is present in the plane mask, it does not prevent a NULL
framebuffer. This causes an unconditional dereference shortly after this line
when extracting the format via imx_lcdc_get_format(), as well as inside
imx_lcdc_update_hw_registers() when calling drm_fb_dma_get_gem_addr().
> + struct drm_display_mode *mode = &cstate->mode;
> struct drm_display_info *disp_info = &lcdc->connector->display_info;
> const int hsync_pol = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : 1;
> const int vsync_pol = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : 1;
[ ... ]
> @@ -230,40 +232,46 @@ static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe,
>
> ret = clk_prepare_enable(lcdc->clk_ipg);
> if (ret) {
> - dev_err(pipe->crtc.dev->dev, "Cannot enable ipg clock: %pe\n", ERR_PTR(ret));
> + dev_err(crtc->dev->dev, "Cannot enable ipg clock: %pe\n", ERR_PTR(ret));
> return;
> }
[Severity: Critical]
This is a pre-existing issue, but does this perform memory-mapped writes to
peripheral registers before the IPG and AHB bus clocks are enabled?
Just prior to enabling the IPG clock here, the driver performs multiple
writel() configuration writes to the LPCR and LCPR registers. Accessing
registers of an unclocked IP block on i.MX platforms can hang the bus and
trigger a synchronous external abort. Could we ensure the clocks are
prepared and enabled before doing configuration writes?
> ret = clk_prepare_enable(lcdc->clk_ahb);
> if (ret) {
> - dev_err(pipe->crtc.dev->dev, "Cannot enable ahb clock: %pe\n", ERR_PTR(ret));
> + dev_err(crtc->dev->dev, "Cannot enable ahb clock: %pe\n", ERR_PTR(ret));
>
> clk_disable_unprepare(lcdc->clk_ipg);
>
> return;
> }
>
> - imx_lcdc_update_hw_registers(pipe, NULL, true);
> + imx_lcdc_update_hw_registers(crtc, NULL, true);
>
> /* Enable VBLANK Interrupt */
> writel(INTR_EOF, lcdc->base + IMX21LCDC_LIER);
> +
> + drm_crtc_vblank_on(crtc);
> }
>
> -static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe)
> +static void imx_lcdc_crtc_helper_atomic_disable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *commit)
> {
> - struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev);
> - struct drm_crtc *crtc = &lcdc->pipe.crtc;
> + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(commit, crtc);
> + struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(commit, crtc);
> + struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(crtc->dev);
> struct drm_pending_vblank_event *event;
>
> + drm_crtc_vblank_off(crtc);
> +
> clk_disable_unprepare(lcdc->clk_ahb);
> clk_disable_unprepare(lcdc->clk_ipg);
>
> - if (pipe->crtc.enabled)
> + if (old_crtc_state->enable)
> clk_disable_unprepare(lcdc->clk_per);
>
> spin_lock_irq(&lcdc->drm.event_lock);
> - event = crtc->state->event;
> + event = new_crtc_state->event;
> if (event) {
> - crtc->state->event = NULL;
> + new_crtc_state->event = NULL;
> drm_crtc_send_vblank_event(crtc, event);
> }
[Severity: High]
This isn't a bug introduced by this patch, but does consuming the event
during the disable phase signal completion to userspace prematurely?
Sending the event here signals to the compositor that the modeset is complete
before the new mode is programmed and flushed, which could lead to frame
submission races or compositor stalls.
> spin_unlock_irq(&lcdc->drm.event_lock);
> @@ -272,17 +280,20 @@ static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe)
> writel(0, lcdc->base + IMX21LCDC_LIER);
[Severity: Critical]
This is a pre-existing issue, but are the bus clocks disabled before clearing
the VBLANK interrupt?
Similar to the enable path, writing to the LIER register while the IPG and
AHB clocks are disabled can trigger a bus fault. Furthermore, since the
interrupt is unmasked before the clocks are disabled, an incoming IRQ could
cause the handler to read the LISR register while clocks are off.
> }
>
> -static int imx_lcdc_pipe_check(struct drm_simple_display_pipe *pipe,
> - struct drm_plane_state *plane_state,
> - struct drm_crtc_state *crtc_state)
> +static int imx_lcdc_crtc_helper_atomic_check(struct drm_crtc *crtc,
> + struct drm_atomic_commit *commit)
> {
> + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc);
> + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(commit, crtc);
> const struct drm_display_mode *mode = &crtc_state->mode;
> - const struct drm_display_mode *old_mode = &pipe->crtc.state->mode;
> + const struct drm_display_mode *old_mode = &old_crtc_state->mode;
> + int ret;
>
> - if (mode->hdisplay < LCDC_MIN_XRES || mode->hdisplay > LCDC_MAX_XRES ||
> - mode->vdisplay < LCDC_MIN_YRES || mode->vdisplay > LCDC_MAX_YRES ||
> - mode->hdisplay % 0x10) { /* must be multiple of 16 */
> - drm_err(pipe->crtc.dev, "unsupported display mode (%u x %u)\n",
> + if (crtc_state->enable &&
> + (mode->hdisplay < LCDC_MIN_XRES || mode->hdisplay > LCDC_MAX_XRES ||
> + mode->vdisplay < LCDC_MIN_YRES || mode->vdisplay > LCDC_MAX_YRES ||
> + mode->hdisplay % 0x10)) { /* must be multiple of 16 */
> + drm_err(crtc->dev, "unsupported display mode (%u x %u)\n",
> mode->hdisplay, mode->vdisplay);
> return -EINVAL;
> }
> @@ -291,46 +302,116 @@ static int imx_lcdc_pipe_check(struct drm_simple_display_pipe *pipe,
> old_mode->hdisplay != mode->hdisplay ||
> old_mode->vdisplay != mode->vdisplay;
[Severity: High]
This is a pre-existing issue, but does this direct assignment to
mode_changed discard modeset requests initiated by the DRM core?
The core drm_atomic_helper_check_modeset() function may have already set
mode_changed to true due to connector routing changes or userspace forcing a
modeset. Using a direct assignment instead of a bitwise OR (|=) silently
clears that flag if the spatial dimensions haven't changed.
> - return 0;
> + if (crtc_state->enable) {
> + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> + if (ret)
> + return ret;
> + }
> +
> + return drm_atomic_add_affected_planes(commit, crtc);
> }
>
> -static void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe,
> - struct drm_plane_state *old_state)
> +static void imx_lcdc_plane_helper_atomic_update(struct drm_plane *plane,
> + struct drm_atomic_commit *commit)
> {
> - struct drm_crtc *crtc = &pipe->crtc;
> - struct drm_pending_vblank_event *event = crtc->state->event;
> - struct drm_plane_state *new_state = pipe->plane.state;
> + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(commit, plane);
> + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(commit, plane);
> + struct drm_crtc *crtc = new_state->crtc ?: old_state->crtc;
> struct drm_framebuffer *fb = new_state->fb;
> struct drm_framebuffer *old_fb = old_state->fb;
> struct drm_crtc *old_crtc = old_state->crtc;
> bool mode_changed = false;
>
> + if (!fb)
> + return;
> +
> if (old_fb && old_fb->format != fb->format)
> mode_changed = true;
> else if (old_crtc != crtc)
> mode_changed = true;
>
> - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed);
> + imx_lcdc_update_hw_registers(crtc, old_state, mode_changed);
> +}
[Severity: High]
Does calling imx_lcdc_update_hw_registers() here cause a persistent reference
count leak for clk_per?
During a modeset, imx_lcdc_crtc_helper_atomic_enable() already calls
imx_lcdc_update_hw_registers() which increments the clock reference count
when the CRTC is enabled.
When the plane helper subsequently calls it again here, the clock prepare
enable block is hit a second time. However, during display disable, the
plane update returns early due to a NULL fb, meaning the clock is only
disabled once in the CRTC disable path.
>
> - if (event) {
> - crtc->state->event = NULL;
> +static int imx_lcdc_plane_helper_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_commit *commit)
> +{
> + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, plane);
> + struct drm_crtc *crtc = plane_state->crtc;
> + struct drm_crtc_state *crtc_state = NULL;
>
> - spin_lock_irq(&crtc->dev->event_lock);
> + if (crtc)
> + crtc_state = drm_atomic_get_new_crtc_state(commit, crtc);
>
> - if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
> - drm_crtc_arm_vblank_event(crtc, event);
> - else
> - drm_crtc_send_vblank_event(crtc, event);
> + return drm_atomic_helper_check_plane_state(plane_state, crtc_state,
[Severity: High]
Can fetching the CRTC state this way cause a kernel panic in the core atomic
helpers?
If userspace submits a commit that modifies only plane properties without
affecting the CRTC, the CRTC is not implicitly added to the atomic commit
state.
In this scenario, drm_atomic_get_new_crtc_state() returns NULL. Passing this
NULL pointer directly to drm_atomic_helper_check_plane_state() causes a crash
when it dereferences crtc_state->enable.
Could this use drm_atomic_get_crtc_state() instead to ensure the state is
brought into the commit if it is missing?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260716-drm-simple-kms-removal-v2-1-7e4a6b8528e6@oss.qualcomm.com?part=1
next prev parent reply other threads:[~2026-07-16 9:15 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-16 9:00 [PATCH v2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Ze Huang
2026-07-16 9:14 ` sashiko-bot [this message]
2026-07-18 19:12 ` Ze Huang
2026-07-20 12:36 ` Thomas Zimmermann
2026-07-20 14:08 ` Maxime Ripard
2026-07-20 14:38 ` Thomas Zimmermann
2026-07-20 15:06 ` Maxime Ripard
2026-07-20 15:31 ` Thomas Zimmermann
2026-07-21 9:28 ` Maxime Ripard
2026-07-21 9:37 ` Thomas Zimmermann
2026-07-21 12:26 ` Maxime Ripard
2026-07-21 9:46 ` Ze Huang
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=20260716091459.1D8101F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=imx@lists.linux.dev \
--cc=sashiko-reviews@lists.linux.dev \
--cc=ze.huang@oss.qualcomm.com \
/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 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.