* [PATCH v3 0/2] drm: replace simple display pipe users with atomic helpers
@ 2026-07-26 19:42 Ze Huang
2026-07-26 19:42 ` [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable Ze Huang
2026-07-26 19:42 ` [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Ze Huang
0 siblings, 2 replies; 7+ messages in thread
From: Ze Huang @ 2026-07-26 19:42 UTC (permalink / raw)
To: Alexey Brodkin, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Joel Stanley,
Andrew Jeffery, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Linus Walleij, Hans de Goede, Alex Lanzano,
Oleksandr Andrushchenko, Philipp Zabel, Uwe Kleine-König,
Marian Cichy
Cc: dri-devel, linux-kernel, linux-aspeed, linux-arm-kernel, imx,
xen-devel, Ze Huang
struct drm_simple_display_pipe was meant to simplify simple DRM
drivers, but instead adds an extra wrapper around normal DRM atomic
helper setup. As noted in Documentation/gpu/todo.rst, remaining users
should be converted to regular atomic helpers and stop depending on the
simple-KMS interfaces.
Each patch replaces drm_simple_display_pipe_init() with explicit
primary plane, CRTC and encoder setup, and moves the old simple-pipe
callbacks into regular plane and CRTC helper callbacks named according
to local driver conventions.
The conversions preserve helper behavior that used to be implicit in
drm_simple_kms_helper.c, including plane-state validation, CRTC
primary-plane checks, affected-plane propagation, framebuffer prepare
handling, and existing event/vblank flow where applicable.
Result is less helper indirection and more explicit driver-side atomic
wiring, with no remaining simple-KMS dependency in these drivers.
Changes are build-tested only. No hardware testing has been performed.
This patch is based on drm-next-2026-06-27.
AI usage disclosure:
- AI tools were also used to review the code and suggest code changes for
the DRM atomic conversion.
Thanks,
Ze Huang
Signed-off-by: Ze Huang <ze.huang@oss.qualcomm.com>
---
Changes in v3:
- Add a new preparatory patch to avoid duplicate clk_per enables in
imx-lcdc pipe update after CRTC enable.
- Use commit-local atomic state in enable/update/check paths.
- Link to v2: https://patch.msgid.link/20260716-drm-simple-kms-removal-v2-1-7e4a6b8528e6@oss.qualcomm.com/
Changes in v2:
- use 'commit' as name of struct drm_atomic_commit in atomic helpers
- improve control flow in *_crtc_helper_atomic_check() and
*_plane_helper_atomic_check()
- Moved page-flip/vblank event handling out of plane update paths and into
CRTC atomic_flush(), using atomic_flush and disable paths
- decide clock disable by old crtc state
- add calls to drm_crtc_vblank_on() and drm_crtc_vblank_off() in crtc
atomic_enable() and atomic_disbale()
- test crtc_state->enable before mode size check
- Link to v1: https://patch.msgid.link/20260705-drm-simple-kms-removal-v1-0-b4e1ca053623@oss.qualcomm.com
---
Ze Huang (2):
drm/imx/lcdc: avoid duplicate clk_per enable
drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers
drivers/gpu/drm/imx/lcdc/imx-lcdc.c | 263 ++++++++++++++++++++++++++----------
1 file changed, 190 insertions(+), 73 deletions(-)
---
base-commit: 3696d07837d1df13a5603d77f667685e7dfb3c53
change-id: 20260704-drm-simple-kms-removal-01a031c6a129
Best regards,
--
Ze Huang <ze.huang@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable 2026-07-26 19:42 [PATCH v3 0/2] drm: replace simple display pipe users with atomic helpers Ze Huang @ 2026-07-26 19:42 ` Ze Huang 2026-07-26 19:59 ` sashiko-bot 2026-07-26 19:42 ` [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Ze Huang 1 sibling, 1 reply; 7+ messages in thread From: Ze Huang @ 2026-07-26 19:42 UTC (permalink / raw) To: Alexey Brodkin, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Joel Stanley, Andrew Jeffery, Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Linus Walleij, Hans de Goede, Alex Lanzano, Oleksandr Andrushchenko, Philipp Zabel, Uwe Kleine-König, Marian Cichy Cc: dri-devel, linux-kernel, linux-aspeed, linux-arm-kernel, imx, xen-devel, Ze Huang The simple-KMS helper calls the pipe update after enabling the CRTC. On an enable commit, imx_lcdc_pipe_enable() already programs the mode and enables clk_per. The following pipe update sees the plane move from no CRTC to the active CRTC, treats it as a mode update, and calls imx_lcdc_update_hw_registers() again. That second call has no old CRTC state to disable clk_per first, but it enables clk_per again at the end. The disable path only drops one reference, leaving clk_per enabled after each on/off cycle. Skip the register update from the pipe update path when the CRTC already needs a modeset. The enable path has already programmed the hardware for that commit; keep the event handling in pipe update unchanged. Fixes: c87e859cdeb5 ("drm/imx/lcdc: Implement DRM driver for imx25") Signed-off-by: Ze Huang <ze.huang@oss.qualcomm.com> --- drivers/gpu/drm/imx/lcdc/imx-lcdc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c index f52832b43aca..81024f7d9e96 100644 --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c @@ -311,7 +311,8 @@ static void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe, else if (old_crtc != crtc) mode_changed = true; - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); + if (!drm_atomic_crtc_needs_modeset(crtc->state)) + imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); if (event) { crtc->state->event = NULL; -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable 2026-07-26 19:42 ` [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable Ze Huang @ 2026-07-26 19:59 ` sashiko-bot 2026-07-27 1:38 ` Ze Huang 0 siblings, 1 reply; 7+ messages in thread From: sashiko-bot @ 2026-07-26 19:59 UTC (permalink / raw) To: Ze Huang; +Cc: imx, Frank.Li, dri-devel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Calls to sleeping functions (`clk_prepare_enable` and `clk_disable_unprepare`) from the plane atomic update and CRTC enable/disable callbacks, violating DRM atomic context requirements. -- --- Patch [1]: [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable --- commit 48c4f154b28e9de1d485ea56766c8a24df823739 Author: Ze Huang <ze.huang@oss.qualcomm.com> drm/imx/lcdc: avoid duplicate clk_per enable The simple-KMS helper calls the pipe update after enabling the CRTC, which causes imx_lcdc_update_hw_registers() to be called again. This patch avoids the duplicate clock enable by skipping the register update when the CRTC already needs a modeset. > diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > index c67fe80b8115e..e00e52c1c46ec 100644 > --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > @@ -310,7 +310,8 @@ static void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe, > else if (old_crtc != crtc) > mode_changed = true; > > - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); > + if (!drm_atomic_crtc_needs_modeset(crtc->state)) > + imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); [Severity: High] This is a pre-existing issue, but does calling imx_lcdc_update_hw_registers() from imx_lcdc_pipe_update() risk sleeping in an atomic context? imx_lcdc_pipe_update() acts as a plane atomic update callback, which executes in an atomic context where sleeping is forbidden. However, it calls imx_lcdc_update_hw_registers(), which in turn calls sleeping functions: imx_lcdc_update_hw_registers() { ... clk_disable_unprepare(lcdc->clk_per); ... clk_prepare_enable(lcdc->clk_per); ... } Will this cause 'scheduling while atomic' panics when a plane format changes without a full modeset? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260727-drm-simple-kms-removal-v3-0-de36e534f7a1@oss.qualcomm.com?part=1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable 2026-07-26 19:59 ` sashiko-bot @ 2026-07-27 1:38 ` Ze Huang 0 siblings, 0 replies; 7+ messages in thread From: Ze Huang @ 2026-07-27 1:38 UTC (permalink / raw) To: sashiko-reviews, Ze Huang; +Cc: imx, Frank.Li, dri-devel On Mon Jul 27, 2026 at 3:59 AM CST, sashiko-bot wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > > Pre-existing issues: > - [High] Calls to sleeping functions (`clk_prepare_enable` and `clk_disable_unprepare`) from the plane atomic update and CRTC enable/disable callbacks, violating DRM atomic context requirements. > -- > > --- Patch [1]: [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable --- > commit 48c4f154b28e9de1d485ea56766c8a24df823739 > Author: Ze Huang <ze.huang@oss.qualcomm.com> > > drm/imx/lcdc: avoid duplicate clk_per enable > > The simple-KMS helper calls the pipe update after enabling the CRTC, > which causes imx_lcdc_update_hw_registers() to be called again. This > patch avoids the duplicate clock enable by skipping the register update > when the CRTC already needs a modeset. > >> diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c >> index c67fe80b8115e..e00e52c1c46ec 100644 >> --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c >> +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c >> @@ -310,7 +310,8 @@ static void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe, >> else if (old_crtc != crtc) >> mode_changed = true; >> >> - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); >> + if (!drm_atomic_crtc_needs_modeset(crtc->state)) >> + imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); > > [Severity: High] > This is a pre-existing issue, but does calling imx_lcdc_update_hw_registers() > from imx_lcdc_pipe_update() risk sleeping in an atomic context? > > imx_lcdc_pipe_update() acts as a plane atomic update callback, which executes > in an atomic context where sleeping is forbidden. However, it calls > imx_lcdc_update_hw_registers(), which in turn calls sleeping functions: > > imx_lcdc_update_hw_registers() { > ... > clk_disable_unprepare(lcdc->clk_per); > ... > clk_prepare_enable(lcdc->clk_per); > ... > } > > Will this cause 'scheduling while atomic' panics when a plane format changes > without a full modeset? False positive ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers 2026-07-26 19:42 [PATCH v3 0/2] drm: replace simple display pipe users with atomic helpers Ze Huang 2026-07-26 19:42 ` [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable Ze Huang @ 2026-07-26 19:42 ` Ze Huang 2026-07-26 19:55 ` sashiko-bot 1 sibling, 1 reply; 7+ messages in thread From: Ze Huang @ 2026-07-26 19:42 UTC (permalink / raw) To: Alexey Brodkin, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Joel Stanley, Andrew Jeffery, Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Linus Walleij, Hans de Goede, Alex Lanzano, Oleksandr Andrushchenko, Philipp Zabel, Uwe Kleine-König, Marian Cichy Cc: dri-devel, linux-kernel, linux-aspeed, linux-arm-kernel, imx, xen-devel, Ze Huang Convert i.MX LCDC to explicit primary plane, CRTC and encoder objects. Keep no-scaling plane check and GEM framebuffer prepare callback from simple-KMS path. Wire the vblank lifecycle explicitly with CRTC vblank callbacks and drm_crtc_vblank_on()/drm_crtc_vblank_off(). Use the old CRTC state in the disable path for clock unwinding so the clock reference count remains paired with the previous active state. Signed-off-by: Ze Huang <ze.huang@oss.qualcomm.com> --- drivers/gpu/drm/imx/lcdc/imx-lcdc.c | 264 ++++++++++++++++++++++++++---------- 1 file changed, 190 insertions(+), 74 deletions(-) diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c index 81024f7d9e96..4835f01954f7 100644 --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2020 Marian Cichy <M.Cichy@pengutronix.de> #include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> #include <drm/drm_bridge.h> #include <drm/drm_bridge_connector.h> #include <drm/drm_damage_helper.h> @@ -14,9 +15,9 @@ #include <drm/drm_gem_dma_helper.h> #include <drm/drm_gem_framebuffer_helper.h> #include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h> #include <drm/drm_vblank.h> #include <linux/bitfield.h> #include <linux/clk.h> @@ -102,7 +103,9 @@ struct imx_lcdc { struct drm_device drm; - struct drm_simple_display_pipe pipe; + struct drm_plane plane; + struct drm_crtc crtc; + struct drm_encoder encoder; struct drm_connector *connector; void __iomem *base; @@ -135,14 +138,15 @@ static unsigned int imx_lcdc_get_format(unsigned int drm_format) } } -static void imx_lcdc_update_hw_registers(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *old_state, +static void imx_lcdc_update_hw_registers(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state, + struct drm_plane_state *new_state, bool mode_set) { - struct drm_crtc *crtc = &pipe->crtc; - struct drm_plane_state *new_state = pipe->plane.state; + struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(crtc->dev); + const struct drm_display_mode *mode = &new_crtc_state->mode; struct drm_framebuffer *fb = new_state->fb; - struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev); u32 lpcr, lvcr, lhcr; u32 framesize; dma_addr_t addr; @@ -155,24 +159,24 @@ static void imx_lcdc_update_hw_registers(struct drm_simple_display_pipe *pipe, return; /* Disable PER clock to make register write possible */ - if (old_state && old_state->crtc && old_state->crtc->enabled) + if (old_crtc_state && old_crtc_state->enable) clk_disable_unprepare(lcdc->clk_per); /* Framesize */ - framesize = FIELD_PREP(IMX21LCDC_LSR_XMAX, crtc->mode.hdisplay >> 4) | - FIELD_PREP(IMX21LCDC_LSR_YMAX, crtc->mode.vdisplay); + framesize = FIELD_PREP(IMX21LCDC_LSR_XMAX, mode->hdisplay >> 4) | + FIELD_PREP(IMX21LCDC_LSR_YMAX, mode->vdisplay); writel(framesize, lcdc->base + IMX21LCDC_LSR); /* HSYNC */ - lhcr = FIELD_PREP(IMX21LCDC_LHCR_HFPORCH, crtc->mode.hsync_start - crtc->mode.hdisplay - 1) | - FIELD_PREP(IMX21LCDC_LHCR_HWIDTH, crtc->mode.hsync_end - crtc->mode.hsync_start - 1) | - FIELD_PREP(IMX21LCDC_LHCR_HBPORCH, crtc->mode.htotal - crtc->mode.hsync_end - 3); + lhcr = FIELD_PREP(IMX21LCDC_LHCR_HFPORCH, mode->hsync_start - mode->hdisplay - 1) | + FIELD_PREP(IMX21LCDC_LHCR_HWIDTH, mode->hsync_end - mode->hsync_start - 1) | + FIELD_PREP(IMX21LCDC_LHCR_HBPORCH, mode->htotal - mode->hsync_end - 3); writel(lhcr, lcdc->base + IMX21LCDC_LHCR); /* VSYNC */ - lvcr = FIELD_PREP(IMX21LCDC_LVCR_VFPORCH, crtc->mode.vsync_start - crtc->mode.vdisplay) | - FIELD_PREP(IMX21LCDC_LVCR_VWIDTH, crtc->mode.vsync_end - crtc->mode.vsync_start) | - FIELD_PREP(IMX21LCDC_LVCR_VBPORCH, crtc->mode.vtotal - crtc->mode.vsync_end); + lvcr = FIELD_PREP(IMX21LCDC_LVCR_VFPORCH, mode->vsync_start - mode->vdisplay) | + FIELD_PREP(IMX21LCDC_LVCR_VWIDTH, mode->vsync_end - mode->vsync_start) | + FIELD_PREP(IMX21LCDC_LVCR_VBPORCH, mode->vtotal - mode->vsync_end); writel(lvcr, lcdc->base + IMX21LCDC_LVCR); lpcr = readl(lcdc->base + IMX21LCDC_LPCR); @@ -184,19 +188,20 @@ static void imx_lcdc_update_hw_registers(struct drm_simple_display_pipe *pipe, writel(new_state->fb->pitches[0] / 4, lcdc->base + IMX21LCDC_LVPWR); /* Enable PER clock */ - if (new_state->crtc->enabled) + if (new_crtc_state->enable) clk_prepare_enable(lcdc->clk_per); } -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 *pstate = drm_atomic_get_new_plane_state(commit, &lcdc->plane); + 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; @@ -207,7 +212,7 @@ static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe, clk_div = DIV_ROUND_CLOSEST_ULL(clk_get_rate(lcdc->clk_per), mode->clock * 1000); - bpp = imx_lcdc_get_format(plane_state->fb->format->format); + bpp = imx_lcdc_get_format(pstate->fb->format->format); writel(FIELD_PREP(IMX21LCDC_LPCR_PCD, clk_div - 1) | FIELD_PREP(IMX21LCDC_LPCR_LPPOL, hsync_pol) | @@ -231,40 +236,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; } 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, cstate, pstate, 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); } spin_unlock_irq(&lcdc->drm.event_lock); @@ -273,66 +284,151 @@ static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe) writel(0, lcdc->base + IMX21LCDC_LIER); } -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) { + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + } + + 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; } - crtc_state->mode_changed = - old_mode->hdisplay != mode->hdisplay || - old_mode->vdisplay != mode->vdisplay; + if (old_mode->hdisplay != mode->hdisplay || + old_mode->vdisplay != mode->vdisplay) + crtc_state->mode_changed = true; - return 0; + 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; + struct drm_crtc_state *old_crtc_state = NULL; + struct drm_crtc_state *new_crtc_state; 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 || !crtc) + return; + + if (old_crtc) + old_crtc_state = drm_atomic_get_old_crtc_state(commit, old_crtc); + + new_crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + if (!new_crtc_state) + return; + if (old_fb && old_fb->format != fb->format) mode_changed = true; else if (old_crtc != crtc) mode_changed = true; - if (!drm_atomic_crtc_needs_modeset(crtc->state)) - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) + imx_lcdc_update_hw_registers(crtc, old_crtc_state, new_crtc_state, + new_state, mode_changed); +} - 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_state *crtc_state = NULL; + + if (plane_state->crtc) { + crtc_state = drm_atomic_get_crtc_state(commit, plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } - spin_lock_irq(&crtc->dev->event_lock); + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} - 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); +static const struct drm_plane_helper_funcs imx_lcdc_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = imx_lcdc_plane_helper_atomic_check, + .atomic_update = imx_lcdc_plane_helper_atomic_update, +}; - spin_unlock_irq(&crtc->dev->event_lock); - } +static const struct drm_plane_funcs imx_lcdc_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static void imx_lcdc_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + struct drm_pending_vblank_event *event = new_crtc_state->event; + + if (!event) + return; + + new_crtc_state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (new_crtc_state->active && drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); } -static const struct drm_simple_display_pipe_funcs imx_lcdc_pipe_funcs = { - .enable = imx_lcdc_pipe_enable, - .disable = imx_lcdc_pipe_disable, - .check = imx_lcdc_pipe_check, - .update = imx_lcdc_pipe_update, +static const struct drm_crtc_helper_funcs imx_lcdc_crtc_helper_funcs = { + .atomic_check = imx_lcdc_crtc_helper_atomic_check, + .atomic_enable = imx_lcdc_crtc_helper_atomic_enable, + .atomic_disable = imx_lcdc_crtc_helper_atomic_disable, + .atomic_flush = imx_lcdc_crtc_helper_atomic_flush, +}; + +static int imx_lcdc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + return 0; +} + +static void imx_lcdc_crtc_disable_vblank(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_funcs imx_lcdc_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = imx_lcdc_crtc_enable_vblank, + .disable_vblank = imx_lcdc_crtc_disable_vblank, +}; + +static const struct drm_encoder_funcs imx_lcdc_encoder_funcs = { + .destroy = drm_encoder_cleanup, }; static const struct drm_mode_config_funcs imx_lcdc_mode_config_funcs = { @@ -370,7 +466,7 @@ MODULE_DEVICE_TABLE(of, imx_lcdc_of_dev_id); static irqreturn_t imx_lcdc_irq_handler(int irq, void *arg) { struct imx_lcdc *lcdc = arg; - struct drm_crtc *crtc = &lcdc->pipe.crtc; + struct drm_crtc *crtc = &lcdc->crtc; unsigned int status; status = readl(lcdc->base + IMX21LCDC_LISR); @@ -388,6 +484,9 @@ static int imx_lcdc_probe(struct platform_device *pdev) struct imx_lcdc *lcdc; struct drm_device *drm; struct drm_bridge *bridge; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; int irq; int ret; struct device *dev = &pdev->dev; @@ -429,23 +528,40 @@ static int imx_lcdc_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Cannot initialize mode configuration structure\n"); - /* CRTC, Plane, Encoder */ - ret = drm_simple_display_pipe_init(drm, &lcdc->pipe, - &imx_lcdc_pipe_funcs, - imx_lcdc_formats, - ARRAY_SIZE(imx_lcdc_formats), NULL, NULL); + plane = &lcdc->plane; + ret = drm_universal_plane_init(drm, plane, 0, + &imx_lcdc_plane_funcs, + imx_lcdc_formats, + ARRAY_SIZE(imx_lcdc_formats), + NULL, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret < 0) + return dev_err_probe(drm->dev, ret, "Cannot initialize primary plane\n"); + drm_plane_helper_add(plane, &imx_lcdc_plane_helper_funcs); + + crtc = &lcdc->crtc; + ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL, + &imx_lcdc_crtc_funcs, NULL); + if (ret < 0) + return dev_err_probe(drm->dev, ret, "Cannot initialize CRTC\n"); + drm_crtc_helper_add(crtc, &imx_lcdc_crtc_helper_funcs); + + encoder = &lcdc->encoder; + ret = drm_encoder_init(drm, encoder, &imx_lcdc_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); if (ret < 0) - return dev_err_probe(drm->dev, ret, "Cannot setup simple display pipe\n"); + return dev_err_probe(drm->dev, ret, "Cannot initialize encoder\n"); + encoder->possible_crtcs = drm_crtc_mask(crtc); ret = drm_vblank_init(drm, drm->mode_config.num_crtc); if (ret < 0) return dev_err_probe(drm->dev, ret, "Failed to initialize vblank\n"); - ret = drm_bridge_attach(&lcdc->pipe.encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return dev_err_probe(drm->dev, ret, "Cannot attach bridge\n"); - lcdc->connector = drm_bridge_connector_init(drm, &lcdc->pipe.encoder); + lcdc->connector = drm_bridge_connector_init(drm, encoder); if (IS_ERR(lcdc->connector)) return dev_err_probe(drm->dev, PTR_ERR(lcdc->connector), "Cannot init bridge connector\n"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers 2026-07-26 19:42 ` [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Ze Huang @ 2026-07-26 19:55 ` sashiko-bot 2026-07-27 1:53 ` Ze Huang 0 siblings, 1 reply; 7+ messages in thread From: sashiko-bot @ 2026-07-26 19:55 UTC (permalink / raw) To: Ze Huang; +Cc: Frank.Li, imx, dri-devel Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [Critical] NULL pointer dereference in `imx_lcdc_crtc_helper_atomic_enable` and `imx_lcdc_update_hw_registers` when the primary plane has no framebuffer. Pre-existing issues: - [High] Sleeping in atomic context by calling clock manipulation functions from the plane `atomic_update` callback. -- --- Patch [2]: [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers --- commit e5bb94188c05e612166e809c2f562025fecbb70c Author: Ze Huang <ze.huang@oss.qualcomm.com> drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Convert i.MX LCDC to explicit primary plane, CRTC and encoder objects. Keep no-scaling plane check and GEM framebuffer prepare callback from simple-KMS path. > diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > index e00e52c1c46ec..e0784ce0125b7 100644 > --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c [ ... ] > @@ -206,7 +211,7 @@ static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe, > > clk_div = DIV_ROUND_CLOSEST_ULL(clk_get_rate(lcdc->clk_per), > mode->clock * 1000); > - bpp = imx_lcdc_get_format(plane_state->fb->format->format); > + bpp = imx_lcdc_get_format(pstate->fb->format->format); [Severity: Critical] Can this dereference a NULL pointer if the primary plane has no framebuffer? If a userspace process issues an atomic modeset commit that enables the CRTC but leaves the primary plane with no framebuffer, pstate->fb would be NULL. The drm_atomic_helper_check_crtc_primary_plane() helper checks if the primary plane is attached, but doesn't guarantee a visible framebuffer. Could this lead to a crash when querying the format here, or when passing the fb to drm_fb_dma_get_gem_addr() inside imx_lcdc_update_hw_registers() just below? [ ... ] > @@ -272,66 +283,151 @@ static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe) [ ... ] > + if (old_fb && old_fb->format != fb->format) > + mode_changed = true; > + else if (old_crtc != crtc) > + mode_changed = true; > + > - if (!drm_atomic_crtc_needs_modeset(crtc->state)) > - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); > + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) > + imx_lcdc_update_hw_registers(crtc, old_crtc_state, new_crtc_state, > + new_state, mode_changed); [Severity: High] This is a pre-existing issue, but does this sleep in an atomic context? When userspace requests a plane update that changes the pixel format but does not alter the display resolution, drm_atomic_crtc_needs_modeset() evaluates to false, but mode_changed is set to true. This causes imx_lcdc_update_hw_registers() to be called with mode_set=true: imx_lcdc_plane_helper_atomic_update() imx_lcdc_update_hw_registers(..., mode_changed); clk_disable_unprepare(lcdc->clk_per); Since clk_disable_unprepare() acquires a mutex and sleeps, is it safe to call this from the plane's atomic_update callback, which executes in atomic context where sleeping is forbidden? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260727-drm-simple-kms-removal-v3-0-de36e534f7a1@oss.qualcomm.com?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers 2026-07-26 19:55 ` sashiko-bot @ 2026-07-27 1:53 ` Ze Huang 0 siblings, 0 replies; 7+ messages in thread From: Ze Huang @ 2026-07-27 1:53 UTC (permalink / raw) To: sashiko-reviews, Ze Huang; +Cc: Frank.Li, imx, dri-devel On Mon Jul 27, 2026 at 3:55 AM CST, sashiko-bot wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > > New issues: > - [Critical] NULL pointer dereference in `imx_lcdc_crtc_helper_atomic_enable` and `imx_lcdc_update_hw_registers` when the primary plane has no framebuffer. > > Pre-existing issues: > - [High] Sleeping in atomic context by calling clock manipulation functions from the plane `atomic_update` callback. > -- > > --- Patch [2]: [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers --- > commit e5bb94188c05e612166e809c2f562025fecbb70c > Author: Ze Huang <ze.huang@oss.qualcomm.com> > > drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers > > Convert i.MX LCDC to explicit primary plane, CRTC and encoder objects. Keep > no-scaling plane check and GEM framebuffer prepare callback from simple-KMS > path. > >> diff --git a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c >> index e00e52c1c46ec..e0784ce0125b7 100644 >> --- a/drivers/gpu/drm/imx/lcdc/imx-lcdc.c >> +++ b/drivers/gpu/drm/imx/lcdc/imx-lcdc.c > [ ... ] >> @@ -206,7 +211,7 @@ static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe, >> >> clk_div = DIV_ROUND_CLOSEST_ULL(clk_get_rate(lcdc->clk_per), >> mode->clock * 1000); >> - bpp = imx_lcdc_get_format(plane_state->fb->format->format); >> + bpp = imx_lcdc_get_format(pstate->fb->format->format); > > [Severity: Critical] > Can this dereference a NULL pointer if the primary plane has no framebuffer? It could not be possible. If the plane was attached to crtc, the fb would be checked [1] in the atomic check phase (before crtc atomic enable): /* either *both* CRTC and FB must be set, or neither */ if (crtc && !fb) { drm_dbg_atomic(plane->dev, "[PLANE:%d:%s] CRTC set but no FB\n", plane->base.id, plane->name); return -EINVAL; [1] https://elixir.bootlin.com/linux/v7.1.3/source/drivers/gpu/drm/drm_atomic.c#L715 > > If a userspace process issues an atomic modeset commit that enables the CRTC > but leaves the primary plane with no framebuffer, pstate->fb would be NULL. > The drm_atomic_helper_check_crtc_primary_plane() helper checks if the > primary plane is attached, but doesn't guarantee a visible framebuffer. > > Could this lead to a crash when querying the format here, or when passing > the fb to drm_fb_dma_get_gem_addr() inside imx_lcdc_update_hw_registers() > just below? > > [ ... ] >> @@ -272,66 +283,151 @@ static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe) > [ ... ] >> + if (old_fb && old_fb->format != fb->format) >> + mode_changed = true; >> + else if (old_crtc != crtc) >> + mode_changed = true; >> + >> - if (!drm_atomic_crtc_needs_modeset(crtc->state)) >> - imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); >> + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) >> + imx_lcdc_update_hw_registers(crtc, old_crtc_state, new_crtc_state, >> + new_state, mode_changed); > > [Severity: High] > This is a pre-existing issue, but does this sleep in an atomic context? > > When userspace requests a plane update that changes the pixel format but > does not alter the display resolution, drm_atomic_crtc_needs_modeset() > evaluates to false, but mode_changed is set to true. > > This causes imx_lcdc_update_hw_registers() to be called with mode_set=true: > > imx_lcdc_plane_helper_atomic_update() > imx_lcdc_update_hw_registers(..., mode_changed); > clk_disable_unprepare(lcdc->clk_per); > > Since clk_disable_unprepare() acquires a mutex and sleeps, is it safe to > call this from the plane's atomic_update callback, which executes in > atomic context where sleeping is forbidden? False positive ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-27 1:54 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-26 19:42 [PATCH v3 0/2] drm: replace simple display pipe users with atomic helpers Ze Huang 2026-07-26 19:42 ` [PATCH v3 1/2] drm/imx/lcdc: avoid duplicate clk_per enable Ze Huang 2026-07-26 19:59 ` sashiko-bot 2026-07-27 1:38 ` Ze Huang 2026-07-26 19:42 ` [PATCH v3 2/2] drm/imx: replace struct drm_simple_display_pipe with regular atomic helpers Ze Huang 2026-07-26 19:55 ` sashiko-bot 2026-07-27 1:53 ` Ze Huang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox