Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Sofus Forstreuter" <sofus.c@icloud.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
	media-ci@linuxtv.org
Subject: Re: [PATCH v3 07/17] media: apple: avd: add h264 support
Date: Tue, 20 Oct 2026 10:55:13 +0000	[thread overview]
Message-ID: <sashiko-outbox-178863@kernel.org> (raw)
In-Reply-To: <20261020-avd-v3-7-85cbaa2125d8@icloud.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] media: apple: avd: out-of-bounds DPB read in stream_slice()
- [Critical] media: apple: avd: segment buffer overread in avd_h264_realloc_slices()
- [Critical] media: apple: avd: out-of-bounds slice write in avd_h264_run()
- [High] media: apple: avd: dangling pointers left by avd_h264_stop()
- [High] media: apple: avd: missing SPS dimension and bit-depth checks
- [High] media: apple: avd: zero return on allocation failure in avd_h264_start()
- [Medium] media: apple: avd: DPB valid vs active mismatch in stream_refs()
- [Medium] media: apple: avd: header_bit_size overflow and silent return in stream_slice()
--

commit 8571614aac70711a51d24aa4b4915d545c41cf5b
Author: Sofus Forstreuter <sofus.c@icloud.com>

media: apple: avd: add h264 support

This commit adds stateless V4L2 H.264 decoding support to the Apple Video
Decoder (AVD) driver.

> diff --git a/drivers/media/platform/apple/avd/avd-h264.c b/drivers/media/platform/apple/avd/avd-h264.c
> new file mode 100644
> index 0000000000000..c32b805a94058
> --- /dev/null
> +++ b/drivers/media/platform/apple/avd/avd-h264.c
> @@ -0,0 +1,862 @@

[ ... ]

> +static void stream_refs(struct avd_ctx *ctx, struct avd_h264_run *run)
> +{
> +	const struct v4l2_ctrl_h264_decode_params *decode = run->decode_params;
> +	const struct v4l2_h264_dpb_entry *dpb = decode->dpb;
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	struct avd_decoded_buffer *dst, *ref;
> +	dma_addr_t addr;
> +
> +	dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf);
> +
> +	push(0, "");
> +	pusha(h264_ctx->bufs.mv_above_info.addr, "mv_above_info", 7);
> +	pusha(run->addresses.mv_color, "mv_color", 0);
> +
> +	push(0, "");
> +	push(0, "");
> +	push(0, "");
> +	push(0, "");
> +
> +	for (int i = 0; i < ARRAY_SIZE(decode->dpb); i++) {
> +		if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_VALID))
> +			continue;
> +
> +		ref = avd_get_ref_buf(ctx, &dst->base.vb, dpb[i].reference_ts);
> +
> +		addr = vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf, 0) +
> +		       ref->comp.start_offset;
> +
> +		push(AVD_REF_NUM(run->num_valid - 1) | AVD_REF_FLAG_CONST |
> +			     AVD_REF_FLAG_LONG(
> +				     dpb[i].flags &
> +				     V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) |
> +			     AVD_REF_DELTA_POC(run->cur_poc -
> +					       dpb[i].top_field_order_cnt),
> +		     "hdr_d0_ref_hdr");
> +
> +		push_comp(ctx, addr, ctx->comp.offsets);
> +	}
> +}

[Severity: Medium]
In avd_h264_run(), run.num_valid comes from reflist_builder.num_valid, which
counts DPB entries with V4L2_H264_DPB_ENTRY_FLAG_ACTIVE set (and also builds
h264_ctx->reflists.{p,b0,b1}, which are never read).

Here in stream_refs(), the loop iterates over entries with
V4L2_H264_DPB_ENTRY_FLAG_VALID set while encoding AVD_REF_NUM(run->num_valid -
1). If the DPB holds valid but inactive frames (for example, decoded frames
held for display reordering or during a non-IDR I-frame where num_valid is 0),
won't the number of pushed reference headers mismatch AVD_REF_NUM and
underflow run->num_valid - 1 when run->num_valid is 0?

[ ... ]

> +static void stream_slice(struct avd_ctx *ctx, struct avd_h264_run *run)
> +{
> +	const struct v4l2_ctrl_h264_decode_params *decode = run->decode_params;
> +	const struct v4l2_ctrl_h264_pps *pps = run->pps;
> +	const struct v4l2_ctrl_h264_sps *sps = run->sps;
> +	const struct v4l2_ctrl_h264_slice_params *sl = run->slice_params;
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	u32 payload_len = h264_ctx->active_slice->size;
> +	bool en_mode = (pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE) ==
> +		       0;
> +	const u8 *data = h264_ctx->active_slice->cpu;
> +	u32 min_off = (sl->header_bit_size + (en_mode ? 0 : 7)) / 8;
> +	u32 off = 2;
> +	u32 num_ref_idx_active, bytes_read = 2;
> +	dma_addr_t coded_in, mv_color_addr;
> +	struct avd_decoded_buffer *dst, *ref;
> +
> +	dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf);
> +
> +	if (off > payload_len || payload_len < min_off)
> +		return;

[Severity: Medium]
Can sl->header_bit_size + 7 overflow a 32-bit unsigned integer when
V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE is set and sl->header_bit_size >=
0xfffffff9, wrapping min_off to 0?

Also, if stream_slice() returns early here when off > payload_len or
payload_len < min_off, avd_h264_run() still calls avd_end_segment(ctx, false)
and avd_submit_job(ctx). Should stream_slice() return an error code so
avd_h264_run() can abort instead of submitting an empty slice segment to the
hardware?

[ ... ]

> +	if (sl->slice_type == V4L2_H264_SLICE_TYPE_B) {
> +
> +		/* sl->ref_pic_list1[0].index < ARRAY_SIZE(decode->dpb) */
> +		/* bidirectional reference of previous mv */
> +		ref = avd_get_ref_buf(
> +			ctx, &dst->base.vb,
> +			decode->dpb[sl->ref_pic_list1[0].index].reference_ts);
> +
> +		mv_color_addr =
> +			vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf,
> +						      0) +
> +			(ref->base.vb.vb2_buf.planes[0].length -
> +			 mv_color_size(fmt_width(ctx), fmt_height(ctx)));
> +
> +		pusha(mv_color_addr, "slc_a78_sps_tile_addr2_lsb8", 0);
> +	}
> +}

[Severity: Critical]
Can sl->ref_pic_list1[0].index read past the end of decode->dpb[] here?

sl->ref_pic_list1[0].index is an unvalidated u8 from
V4L2_CID_STATELESS_H264_SLICE_PARAMS, while decode->dpb only has
V4L2_H264_NUM_DPB_ENTRIES (16) elements. Neither std_validate_compound() nor
avd_h264_try_ctrl() checks that ref_pic_list1[0].index <
ARRAY_SIZE(decode->dpb), so a slice with slice_type == V4L2_H264_SLICE_TYPE_B
and index >= 16 (such as 0xff) will read out-of-bounds from decode->dpb.

> +
> +static int avd_h264_alloc_bufs(struct avd_ctx *ctx)
> +{
> +	struct avd_dev *dev = ctx->dev;
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	int ret, w, bit_depth, mb;
> +
> +	w = fmt_width(ctx);
> +	bit_depth = (ctx->image_fmt == AVD_IMG_FMT_420_10BIT ||
> +		     ctx->image_fmt == AVD_IMG_FMT_422_10BIT) ?
> +			    10 :
> +			    8;
> +
> +	mb = DIV_ROUND_UP(w, 16);

[ ... ]

> +static int avd_h264_validate_sps(struct avd_ctx *ctx,
> +				 const struct v4l2_ctrl_h264_sps *sps)
> +{
> +	if (sps->chroma_format_idc > 2)
> +		/* Only 4:0:0, 4:2:0 and 4:2:2 are supported */
> +		return -EINVAL;
> +	if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8)
> +		/* Luma and chroma bit depth mismatch */
> +		return -EINVAL;
> +	if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY))
> +		/* no interlaced support */
> +		return -EINVAL;
> +
> +	return 0;
> +}

[Severity: High]
Should avd_h264_validate_sps() also validate that the SPS macroblock
dimensions fit within ctx->coded_fmt and that sps->bit_depth_luma_minus8 is 0
or 2?

avd_h264_alloc_bufs() sizes the internal scratch DMA buffers (above_info,
ip_above, lf_above, lf_above_info, mv_above_info) using fmt_width(ctx) and
ctx->image_fmt, and capture buffers are sized from ctx->decoded_fmt. However,
stream_hdr() programs the hardware with:

	u32 width = (sps->pic_width_in_mbs_minus1 + 1) * 16;
	u32 height = (sps->pic_height_in_map_units_minus1 + 1) * 16;

and sps->bit_depth_luma_minus8 directly from the SPS control. If userspace
supplies an SPS with dimensions or bit depth larger than the configured
format, won't the hardware DMA-write past the end of the scratch and capture
buffers?

> +
> +static void avd_h264_stop(struct avd_ctx *ctx)
> +{
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	struct avd_dev *dev = ctx->dev;
> +	int i;
> +
> +	if (!h264_ctx)
> +		return;
> +
> +	avd_buf_free(dev, &h264_ctx->bufs.above_info);
> +	avd_buf_free(dev, &h264_ctx->bufs.lf_above_info);
> +	avd_buf_free(dev, &h264_ctx->bufs.lf_above);
> +	avd_buf_free(dev, &h264_ctx->bufs.ip_above);
> +	avd_buf_free(dev, &h264_ctx->bufs.mv_above_info);
> +
> +	for (i = 0; i < h264_ctx->slice_num; i++)
> +		avd_buf_free(dev, &h264_ctx->slices[i]);
> +
> +	kfree(h264_ctx->slices);
> +	kfree(h264_ctx);
> +}

[Severity: High]
When avd_stop_streaming() calls avd_h264_stop(), ctx->priv and
ctx->job.segments are not cleared to NULL.

If userspace queues a slice with V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF set, stops
streaming via VIDIOC_STREAMOFF, and restarts streaming via VIDIOC_STREAMON:

1. If avd_h264_start() fails early before reassigning ctx->priv, ctx->priv
   remains a dangling pointer to the freed h264_ctx.
2. If avd_h264_start() succeeds and userspace queues a slice with
   first_mb_in_slice != 0, avd_h264_run() sees ctx->job.segments != NULL from
   before streamoff and submits stale segments containing the DMA addresses of
   the freed scratch and slice buffers.

Should avd_h264_stop() clear ctx->priv = NULL and ctx->job.segments = NULL?

> +
> +static int avd_h264_start(struct avd_ctx *ctx)
> +{
> +	struct avd_h264_ctx *h264_ctx;
> +	struct v4l2_ctrl *ctrl;
> +	int ret;
> +
> +	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, V4L2_CID_STATELESS_H264_SPS);
> +	if (!ctrl)
> +		return -EINVAL;
> +
> +	ret = avd_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps);
> +	if (ret)
> +		return ret;
> +
> +	h264_ctx = kzalloc_obj(*h264_ctx, GFP_KERNEL);
> +	if (!h264_ctx)
> +		return -ENOMEM;
> +
> +	ctx->priv = h264_ctx;
> +
> +	/* assume one slice per row */
> +	h264_ctx->alloc_slice_num =
> +		ctrl->p_new.p_h264_sps->pic_height_in_map_units_minus1 + 1;
> +	h264_ctx->slices = kzalloc_objs(*h264_ctx->slices,
> +					h264_ctx->alloc_slice_num, GFP_KERNEL);
> +	if (!h264_ctx->slices)
> +		goto err_free_ctx;
> +
> +	ret = avd_h264_alloc_bufs(ctx);
> +	if (ret)
> +		goto err_free_ctx;
> +
> +	return 0;
> +
> +err_free_ctx:
> +	avd_h264_stop(ctx);
> +	ctx->priv = NULL;
> +	return ret;
> +}

[Severity: High]
If kzalloc_objs() fails when allocating h264_ctx->slices, ret is still 0 from
avd_h264_validate_sps(). Doesn't jumping to err_free_ctx free h264_ctx, set
ctx->priv = NULL, and return 0 to avd_start_streaming(), leading to a NULL
pointer dereference in avd_h264_run()?

Also, since avd_h264_start() runs from avd_start_streaming() outside any
v4l2_ctrl_ops callback, should it inspect ctrl->p_cur.p_h264_sps instead of
ctrl->p_new.p_h264_sps?

[ ... ]

