From: sashiko-bot@kernel.org
To: "Cody Kang" <codykang.hk@gmail.com>
Cc: neil.armstrong@linaro.org, olteanv@gmail.com, robh@kernel.org,
linux-phy@lists.infradead.org, devicetree@vger.kernel.org,
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
conor+dt@kernel.org
Subject: Re: [PATCH v2 09/17] drm/spacemit: add Saturn DPU hardware backend
Date: Sun, 09 Aug 2026 13:31:42 +0000 [thread overview]
Message-ID: <20260809133143.5776C1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260809-k3-display-v2-9-327d7910bf71@gmail.com>
Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] Dynamic memory allocation using GFP_KERNEL inside plane atomic update, disable, and CRTC flush paths.
- [High] If cmdlist memory allocation fails, the driver falls back to direct MMIO writes during atomic updates, breaking transaction semantics.
- [High] Unsynchronized lockless access to `flip_done` between ISR and timer handler allows double `drm_crtc_vblank_put()`.
- [High] Dereferencing `state->fb` without checking for NULL causes a crash when configuring pixel blending for solid fill planes.
- [High] Potential NULL pointer dereference of `state->fb` in `saturn_cal_layer_fbcmem_size`.
- [High] The `dpu_init` function uses the sleeping function `usleep_range()` while executing from an atomic commit path.
- [High] The `cfg_rdy_timer` is not synchronously cancelled during device teardown, leading to a potential use-after-free.
- [High] The VBLANK ISR and timer handler directly access `crtc->state->event`, causing a race condition with concurrent atomic commits.
- [High] Silently dropped cmdlist rows cause the hardware DMA to read out-of-bounds memory.
- [Medium] Hardware clocks are disabled in the same forward order they were enabled, violating clock dependencies.
- [Medium] C bitfields are used for defining hardware DMA descriptor layouts.
- [Medium] Unchecked `drm_crtc_vblank_get` return value leads to refcount underflow.
- [Medium] Double-clearing of underrun interrupt status due to stale irq_raw snapshot.
- [Medium] Useless and contextually incorrect read memory barrier (dma_rmb).
--
--- Patch [9]: [PATCH v2 09/17] drm/spacemit: add Saturn DPU hardware backend ---
commit 203a676effd210733651accaca4eebc02e32d9c2
Author: Cody Kang <codykang.hk@gmail.com>
Subject: drm/spacemit: add Saturn DPU hardware backend
This commit adds the register-level programming of the Saturn instance,
including clock and reset bring-up, scene control, and timing generator setup.
The backend is exposed as an ops table for the KMS layer.
> diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c
[ ... ]
> +static int dpu_disable_clocks(struct spacemit_crtc *a_crtc)
> +{
> + struct dpu_clk_context *clk_ctx = &a_crtc->clk_ctx;
> +
> + trace_dpu_disable_clocks(a_crtc->dev_id);
> +
> + clk_disable_unprepare(clk_ctx->pxclk);
> + clk_disable_unprepare(clk_ctx->mclk);
> + clk_disable_unprepare(clk_ctx->escclk);
> +
> + clk_disable_unprepare(clk_ctx->aclk);
> + clk_disable_unprepare(clk_ctx->dscclk);
[Severity: Medium]
Providers should usually be disabled after their consumers, but this matches
the exact forward order they were enabled in dpu_enable_clocks().
Are these clocks being disabled in the correct order?
[ ... ]
> +static int dpu_init(struct spacemit_crtc *a_crtc)
> +{
> + unsigned int timeout = 1000;
> + struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
> + struct spacemit_hw_device *hwdev = priv->hwdev;
> +
> + if (!a_crtc->clocks_on)
> + return 0;
> +
> + while (timeout) {
> + if (hwdev->get_cfg_rdy(a_crtc, hwdev) == 0)
> + break;
> + usleep_range(100, 200);
[Severity: High]
dpu_init() appears to be called from the CRTC's atomic enable sequence,
where sleeping is strictly forbidden and can cause kernel warnings or
deadlocks.
Is it safe to call a sleeping function like usleep_range() here?
[ ... ]
> +static void dpu_uninit(struct spacemit_crtc *a_crtc)
> +{
> + struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
> + struct spacemit_hw_device *hwdev = priv->hwdev;
> +
> + if (!a_crtc->clocks_on)
> + return;
> +
> + trace_dpu_uninit(a_crtc->dev_id);
> + hwdev->irq_enable(a_crtc, false);
> +}
[Severity: High]
If the driver unbinds while the timer is pending, the timer callback could
execute on freed spacemit_crtc memory, leading to a use-after-free.
Should there be a synchronous cancellation of cfg_rdy_timer here?
[ ... ]
> +static inline void dpu_isr_vblank(struct spacemit_crtc *a_crtc, bool *flip)
> +{
> + struct drm_crtc *crtc = &a_crtc->crtc;
> + struct drm_device *drm = crtc->dev;
> + struct drm_pending_vblank_event *event;
> +
> + drm_crtc_handle_vblank(crtc);
> +
> + if (*flip)
> + return;
> + *flip = true;
> +
> + /*
> + * event_lock serialises this against the commit arming
> + * crtc->state->event.
> + */
> + spin_lock(&drm->event_lock);
> + event = crtc->state->event;
> + if (event) {
[Severity: High]
The crtc->state can be swapped asynchronously by
drm_atomic_helper_swap_state(), which could cause this handler to point to a
new event and lose the old one if multiple commits are queued rapidly.
Is it safe to directly dereference crtc->state->event dynamically from an
asynchronous context?
[ ... ]
> +static u32 dpu_online_isr(struct spacemit_crtc *a_crtc)
> +{
[ ... ]
> + irq_raw = hwdev->get_int_sts(hwdev, dev_id);
> + trace_dpu_isr_status("ONLINE", irq_raw);
> + irq_bit = hwdev->get_irq_bit(INT_UNDERRUN, dev_id);
> + irq_ur_bit = irq_raw & irq_bit;
> + if (irq_ur_bit && !a_crtc->ur_reported) {
> + hwdev->clr_int_sts(a_crtc, irq_ur_bit, dev_id);
> + trace_dpu_isr_status("underrun", irq_ur_bit);
> + drm_err_ratelimited(a_crtc->crtc.dev, "underrun\n");
> + a_crtc->ur_reported = true;
> + }
> + irq_bit = hwdev->get_irq_bit(INT_CFG_RDY, dev_id);
> + if (irq_raw & irq_bit) {
> + hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
> + trace_dpu_isr_status("cfg_rdy_clr", irq_raw & irq_bit);
> + /*
> + * A fired watchdog already sent the event and put this flip's
> + * vblank reference; re-arming the vsync path would put twice.
> + */
> + if (timer_delete(&a_crtc->cfg_rdy_timer))
> + a_crtc->flip_done = false;
[Severity: High]
If the timer executes on another CPU, timer_delete() returns 0 and both the
ISR and the timer handler could execute lockless modifications to flip_done
and call drm_crtc_vblank_put() twice.
Could this non-synchronous timer cancellation leave the callback running
concurrently?
[ ... ]
> + irq_bit = hwdev->get_irq_bit(INT_REST, dev_id) | irq_ur_bit;
> + if (irq_raw & irq_bit)
> + hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
[Severity: Medium]
The code takes a single snapshot of irq_raw at the start of the ISR, clears
irq_ur_bit if present, and then clears it again here using the stale
snapshot. A new underrun firing in that window could be cleared without
being processed.
Does double-clearing the underrun interrupt status risk losing interrupts?
[ ... ]
> +static void dpu_run(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_state)
> +{
[ ... ]
> + /* flush all config register writes before CFG_RDY is raised */
> + mb();
> +
> + drm_crtc_vblank_get(crtc);
[Severity: Medium]
If getting the vblank fails, the reference is not taken, but the ISR still
unconditionally calls drm_crtc_vblank_put(), which can lead to a vblank
reference count underflow.
Should the return value of drm_crtc_vblank_get() be checked?
> + if (hwdev->enable_cfg_irq)
> + hwdev->enable_cfg_irq(a_crtc, hwdev, true);
> +
> + saturn_ctrl_cfg_ready(a_crtc, true);
> +
> + if (unlikely(a_crtc->is_1st_f)) {
> + drm_dbg(a_crtc->crtc.dev, "DPU start\n");
> + a_crtc->is_1st_f = false;
> + saturn_ctrl_sw_start(a_crtc, true);
> + }
> + dma_rmb();
> +}
[Severity: Medium]
A read memory barrier strictly enforces ordering between CPU reads, but the
preceding operation writes to DMA control registers to start the engine.
Is this read memory barrier required here? It seems semantically out of
place.
> diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c b/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c
[ ... ]
> +void saturn_hee_plane_update_hw_channel(struct drm_plane *plane)
> +{
[ ... ]
> + if (state->fb->format && state->fb->format->has_alpha &&
> + pixel_alpha != DRM_MODE_BLEND_PIXEL_NONE) {
[Severity: High]
For solid fill planes, state->fb is typically NULL. Evaluating
state->fb->format without a NULL check could cause a kernel oops when
userspace submits an atomic commit for a solid-color plane.
Can state->fb be NULL here?
[ ... ]
> + cl_cmp = alloc_cmdlist_regs(CMPS_X_REG);
> +
> + base = CMP_BASE_ADDR[a_crtc->dev_id];
> + dpu_write(hwdev, CMPS_X_REG, base, dst_w, mode->hdisplay, cl_cmp);
> + dpu_write(hwdev, CMPS_X_REG, base, dst_h, mode->vdisplay, cl_cmp);
> +
> + SATURN_WRITE_COMPOSER_LAYER(00);
[Severity: High]
The alloc_cmdlist_regs macro internally uses kzalloc with GFP_KERNEL.
Can this allocation sleep in an atomic context and trigger deadlock warnings,
since it is called from the plane atomic update callback?
Also, if this allocation fails and returns NULL, cl_cmp is passed directly to
SATURN_WRITE_COMPOSER_LAYER and dpu_write. The dpu_write logic falls back to
direct MMIO writes when the command list is NULL.
Does this bypass command list synchronization to VBLANK and risk applying
partial state immediately?
[ ... ]
> +struct cmdlist_header {
> + u64 next_list_addr : 39;
> + /* reserved */
> + u32: 1;
> + u32 nod_len : 16;
> + /* 1: the last cmdlist node, 2: pending node, otherwise 0 */
> + u32 nod_type : 2;
> + u32 next_nod_secu : 1;
> + u64 wait_event_low : 5;
> + u64 wait_event : 59;
> + /* reserved */
> + u32: 5;
> +};
[Severity: Medium]
Bitfield packing and ordering are heavily compiler and architecture dependent,
which might produce incorrect structures in memory for the hardware DMA to
read.
Is it safe to use C bitfields for hardware DMA descriptor layouts?
[ ... ]
> +void saturn_hee_cmdlist_fill_data_row(struct cmdlist *cl, u32 strobe,
> + u32 offset, u32 value[])
> +{
> + struct cmdlist_row *row;
> + u8 i;
> +
> + /*
> + * packing counts rows blindly; drop the row rather than the buffer end
> + */
> + if (WARN_ONCE(CL_HEADER_SZ + (cl->nod_len + 1) * CL_ROW_SZ > cl->size,
> + "cmdlist buffer full (rows %u)\n", cl->nod_len))
> + return;
[Severity: High]
Since this function silently drops the row and returns void, the caller
cmdlist_regs_packing() will blindly increment cl->nod_len regardless of
buffer truncation.
Could this inflated length cause the DMA engine to read past the end of the
initialized buffer?
> diff --git a/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c
[ ... ]
> +int saturn_cal_layer_fbcmem_size(struct drm_plane *plane,
> + struct drm_plane_state *state)
> +{
> + struct spacemit_plane_state *pstate = to_spacemit_plane_state(state);
> + u32 drm_4cc_fmt = pstate->state.fb->format->format;
[Severity: High]
If a plane is being disabled or is in a solid fill mode, the framebuffer
pointer will be NULL, which would cause an immediate crash during the atomic
check phase.
Is it guaranteed that pstate->state.fb is non-NULL here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260809-k3-display-v2-0-327d7910bf71@gmail.com?part=9
next prev parent reply other threads:[~2026-08-09 13:31 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-09 13:14 [PATCH v2 00/17] drm/spacemit: add SpacemiT K3 display support Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 01/17] dt-bindings: display: spacemit: add K3 Saturn DPU controller Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 02/17] dt-bindings: phy: add SpacemiT K3 Innosilicon DP PHY Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 03/17] dt-bindings: display: spacemit: add K3 Innosilicon DP/eDP controller Cody Kang via B4 Relay
2026-08-09 13:25 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 04/17] dt-bindings: soc: spacemit: allow eDP/DP PHY PLL pixel clocks on K3 APMU Cody Kang via B4 Relay
2026-08-09 20:57 ` Rob Herring (Arm)
2026-08-09 13:14 ` [PATCH v2 05/17] phy: spacemit: add Innosilicon DP TX PHY driver Cody Kang via B4 Relay
2026-08-09 13:26 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 06/17] clk: spacemit: k3: parent eDP/DP pixel clock to the PHY PLL Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 07/17] drm/spacemit: add Saturn DPU register model Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU Cody Kang via B4 Relay
2026-08-09 13:32 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 09/17] drm/spacemit: add Saturn DPU hardware backend Cody Kang via B4 Relay
2026-08-09 13:31 ` sashiko-bot [this message]
2026-08-09 13:14 ` [PATCH v2 10/17] drm/spacemit: add Saturn DPU KMS pipeline Cody Kang via B4 Relay
2026-08-09 13:35 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 11/17] drm/spacemit: add Saturn DPU DRM device driver Cody Kang via B4 Relay
2026-08-09 13:35 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 12/17] drm/spacemit: add Innosilicon DP/eDP controller bridge driver Cody Kang via B4 Relay
2026-08-09 13:36 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 13/17] MAINTAINERS: add SpacemiT K3 display driver entry Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 14/17] riscv: dts: spacemit: k3: add display nodes Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 15/17] riscv: dts: spacemit: k3-pico-itx: enable the DisplayPort output Cody Kang via B4 Relay
2026-08-09 13:30 ` sashiko-bot
2026-08-09 13:14 ` [PATCH v2 16/17] riscv: dts: spacemit: k3-com260-ifx: " Cody Kang via B4 Relay
2026-08-09 13:14 ` [PATCH v2 17/17] riscv: defconfig: spacemit: k3: enable display driver Cody Kang via B4 Relay
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=20260809133143.5776C1F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=codykang.hk@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=linux-phy@lists.infradead.org \
--cc=neil.armstrong@linaro.org \
--cc=olteanv@gmail.com \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vkoul@kernel.org \
/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