Linux-PHY Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Cody Kang" <codykang.hk@gmail.com>
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
Subject: Re: [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU
Date: Sat, 25 Jul 2026 05:04:38 +0000	[thread overview]
Message-ID: <20260725050439.2A4CB1F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260725-k3-display-v1-8-6de34d80e86c@gmail.com>

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.

> +			__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.

> +		} 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.

> +		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.

> +			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.

> +			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.

> +}
> +
> +/* 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.

> +
> +	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.

> +
> +	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.

[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.

> +	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.

> +		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.

> +		fallthrough;
> +	case 2:
> +		tbu.ttb_pa[1] = mmu_tbl->pa + (offset1 >> PAGE_SHIFT) * 4;
> +		tbu.tbu_va[1] = TBU_BASE_VA(tbu_id) + offset1;
> +		tbu.ttb_size[1] = PAGE_ALIGN(offset2 - rounddown(offset1, PAGE_SIZE)) >> PAGE_SHIFT;
> +		fallthrough;
> +	case 1:
> +		tbu.ttb_pa[0] = mmu_tbl->pa;
> +		tbu.tbu_va[0] = TBU_BASE_VA(tbu_id);
> +		tbu.ttb_size[0] = PAGE_ALIGN(offset1) >> PAGE_SHIFT;
> +		fallthrough;
> +	default:
> +		break;
> +	}
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260725-k3-display-v1-0-6de34d80e86c@gmail.com?part=8

-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

  reply	other threads:[~2026-07-25  5:04 UTC|newest]

Thread overview: 26+ 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-07-25  4:51 ` [PATCH RESEND 02/17] dt-bindings: phy: add SpacemiT K3 Innosilicon DP PHY Cody Kang via B4 Relay
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-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-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-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 [this message]
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-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-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=20260725050439.2A4CB1F00A3A@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