Devicetree
 help / color / mirror / Atom feed
From: Cody Kang <codykang.hk@gmail.com>
To: sashiko-reviews@lists.linux.dev
Cc: conor+dt@kernel.org, linux-phy@lists.infradead.org,
	vkoul@kernel.org, neil.armstrong@linaro.org,
	dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	robh@kernel.org, olteanv@gmail.com, sashiko-bot@kernel.org
Subject: Re: [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU
Date: Sun, 09 Aug 2026 08:39:00 +0800	[thread overview]
Message-ID: <db9f1190f5096e83bc9af5c082c519a6.codykang.hk@gmail.com> (raw)
In-Reply-To: <20260725050439.2A4CB1F00A3A@smtp.kernel.org>

On Sat, 25 Jul 2026 05:04:38 +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
> - [Critical] DMMU page table size calculation underflow allows arbitrary physical memory access.
> - [Critical] Shallow copy of DMA buffers in atomic state causes active display corruption.
> - [High] crtc_cmdlist_atomic_commit() passes a guaranteed NULL pointer to the hardware command list execution callback.
> - [High] Global `cmdlist_groups` array causes data races across concurrent atomic commits.
> - [High] Unconditional dereference of `first_cl` in `cmdlist_atomic_commit()` causes a kernel panic if the command list group is empty.
> - [High] DMMU maps only the first GEM object, breaking multi-planar framebuffers.
> - [High] NULL pointer dereference in `dpu_plane_info` tracepoint.
> - [High] Sleeping in atomic context via `drm_gem_shmem_get_pages_sgt()`.
> - [High] Missing bounds check on `nod_len` causes DMA buffer overflow.
> - [Medium] Unaligned base virtual addresses are used for DMMU translation regions.
> - [Medium] Memory leak of DMA buffers upon atomic state destruction.
> - [Medium] Speculative array out-of-bounds access in `cmdlist_sort_by_group()`.
> --
>
> --- Patch [8]: [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU ---
> commit dc983d68951c3577447bb897e17c833e557da908
> Author: Cody Kang <codykang.hk@gmail.com>
>
> drm/spacemit: add Saturn DPU core types, cmdlist and display MMU
>
> This commit introduces the shared driver types, MMIO accessors, trace
> events, command-list builder, and DMMU table code for the Saturn DPU.
> It enables batched register programming by building per-module register
> lists in memory, which the hardware command-list engine replays.
>
> > diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_trace.h b/drivers/gpu/drm/spacemit/dpu/dpu_trace.h
> > index 0000000000000..abfe34a67a4bd 100644
> > --- /dev/null
> > +++ b/drivers/gpu/drm/spacemit/dpu/dpu_trace.h
> [ ... ]
> > +TRACE_EVENT(dpu_plane_info,
> > +	    TP_PROTO(struct drm_plane_state *state, struct drm_framebuffer *fb,
> > +		     u32 rdma_id, u32 alpha, u32 rotation),
> > +	TP_ARGS(state, fb, rdma_id, alpha, rotation),
> > +	TP_STRUCT__entry(
> > +			__field(u32, rdma_id)
> > +			__field(u32, src_w)
> > +			__field(u32, src_h)
> > +			__field(u32, src_x)
> > +			__field(u32, src_y)
> > +			__field(u32, crtc_w)
> > +			__field(u32, crtc_h)
> > +			__field(u32, crtc_x)
> > +			__field(u32, crtc_y)
> > +			__field(u32, width)
> > +			__field(u32, height)
> > +			__field(u32, format)
> > +			__field(u32, blend_mode)
> > +			__field(u32, alpha)
> > +			__field(u32, zpos)
> > +			__field(u32, rotation)
> > +	),
> > +	TP_fast_assign(
> > +			__entry->rdma_id = rdma_id;
> > +			__entry->src_w = state->src_w >> 16;
> > +			__entry->src_h = state->src_h >> 16;
> > +			__entry->src_x = state->src_x >> 16;
> > +			__entry->src_y = state->src_y >> 16;
> > +			__entry->crtc_w = state->crtc_w;
> > +			__entry->crtc_h = state->crtc_h;
> > +			__entry->crtc_x = state->crtc_x;
> > +			__entry->crtc_y = state->crtc_y;
> > +			__entry->width = fb->width;
> > +			__entry->height = fb->height;
> > +			__entry->format = fb->format->format;
>
> [Severity: High]
> Can this dereference a NULL pointer? When a plane is disabled during an
> atomic commit, state->fb might be NULL. Unconditionally reading fb->width
> here could cause a kernel panic if tracing is enabled.

The tracepoint's only caller passes a validated fb from an enabled
plane's update path; disabled planes never reach it.

> > +			__entry->blend_mode = state->pixel_blend_mode;
> > +			__entry->alpha = alpha;
> > +			__entry->zpos = state->zpos;
> > +			__entry->rotation = rotation;
> > +	),
> [ ... ]
> > diff --git a/drivers/gpu/drm/spacemit/spacemit_cmdlist.c b/drivers/gpu/drm/spacemit/spacemit_cmdlist.c
> > index 0000000000000..787d38e9e135f 100644
> > --- /dev/null
> > +++ b/drivers/gpu/drm/spacemit/spacemit_cmdlist.c
> [ ... ]
> > +void cmdlist_regs_packing(struct cmdlist *cl, enum cmdlist_mode_type mod,
> > +			  struct cmdlist_regs *cl_regs)
> > +{
> [ ... ]
> > +	for (i = 0; i < cl_regs->size;) {
> > +		u32 value[CMDLIST_ROW_REGS] = {0x0};
> > +		u32 offset, strobe = 0;
> > +		u8 regs_in_row = 0;
> > +
> > +		if (cl_regs->flags[i]) {
> > +			offset = cl_regs->base + i * sizeof(u32);
> > +			for (u8 j = 0; j < CMDLIST_ROW_REGS; j++) {
> > +				index = i + j;
> > +				if (likely(index < cl_regs->size)) {
> > +					if (cl_regs->flags[index]) {
> > +						value[j] = reg_base[index];
> > +						regs_in_row++;
> > +						strobe |= CMDLIST_REG_STROBE(j);
> > +					}
> > +				} else {
> > +					break;
> > +				}
> > +			}
> > +			hwdev->cmdlist_fill_data_row(cl, strobe, offset, value);
> > +			/*
> > +			 * the row covers the whole window; a shorter stride
> > +			 * would revisit covered words as duplicate rows
> > +			 */
> > +			i += CMDLIST_ROW_REGS;
> > +			cl->nod_len++;
>
> [Severity: High]
> Is there a risk of overflowing the DMA command list buffer here? The loop
> increments cl->nod_len and writes to memory, but there doesn't seem to be
> a bounds check to ensure the packed rows do not exceed the allocated
> capacity in cl->size.

Not reachable: the 4 KiB cmdlist holds 255 rows, the largest
register window (146 words) packs into at most 49, and no single
list carries more than a few windows. v2 I can add a WARN_ONCE
guard in the row-fill helpers rather than trusting the arithmetic.

> > +		} else {
> > +			i++;
> > +		}
> > +	}
> > +
> > +	drm_dbg(a_crtc->crtc.dev, "row_num = %d\n", cl->nod_len);
> > +}
> > +
> > +void cmdlist_sort_by_group(struct drm_crtc *crtc)
> > +{
> > +	struct cmdlist *first_cl;
> > +	struct cmdlist *last_cl;
> > +	struct cmdlist *p;
> > +	struct cmdlist *prev;
> > +	struct drm_plane *plane;
> > +	struct spacemit_crtc_rdma *rdmas =
> > +			to_spacemit_crtc_state(crtc->state)->rdmas;
> > +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> > +
> > +	drm_atomic_crtc_for_each_plane(plane, crtc) {
> > +		struct spacemit_plane_state *spacemit_pstate =
> > +			to_spacemit_plane_state(plane->state);
> > +		u32 rdma_id = spacemit_pstate->rdma_id;
> > +
> > +		first_cl = &spacemit_pstate->cl;
> > +		if (!first_cl->va)
> > +			continue;
> > +
> > +		last_cl = first_cl;
> > +		last_cl->next = NULL;
> > +
> > +		rdmas[rdma_id].in_use = true;
>
> [Severity: Medium]
> Could rdma_id exceed the bounds of the array? It is used directly as an
> index for the rdmas array without checking if it is within hwdev->rdma_nums.

rdma_id is validated against hwdev->rdma_nums in plane atomic_check
before any array use.

> > +		if (priv->cmdlist_groups[rdma_id]) {
>
> [Severity: High]
> Does modifying the global priv->cmdlist_groups array here cause data races?
> Since this array is stored in the shared spacemit_drm_private structure,
> concurrent atomic commits on multiple CRTCs might overwrite each other's
> command lists without locking.

There is one CRTC per DRM device in this driver; commits on a CRTC
are serialized by the atomic helpers, and the group array is filled
and consumed within a single commit tail.

> > +			p = priv->cmdlist_groups[rdma_id];
> > +			prev = NULL;
> > +			while (p) {
> > +				if (cl_to_spacemit_pstate(p)->state.crtc_y <
> > +				    spacemit_pstate->state.crtc_y) {
> > +					prev = p;
> > +					p = p->next;
> > +				} else {
> > +					break;
> > +				}
> > +			}
> > +			if (!prev) {
> > +				priv->cmdlist_groups[rdma_id] = first_cl;
> > +				last_cl->next = p;
> > +			} else {
> > +				prev->next = first_cl;
> > +				last_cl->next = p;
> > +			}
> > +		} else {
> > +			priv->cmdlist_groups[rdma_id] = first_cl;
> > +		}
> > +	}
> > +}
> > +
> > +void cmdlist_atomic_commit(struct drm_crtc *crtc,
> > +			   struct drm_crtc_state *old_state)
> > +{
> [ ... ]
> > +	for (i = 0; i < hwdev->rdma_nums; i++) {
> > +		if (old_rdmas[i].in_use)
> > +			hwdev->enable_cmdlist(a_crtc, hwdev, i, false);
> > +
> > +		if (cur_rdmas[i].in_use) {
> > +			drm_dbg(crtc->dev, "cmdlist group = %d\n", i);
> > +			cur_cl = priv->cmdlist_groups[i];
> > +			first_cl = cur_cl;
> > +			while (cur_cl) {
> > +				hwdev->cmdlist_fill_conf_row(cur_cl, hwdev,
> > +					CMDLIST_CMP_INVALID);
> > +				cmdlist_dump_node(cur_cl);
> > +				cur_cl = cur_cl->next;
> > +			}
> > +			chy = cl_to_spacemit_pstate(first_cl)->state.crtc_y;
>
> [Severity: High]
> Could first_cl be NULL here? If cur_rdmas[i].in_use is true but
> priv->cmdlist_groups[i] is empty, first_cl would be NULL. This can happen
> if planes are removed from the state but in_use was copied as true from the
> old state. The cl_to_spacemit_pstate macro would yield a negative offset
> from NULL, resulting in a crash.

in_use cannot be inherited: crtc duplicate_state allocates a zeroed
rdmas array for every state, so it is only set by
cmdlist_sort_by_group() for planes whose cmdlist exists in the same
commit.

> > +			addrl = (lower_32_bits(priv->cmdlist_groups[i]->pa) &
> > +				 CMDLIST_ADDRL_ALIGN_MASK) >> CMDLIST_ADDRL_ALIGN_BITS;
> > +
> > +			addrh = upper_32_bits(priv->cmdlist_groups[i]->pa);
> > +			hwdev->cfg_cmdlist(hwdev, i, chy, addrl, addrh);
> > +			hwdev->enable_cmdlist(a_crtc, hwdev, i, true);
> > +			priv->cmdlist_groups[i] = NULL;
> > +		}
> > +	}
> > +}
> > +
> [ ... ]
> > +void crtc_cmdlist_atomic_commit(struct spacemit_crtc *a_crtc)
> > +{
> > +	struct cmdlist *cur_cl;
> > +	struct drm_crtc *crtc = &a_crtc->crtc;
> > +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> > +	struct spacemit_hw_device *hwdev = priv->hwdev;
> > +	u8 crtc_id = drm_crtc_index(crtc);
> > +	u8 dev_id = a_crtc->dev_id;
> > +
> > +	drm_dbg(crtc->dev, "cmdlist group = %d\n",
> > +		hwdev->rdma_nums + crtc_id);
> > +	cur_cl = priv->cmdlist_groups[hwdev->rdma_nums + crtc_id];
> > +	while (cur_cl) {
> > +		hwdev->cmdlist_fill_conf_row(cur_cl, hwdev, dev_id);
> > +		cmdlist_dump_node(cur_cl);
> > +		cur_cl = cur_cl->next;
> > +	}
> > +	hwdev->crtc_cmdlist(cur_cl, hwdev, priv, crtc_id, dev_id);
>
> [Severity: High]
> Does this pass a guaranteed NULL pointer to the hardware callback?
> The while loop exhausts cur_cl to NULL, which is then immediately passed
> to hwdev->crtc_cmdlist. It looks like the head of the list should be
> passed instead.

The callback never reads that parameter; it re-reads the group from
spacemit_drm_private itself. Will drop the unused parameter in v2 so
this stops reading like a bug.

> > +}
> > +
> > +/* Called from atomic_check(), where -ENOMEM is still an answer. */
> > +int spacemit_cmdlist_alloc(struct device *dev, struct cmdlist *cl)
> > +{
> > +	if (cl->va)
> > +		return 0;
>
> [Severity: Critical]
> Does returning early when cl->va is present cause active display corruption?
> Because struct cmdlist is embedded directly in the plane state, DRM's state
> duplication will shallow-copy cl->va. If atomic_update mutates this same
> virtual address while the hardware is actively reading it from the old
> state, it could lead to screen tearing or DPU hangs. DMA buffers for
> command lists generally need to be multi-buffered or allocated per-state.

There is no shallow copy: duplicate_state zeroes the embedded
cmdlist, atomic_check allocates a fresh buffer for each new state,
and destroy_state frees it.

> > +
> > +	cl->size = PER_CMDLIST_SIZE;
> > +	cl->va = dma_alloc_coherent(dev, cl->size, &cl->pa,
> > +				    GFP_KERNEL | __GFP_ZERO);
> > +	if (!cl->va)
> > +		return -ENOMEM;
>
> [Severity: Medium]
> Is there a corresponding dma_free_coherent() during state destruction?
> If the allocated cl->va is never freed when the atomic state is destroyed,
> this might cause a steady leak of DMA memory.

Yes: spacemit_plane_atomic_destroy_state() frees both the cmdlist
and the DMMU table.

> > +
> > +	return 0;
> > +}
> [ ... ]
> > diff --git a/drivers/gpu/drm/spacemit/spacemit_dmmu.c b/drivers/gpu/drm/spacemit/spacemit_dmmu.c
> > index 0000000000000..2ae30dd1a4f23 100644
> > --- /dev/null
> > +++ b/drivers/gpu/drm/spacemit/spacemit_dmmu.c
> [ ... ]
> > +int spacemit_dmmu_map(struct drm_framebuffer *fb, struct dpu_mmu_tbl *mmu_tbl,
> > +		      u8 tbu_id, struct cmdlist_regs *cl_rdma,
> > +		 struct drm_plane *plane)
> > +{
> > +	struct spacemit_drm_private *priv = fb->dev->dev_private;
> > +	struct spacemit_hw_device *hwdev = priv->hwdev;
> > +	const struct drm_format_info *format = NULL;
> > +	struct sg_table *sgt = NULL;
> > +	u32 total_size, offset1, offset2;
> > +	struct tbu_instance tbu = { };
> > +	u8 plane_num;
> > +	u32 val;
> > +
> > +	format = fb->format;
> > +	sgt = drm_gem_shmem_get_pages_sgt(to_drm_gem_shmem_obj(fb->obj[0]));
>
> [Severity: High]
> Will this sleep in atomic context? This function appears to be called
> during hardware programming in the atomic commit phase. Calling
> drm_gem_shmem_get_pages_sgt allocates memory and can acquire a mutex,
> which is forbidden in atomic context. Buffer mapping typically needs to be
> deferred to the sleepable prepare_fb callback.

This runs from plane atomic_update in the commit tail, which is
process context; sleeping is allowed there.

> [Severity: High]
> Does mapping only fb->obj[0] break multi-planar framebuffers? If userspace
> provides a multi-planar framebuffer where the planes reside in different GEM
> objects, extracting the SG table only for obj[0] will result in incorrect
> physical memory pages for the secondary planes.

Every advertised format is single-planar, so the two- and
three-plane arms cannot execute; will drop them in v2 rather than
harden dead code.

> > +	if (IS_ERR(sgt))
> > +		return PTR_ERR(sgt);
> > +
> > +	plane_num = format->num_planes;
> > +
> > +	total_size = roundup(fb->obj[0]->size, PAGE_SIZE);
> > +
> > +	offset1 = plane_num > 1 ? fb->offsets[1] : total_size;
> > +	offset2 = plane_num > 2 ? fb->offsets[2] : total_size;
> > +
> > +	switch (plane_num) {
> > +	case 3:
> > +		tbu.ttb_pa[2] = mmu_tbl->pa + (offset2 >> PAGE_SHIFT) * 4;
> > +		tbu.tbu_va[2] = TBU_BASE_VA(tbu_id) + offset2;
>
> [Severity: Medium]
> Could adding offset2 directly to TBU_BASE_VA result in unaligned base
> virtual addresses? If userspace supplies an offset that is not a multiple
> of PAGE_SIZE, programming an unaligned virtual address might confuse the
> hardware MMU.

Unreachable for the same reason: single-planar formats only.

> > +		tbu.ttb_size[2] = PAGE_ALIGN(total_size - rounddown(offset2, PAGE_SIZE))
> > +				  >> PAGE_SHIFT;
>
> [Severity: Critical]
> Can this size calculation underflow? If a malicious userspace client
> provides offset2 smaller than offset1, the unsigned subtraction
> offset2 - rounddown(offset1, PAGE_SIZE) will underflow to a massive
> positive value. This could program the DMMU with a huge translation block,
> allowing arbitrary out-of-bounds physical memory reads.

Also unreachable: single-planar formats only, and the framebuffer
core bounds every offsets[] within the GEM object at creation time.

Cody

  reply	other threads:[~2026-08-09  0:39 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-25  4:51 [PATCH RESEND 00/17] drm/spacemit: add SpacemiT K3 display support Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 01/17] dt-bindings: display: spacemit: add K3 Saturn DPU controller Cody Kang via B4 Relay
2026-08-07 22:58   ` Rob Herring (Arm)
2026-07-25  4:51 ` [PATCH RESEND 02/17] dt-bindings: phy: add SpacemiT K3 Innosilicon DP PHY Cody Kang via B4 Relay
2026-08-07 23:00   ` Rob Herring (Arm)
2026-07-25  4:51 ` [PATCH RESEND 03/17] dt-bindings: display: spacemit: add K3 Innosilicon DP/eDP controller Cody Kang via B4 Relay
2026-08-07 23:04   ` Rob Herring
2026-08-08 14:39     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 04/17] dt-bindings: soc: spacemit: allow eDP/DP PHY PLL pixel clocks on K3 APMU Cody Kang via B4 Relay
2026-08-07 23:08   ` Rob Herring
2026-08-08 14:29     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 05/17] phy: spacemit: add Innosilicon DP TX PHY driver Cody Kang via B4 Relay
2026-07-25  5:02   ` sashiko-bot
2026-08-08  9:48     ` Cody Kang
2026-07-29 12:36   ` Uwe Kleine-König
2026-08-08 14:14     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 06/17] clk: spacemit: k3: parent eDP/DP pixel clock to the PHY PLL Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 07/17] drm/spacemit: add Saturn DPU register model Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU Cody Kang via B4 Relay
2026-07-25  5:04   ` sashiko-bot
2026-08-09  0:39     ` Cody Kang [this message]
2026-07-27  7:35   ` Philipp Zabel
2026-08-08 13:07     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 09/17] drm/spacemit: add Saturn DPU hardware backend Cody Kang via B4 Relay
2026-07-25  5:12   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 10/17] drm/spacemit: add Saturn DPU KMS pipeline Cody Kang via B4 Relay
2026-07-25  5:04   ` sashiko-bot
2026-08-09  0:59     ` Cody Kang
2026-07-27  7:35   ` Philipp Zabel
2026-08-08  5:03     ` Guodong Xu
2026-08-09  3:26       ` Cody Kang
2026-08-08 13:21     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 11/17] drm/spacemit: add Saturn DPU DRM device driver Cody Kang via B4 Relay
2026-07-25  5:05   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 12/17] drm/spacemit: add Innosilicon DP/eDP controller bridge driver Cody Kang via B4 Relay
2026-07-25  5:04   ` sashiko-bot
2026-08-08 12:51     ` Cody Kang
2026-07-27  7:49   ` Philipp Zabel
2026-08-08 13:30     ` Cody Kang
2026-07-27 12:29   ` Yao Zi
2026-08-08 14:07     ` Cody Kang
2026-07-25  4:51 ` [PATCH RESEND 13/17] MAINTAINERS: add SpacemiT K3 display driver entry Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 14/17] riscv: dts: spacemit: k3: add display nodes Cody Kang via B4 Relay
2026-07-25  5:08   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 15/17] riscv: dts: spacemit: k3-pico-itx: enable the DisplayPort output Cody Kang via B4 Relay
2026-07-25  5:12   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 16/17] riscv: dts: spacemit: k3-com260-ifx: " Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 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=db9f1190f5096e83bc9af5c082c519a6.codykang.hk@gmail.com \
    --to=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-bot@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