> +static int avd_h264_realloc_slices(struct avd_ctx *ctx)
> +{
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	struct avd_job *job = &ctx->job;
> +	struct avd_buf tmp_buf = {};
> +	size_t alloc_slice_num;
> +	void *tmp;
> +	int ret;
> +
> +	tmp = h264_ctx->slices;
> +	alloc_slice_num = (h264_ctx->alloc_slice_num * 3) / 2 + 1;
> +	h264_ctx->slices =
> +		kzalloc_objs(*h264_ctx->slices, alloc_slice_num, GFP_KERNEL);
> +	if (!h264_ctx->slices) {
> +		/* make deallocating a little easier */
> +		h264_ctx->slices = tmp;
> +		return -ENOMEM;
> +	}
> +
> +	h264_ctx->alloc_slice_num = alloc_slice_num;
> +	memcpy(h264_ctx->slices, tmp,
> +	       sizeof(*h264_ctx->slices) * h264_ctx->slice_num);
> +	kfree(tmp);
> +
> +	/* we could already have the correct size */
> +	if ((alloc_slice_num + 1) * sizeof(*job->segments) < job->buf.size)
> +		return 0;
> +
> +	ret = avd_buf_alloc(ctx->dev, &tmp_buf,
> +			   (alloc_slice_num + 1) * sizeof(*job->segments));
> +	if (ret)
> +		return -ENOMEM;
> +
> +	job->segments = tmp_buf.cpu;
> +	job->num_alloc = (alloc_slice_num + 1);
> +	memset(tmp_buf.cpu, 0, tmp_buf.size);
> +	memcpy(job->segments, job->buf.cpu,
> +	       sizeof(*job->segments) * (job->num + 1));
> +
> +	avd_buf_free(ctx->dev, &job->buf);
> +	memcpy(&job->buf, &tmp_buf, sizeof(tmp_buf));
> +	return 0;
> +}

[Severity: Critical]
Does the memcpy() into job->segments read past the end of job->buf.cpu here?

On the first slice of a frame, avd_init_job() allocates job->buf with
old_alloc_slice_num + 1 segments. stream_hdr() consumes 1 segment and each of
the first old_alloc_slice_num slices calls avd_end_segment(), which increments
job->num to old_alloc_slice_num + 1. When the next slice triggers
avd_h264_realloc_slices(), job->num already equals the total number of
segments allocated in job->buf.cpu, so copying job->num + 1 segments reads one
struct avd_segment past the end of job->buf.cpu.

In addition, if avd_buf_alloc() fails and returns -ENOMEM (or if the early
return path is taken without updating job->num_alloc), h264_ctx->alloc_slice_num
has already been updated to the larger alloc_slice_num while job->segments
still points to the smaller buffer. Won't subsequent slices then bypass
avd_h264_realloc_slices() and write past the end of job->segments?

> +
> +static int avd_h264_run(struct avd_ctx *ctx)
> +{
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	struct v4l2_h264_reflist_builder reflist_builder;
> +	struct avd_h264_run run;
> +	struct vb2_v4l2_buffer *src;
> +	void *slice;
> +	int ret;
> +
> +	avd_h264_run_preamble(ctx, &run);
> +
> +	src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> +	slice = vb2_plane_vaddr(&src->vb2_buf, 0);
> +	if (!slice) {
> +		dev_err_ratelimited(ctx->dev->dev, "could not get slice vaddr");
> +		ret = -EINVAL;
> +		goto postamble;
> +	}
> +
> +	if (ctx->job.segments &&
> +	    h264_ctx->slice_num >= h264_ctx->alloc_slice_num) {
> +		ret = avd_h264_realloc_slices(ctx);
> +		if (ret)
> +			goto postamble;
> +	}
> +
> +	h264_ctx->active_slice = &h264_ctx->slices[h264_ctx->slice_num];
> +	ret = avd_buf_alloc(ctx->dev, h264_ctx->active_slice,
> +			    vb2_get_plane_payload(&src->vb2_buf, 0));
> +	if (ret)
> +		goto postamble;
> +
> +	memcpy(h264_ctx->active_slice->cpu, slice,
> +	       h264_ctx->active_slice->size);
> +	h264_ctx->slice_num++;
> +
> +	/* Build the P/B{0,1} ref lists. */
> +	v4l2_h264_init_reflist_builder(&reflist_builder, run.decode_params,
> +				       run.sps, run.decode_params->dpb);
> +
> +	run.num_valid = reflist_builder.num_valid;
> +	run.cur_poc = reflist_builder.cur_pic_order_count;
> +
> +	v4l2_h264_build_p_ref_list(&reflist_builder, h264_ctx->reflists.p);
> +	v4l2_h264_build_b_ref_lists(&reflist_builder, h264_ctx->reflists.b0,
> +				    h264_ctx->reflists.b1);
> +
> +
> +	if (is_new_frame(run.slice_params)) {
> +		ret = avd_init_job(ctx, AVD_CODEC_H264,
> +				   h264_ctx->alloc_slice_num + 1);
> +		if (ret)
> +			goto postamble;
> +		stream_hdr(ctx, &run);
> +		/* h264 only submits once */
> +		avd_end_segment(ctx, true);
> +	}
> +
> +	if (!ctx->job.segments) {
> +		ret = -EINVAL;
> +		goto postamble;
> +	}
> +
> +	stream_slice(ctx, &run);
> +	avd_end_segment(ctx, false);
> +
> +	if (run.base.bufs.src->flags & V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF) {
> +		avd_run_postamble(ctx, &run.base);
> +		avd_job_finish(ctx, VB2_BUF_STATE_DONE);
> +		return 0;
> +	}
> +
> +	ret = avd_submit_job(ctx);
> +
> +postamble:
> +	avd_run_postamble(ctx, &run.base);
> +	return ret;
> +}
> +
> +static void avd_h264_done(struct avd_ctx *ctx, struct vb2_v4l2_buffer *src_buf,
> +			  struct vb2_v4l2_buffer *dst_buf,
> +			  enum vb2_buffer_state result)
> +{
> +	struct avd_dev *avd = ctx->dev;
> +	struct avd_h264_ctx *h264_ctx = ctx->priv;
> +	int i;
> +
> +	if (!(src_buf->flags & V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF)) {
> +		for (i = 0; i < h264_ctx->slice_num; i++)
> +			avd_buf_free(avd, &h264_ctx->slices[i]);
> +		h264_ctx->slice_num = 0;
> +		ctx->job.segments = NULL;
> +	}
> +}

[Severity: Critical]
Can h264_ctx->slices[] be written out-of-bounds when
V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF is used?

In avd_h264_run(), the reallocation check:

	if (ctx->job.segments &&
	    h264_ctx->slice_num >= h264_ctx->alloc_slice_num)

is gated on ctx->job.segments being non-NULL, and active_slice is allocated
and h264_ctx->slice_num is incremented before checking is_new_frame() or
!ctx->job.segments.

If ctx->job.segments is NULL and userspace queues buffers with
first_mb_in_slice != 0 and V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF set,
avd_h264_run() increments h264_ctx->slice_num and returns -EINVAL at if
(!ctx->job.segments). Because avd_h264_done() only frees slices and resets
h264_ctx->slice_num when !(src_buf->flags & V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF)
even on VB2_BUF_STATE_ERROR, h264_ctx->slice_num keeps growing while
ctx->job.segments remains NULL, bypassing avd_h264_realloc_slices() and
writing past the end of h264_ctx->slices[].

Similarly, if a new frame (first_mb_in_slice == 0) arrives after prior slices
were held with V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF, avd_init_job() resets
ctx->job.num to 0 without freeing the previous h264_ctx->slices[] or resetting
h264_ctx->slice_num.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020-avd-v3-0-85cbaa2125d8@icloud.com?part=7

  reply	other threads:[~2026-10-20 10:55 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20  9:37 [PATCH v3 00/17] media: apple: add avd driver Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 01/17] dt-bindings: media: add apple,avd Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 02/17] media: v4l2: Add P210 pixel format Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 03/17] media: v4l2: Add Apple interchange pixel formats Sofus Forstreuter
2026-10-20  9:56   ` sashiko-bot
2026-10-20  9:37 ` [PATCH v3 04/17] media: v4l2-ctrls: validate av1 tile info Sofus Forstreuter
2026-10-20 10:10   ` sashiko-bot
2026-10-20  9:37 ` [PATCH v3 05/17] media: v4l2-ctrls: validate vp9 tile_rows_log2 Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 06/17] media: apple: add avd driver Sofus Forstreuter
2026-10-20 10:34   ` sashiko-bot
2026-10-20  9:37 ` [PATCH v3 07/17] media: apple: avd: add h264 support Sofus Forstreuter
2026-10-20 10:55   ` sashiko-bot [this message]
2026-10-20  9:37 ` [PATCH v3 08/17] media: apple: avd: add vp9 support Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 09/17] media: apple: avd: add hevc support Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 10/17] media: apple: avd: add av1 support Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 11/17] arm64: dts: apple: t8103: add avd nodes Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 12/17] arm64: dts: apple: t8112: " Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 13/17] arm64: dts: apple: t8122: " Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 14/17] arm64: dts: apple: t600x: " Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 15/17] arm64: dts: apple: t602x: " Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 16/17] arm64: dts: apple: t6030: " Sofus Forstreuter
2026-10-20  9:37 ` [PATCH v3 17/17] arm64: dts: apple: t6031: " Sofus Forstreuter

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=sashiko-outbox-178863@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=media-ci@linuxtv.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sofus.c@icloud.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox