From: sashiko-bot@kernel.org
To: "Daniel Golle" <daniel@makrotopia.org>,
devicetree@vger.kernel.org, linux-clk@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org, media-ci@linuxtv.org
Subject: Re: [RFC PATCH 3/6] media: rockchip: Add RKVENC v1 video encoder driver
Date: Sat, 14 Nov 2026 15:27:48 +0000 [thread overview]
Message-ID: <sashiko-outbox-214704@kernel.org> (raw)
In-Reply-To: <96b018d341e884c12e1d5c5c6d42880f134a644c.1794666330.git.daniel@makrotopia.org>
Thank you for your contribution! Sashiko AI review found 22 potential
issues to consider:
- [High] media: rkvenc: DMA buffer overflow via rkvenc_s_fmt_vid_out()
while streaming
- [High] media: rkvenc: missing NV12M plane 1 and data_offset check in
rkvenc_buf_prepare()
- [High] media: rkvenc: divide-by-zero in rkvenc_rc_pick() and 32-bit RC
overflows
- [High] media: rkvenc: missing plane 1 and data_offset checks in
rkvenc_buf_prepare()
- [High] media: rkvenc: premature watchdog cancel in rkvenc_remove()
before cleanup
- [High] media: rkvenc: SCLR_DONE masked by IRQF_ONESHOT in
rkvenc_hw_recover()
- [High] media: rkvenc: watchdog cancel race in rkvenc_irq_thread() and
device_run()
- [High] media: rockchip: rkvenc: job_running races in IRQ, watchdog,
and device_run()
- [Medium] media: rkvenc: unreachable I-frame RC model and broken IDR
overflow retry
- [Medium] media: rkvenc: VB2_BUF_STATE_DONE in rkvenc_buf_queue() and
stale header_mode
- [Medium] media: rkvenc: rkvenc_gop_commit() drops concurrent
force_keyframe requests
- [Medium] media: rkvenc: missing CREATE_BUFS validation in
rkvenc_queue_setup()
- [Medium] media: rkvenc: rkvenc_apply_ctrls() prevents setting gop_size
below 30
- [Medium] media: rkvenc: control handler leak on error in
rkvenc_init_ctrls()
- [Medium] media: rkvenc: missing pm_runtime_disable() on error in
rkvenc_probe()
- [Medium] media: rkvenc: rkvenc_try_fmt_vid_cap() ignores active OUTPUT
dimensions
- [Medium] media: rockchip: rkvenc: userspace control/format values
trigger WARN_ON()
- [Medium] media: rockchip: rkvenc: broken control updates and IDR
header refresh in v4l2
- [Medium] media: rockchip: rkvenc: missing pm_runtime_disable() on
probe error path
- [Medium] media: rockchip: rkvenc: watchdog canceled before V4L2/M2M
teardown in remove()
- [Low] media: rkvenc: HEVC merge candidate 0 overridden in
rkvenc_hevc_setup()
- [Low] media: rkvenc: CAPTURE format mismatch and round_up() overflow
in try_fmt
--
commit aafc130d19efda3692073c7ce74ebbd170ec8aca
Author: Daniel Golle <daniel@makrotopia.org>
media: rockchip: Add RKVENC v1 video encoder driver
This commit adds a stateful V4L2 memory-to-memory H.264 and HEVC video
encoder driver for the Rockchip RKVENC v1 IP on RV1109/RV1126 and
RK3566/RK3568 SoCs.
> diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c
> new file mode 100644
> index 0000000000000..208bb122a3f87
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c
> @@ -0,0 +1,72 @@
[ ... ]
> +void rkvenc_gop_commit(struct rkvenc_ctx *ctx)
> +{
> + struct rkvenc_gop *g = &ctx->gop;
> +
> + if (ctx->cur_intra) {
> + g->idr_pic_id++;
> + ctx->last_good = ctx->cur;
> + ctx->last_good_valid = true;
> + g->frames_since_idr = 1;
> + } else {
> + g->frames_since_idr++;
> + }
> + ctx->last = ctx->cur;
> + ctx->cur ^= 1;
> + ctx->c.force_keyframe = false;
> +}
> +
> +void rkvenc_gop_rollback(struct rkvenc_ctx *ctx)
> +{
> + ctx->c.force_keyframe = false;
> +}
> +
> +void rkvenc_gop_force_keyframe(struct rkvenc_ctx *ctx)
> +{
> + ctx->c.force_keyframe = true;
> +}
[Severity: Medium]
Does clearing ctx->c.force_keyframe unconditionally in rkvenc_gop_commit()
and rkvenc_gop_rollback() drop keyframe requests that arrive while a frame
is in flight?
CPU0 CPU1
rkvenc_device_run()
idr = rkvenc_gop_is_idr(ctx)
/* false: starts a P frame */
rkvenc_s_ctrl()
rkvenc_gop_force_keyframe(ctx)
ctx->c.force_keyframe = true
rkvenc_irq_thread()
rkvenc_finish_job()
rkvenc_gop_commit(ctx)
ctx->c.force_keyframe = false <-- request lost before next frame
Consuming ctx->c.force_keyframe when rkvenc_device_run() decides idr = true
(or only clearing it when committing an intra frame) would preserve requests
made while a P frame is encoding.
> diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c
> new file mode 100644
> index 0000000000000..49bf3cccdf8a0
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c
> @@ -0,0 +1,63 @@
[ ... ]
> +int rkvenc_hevc_setup(struct rkvenc_ctx *ctx)
> +{
> + struct rkvenc_hevc_seq *seq = &ctx->hevc_seq;
> +
> + memset(seq, 0, sizeof(*seq));
> + seq->width = ctx->width;
> + seq->height = ctx->height;
> + seq->level_idc = rkvenc_hevc_level(ctx->c.level);
> + seq->log2_max_poc_lsb_m4 = 4;
> + seq->pic_init_qp = ctx->c.p_frame_qp;
> + seq->max_merge_cand = ctx->c.hevc_max_merge ?: 2;
[Severity: Low]
Does using ?: 2 here prevent setting
V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1 to 0? rkvenc_init_ctrls()
registers that control with a minimum of 0, so a valid control value of 0 is
overwritten with 2.
Also, V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION is registered and stored into
ctx->c.hevc_tmv in rkvenc_apply_ctrls(), and ctx->vlock is initialized in
rkvenc_open(), but neither is used anywhere in the driver.
[ ... ]
> diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c
> new file mode 100644
> index 0000000000000..b7ab57e490cf0
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c
> @@ -0,0 +1,214 @@
[ ... ]
> +static u32 rkvenc_rc_capacity(struct rkvenc_ctx *ctx)
> +{
> + if (ctx->c.vbv_size)
> + return ctx->c.vbv_size;
> + return ctx->rc_tavg * max_t(u32, ctx->c.gop_size, 1);
> +}
[ ... ]
> + if (ctx->rc_x[0] && ctx->rc_x[1]) {
> + u64 w = div_u64((u64)ctx->rc_x[0] * 256,
> + ctx->rc_x[1] ?: 1);
> + u32 n = max_t(u32, ctx->c.gop_size, 1);
> + u64 t_p, t_t;
> +
> + w = min_t(u64, w, RKVENC_RC_W_CLAMP);
> + t_p = div64_u64((u64)ctx->rc_tavg * n * 256,
> + w + 256 * (n - 1));
> + t_t = t ? t_p : t_p * w / 256;
> + t_t = rkvenc_rc_correct(ctx, t_t, n);
> +
> + target = (u32)div_u64((u64)ctx->rc_x[t] * 256, t_t);
> +
> + for (q = 0; q <= RKVENC_QP_MAX; q++)
> + if (rkvenc_qs[q] >= target)
> + break;
> + if (q > RKVENC_QP_MAX)
> + q = RKVENC_QP_MAX;
> + } else {
> + q = prev;
> + }
[ ... ]
> +void rkvenc_rc_update(struct rkvenc_ctx *ctx, u32 bytes, u32 qp_avg,
> + u8 frame_type)
> +{
> + unsigned int t = frame_type ? 1 : 0;
> + u64 s = (u64)bytes * 8;
> + s64 cap = max_t(u64, rkvenc_rc_capacity(ctx), 1);
> +
> + if (!ctx->rc_active)
> + return;
> +
> + ctx->rc_x[t] = rkvenc_rc_complexity(ctx, t, s, qp_avg);
> +
> + ctx->rc_buf += (s32)s - (s32)ctx->rc_tavg;
> + ctx->rc_buf = clamp_t(s64, ctx->rc_buf, -cap, cap);
> +
> + ctx->rc_first[t] = false;
> +}
[Severity: High]
Can div64_u64() in rkvenc_rc_pick() divide by zero when ctx->c.gop_size is 0?
When ctx->c.gop_size is 0 (allowed by the 0..300 control range), n is 1.
If a low-complexity, low-QP I frame is followed by a high-complexity, high-QP
P frame such that ctx->rc_x[1] > (u64)ctx->rc_x[0] * 256, w truncates to 0
and the divisor w + 256 * (n - 1) becomes 0.
In addition, ctx->rc_tavg * max_t(u32, ctx->c.gop_size, 1) in
rkvenc_rc_capacity() can overflow u32 at high bitrates and low frame rates
(e.g. 40 Mbps at 1 fps with gop_size = 300), and ctx->rc_buf += (s32)s -
(s32)ctx->rc_tavg in rkvenc_rc_update() can overflow 32-bit signed
arithmetic before being clamped to [-cap, cap].
> +/* The step limit alone would need frames that the overflow keeps dropping */
> +void rkvenc_rc_overflow(struct rkvenc_ctx *ctx, u32 bytes, u8 frame_type)
> +{
> + s8 start = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp;
> + unsigned int t = frame_type ? 1 : 0;
> +
> + rkvenc_rc_update(ctx, bytes, ctx->cur_qp, frame_type);
> + if (ctx->rc_qp[t] < start)
> + ctx->rc_qp[t] = start;
> +}
[Severity: Medium]
Does the I-frame rate control and overflow retry logic in rkvenc_rc_pick()
and rkvenc_rc_overflow() ever adjust QP for IDR frames?
In rkvenc_rc_pick(), when t == 0:
- if !ctx->rc_first[1] is true, it always takes the rkvenc_rc_p_qp_mean()
branch and returns early;
- if ctx->rc_first[1] is true, ctx->rc_x[1] is still 0, so
if (ctx->rc_x[0] && ctx->rc_x[1]) is false and it falls back to q = prev.
As a result, the t == 0 complexity branch in rkvenc_rc_pick() is unreachable,
all-Intra streams (gop_size == 1 or intra-only resolutions) stay at the
initial i_frame_qp forever, and when an IDR frame overflows the bitstream
buffer, the retry in rkvenc_finish_job() ignores the complexity update from
rkvenc_rc_overflow() and re-encodes at the same failing QP (or an unadjusted
P-frame mean QP). Also, ctx->rc_tavg is only computed in rkvenc_rc_init(),
so runtime changes to V4L2_CID_MPEG_VIDEO_BITRATE have no effect.
> diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
> new file mode 100644
> index 0000000000000..3be496f5cf0c1
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
> @@ -0,0 +1,1069 @@
[ ... ]
> +/* Runs for every frame, so that control changes apply to the next one */
> +static void rkvenc_apply_ctrls(struct rkvenc_ctx *ctx)
> +{
> + struct rkvenc_ctrls *c = &ctx->c;
> + bool h264 = ctx->codec == RKVENC_CODEC_H264;
> +
> + c->bitrate_mode = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_BITRATE_MODE);
> + c->bitrate = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_BITRATE);
> + c->vbv_size = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_VBV_SIZE);
> + c->frame_rc_enable =
> + rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE);
> + c->gop_size = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_GOP_SIZE);
> + c->i_period = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD);
> + c->header_mode = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEADER_MODE);
> + c->prepend_spspps =
> + rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR);
> +
> + if (h264) {
[ ... ]
> + c->min_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_MIN_QP);
> + c->max_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_MAX_QP);
> + c->i_frame_qp =
> + rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP);
> + c->p_frame_qp =
> + rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP);
> + if (rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD) >
> + c->gop_size)
> + c->gop_size =
> + rkvenc_ctrl_val(ctx,
> + V4L2_CID_MPEG_VIDEO_H264_I_PERIOD);
> + } else {
[ ... ]
> + if (rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD) >
> + c->gop_size)
> + c->gop_size =
> + rkvenc_ctrl_val(ctx,
> + V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD);
> + }
[Severity: Medium]
Does taking the maximum of V4L2_CID_MPEG_VIDEO_GOP_SIZE and
V4L2_CID_MPEG_VIDEO_H264_I_PERIOD (or V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD)
prevent setting a GOP size below 30?
All three controls default to 30 in rkvenc_init_ctrls(), so if an
application lowers only V4L2_CID_MPEG_VIDEO_GOP_SIZE (for example to 1 for
all-Intra or 0 for infinite GOP), the untouched codec-specific period control
stays at 30 and overrides c->gop_size back to 30.
[ ... ]
> +static int rkvenc_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers,
> + unsigned int *num_planes, unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct rkvenc_ctx *ctx = vb2_get_drv_priv(vq);
> + const struct rkvenc_fmt *fmt;
> + unsigned int i;
> + u32 size;
> +
> + if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> + fmt = rkvenc_find_fmt(rkvenc_output_fmts,
> + ARRAY_SIZE(rkvenc_output_fmts),
> + ctx->src_fourcc);
> + if (!fmt)
> + return -EINVAL;
> + *num_planes = fmt->num_planes;
> + for (i = 0; i < *num_planes; i++)
> + sizes[i] = rkvenc_output_plane_size(ctx, i);
> + return 0;
> + }
> +
> + fmt = rkvenc_find_fmt(rkvenc_capture_fmts,
> + ARRAY_SIZE(rkvenc_capture_fmts),
> + ctx->coded_fourcc);
> + if (!fmt)
> + return -EINVAL;
> + size = rkvenc_capture_size(ctx);
> + *num_planes = 1;
> + if (*num_buffers)
> + sizes[0] = max(sizes[0], size);
> + else
> + sizes[0] = size;
> + return 0;
> +}
[Severity: Medium]
Does rkvenc_queue_setup() validate *num_planes and sizes[] when called from
VIDIOC_CREATE_BUFS?
When *num_planes != 0, the vb2 queue_setup callback is expected to return
-EINVAL if *num_planes does not match the format or if any sizes[i] is
smaller than the minimum plane size. Here, the OUTPUT branch unconditionally
overwrites *num_planes and sizes[i], and the CAPTURE branch checks
*num_buffers instead of *num_planes and clamps sizes[0] instead of rejecting
invalid plane counts or undersized buffers.
> +static int rkvenc_buf_prepare(struct vb2_buffer *vb)
> +{
> + struct vb2_queue *vq = vb->vb2_queue;
> + struct rkvenc_ctx *ctx = vb2_get_drv_priv(vq);
> +
> + if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> + if (vb2_plane_size(vb, 0) < rkvenc_output_plane_size(ctx, 0))
> + return -EINVAL;
> + } else {
> + if (vb2_plane_size(vb, 0) < rkvenc_capture_size(ctx))
> + return -EINVAL;
> + }
> + return 0;
> +}
[Severity: High]
When V4L2_PIX_FMT_NV12M (ctx->src_num_planes == 2) is used, can a queued
buffer with an undersized plane 1 or a large planes[1].data_offset cause an
out-of-bounds DMA read?
rkvenc_buf_prepare() only checks vb2_plane_size(vb, 0) and does not check
plane 1 or account for planes[1].data_offset before rkvenc_device_run()
adds data_offset to the plane 1 DMA address.
> +static void rkvenc_buf_queue(struct vb2_buffer *vb)
> +{
> + struct rkvenc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +
> + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) {
> + if (vb2_is_streaming(vb->vb2_queue) &&
> + v4l2_m2m_dst_buf_is_last(ctx->m2m_ctx)) {
> + vb2_set_plane_payload(vb, 0, 0);
> + vbuf->field = V4L2_FIELD_NONE;
> + v4l2_m2m_last_buffer_done(ctx->m2m_ctx, vbuf);
> + return;
> + }
> +
> + /* The parameter sets take a CAPTURE buffer of their own */
> + if (ctx->header_pending &&
> + ctx->c.header_mode ==
> + V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) {
> + u8 *vaddr = vb2_plane_vaddr(vb, 0);
> +
> + if (vaddr) {
> + rkvenc_ensure_header(ctx);
> + if (rkvenc_copy_headers(ctx, vaddr,
> + vb2_plane_size(vb, 0)) < 0)
> + vb2_set_plane_payload(vb, 0, 0);
> + else
> + vb2_set_plane_payload(vb, 0,
> + ctx->hdr_len);
> + } else {
> + vb2_set_plane_payload(vb, 0, 0);
> + }
> + vbuf->field = V4L2_FIELD_NONE;
> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
> + ctx->header_pending = false;
> + return;
> + }
> + }
> +
> + v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf);
> +}
[Severity: Medium]
Can completing the separate-header buffer with VB2_BUF_STATE_DONE inside
rkvenc_buf_queue() trigger a vb2 warning if rkvenc_start_streaming()
subsequently fails?
For buffers queued before VIDIOC_STREAMON, vb2_start_streaming() invokes
.buf_queue() before .start_streaming(). If rkvenc_buf_queue() finishes a
buffer onto q->done_list and rkvenc_start_streaming() then returns an error,
rkvenc_return_dst_bufs() cannot reclaim that buffer and vb2_start_streaming()
hits WARN_ON(!list_empty(&q->done_list)).
In addition, rkvenc_buf_queue() tests ctx->c.header_mode, which is only
updated in rkvenc_apply_ctrls() (and starts at 0, i.e. SEPARATE, before the
first stream), while rkvenc_s_ctrl() sets ctx->header_pending = true on
SEPARATE without updating ctx->c.header_mode or clearing ctx->header_pending
when switching back to JOINED_WITH_1ST_FRAME, and rkvenc_start_streaming()
does not re-arm ctx->header_pending on stream restart.
[ ... ]
> +static void rkvenc_device_run(void *priv)
> +{
> + struct rkvenc_ctx *ctx = priv;
> + struct rkvenc_dev *dev = ctx->dev;
> + struct vb2_v4l2_buffer *src, *dst;
> + struct rkvenc_frame f = {};
> + u8 qp;
> + bool idr;
> + u32 hdr_len = 0;
> + u8 *vaddr;
> + int ret;
> +
> + dev->job_ctx = ctx;
> + dev->job_running = true;
[Severity: High]
Can setting dev->job_ctx and dev->job_running = true here without holding
dev->irq_lock race with an already-dispatched rkvenc_watchdog_work()?
CPU0 (rkvenc_irq_thread) CPU1 (kworker)
spin_lock(&dev->irq_lock)
dev->job_running = false
spin_unlock(&dev->irq_lock)
cancel_delayed_work(&watchdog)
rkvenc_watchdog_work()
rkvenc_finish_job()
rkvenc_device_run()
dev->job_ctx = ctx
dev->job_running = true
spin_lock(&dev->irq_lock)
/* sees job_running == true! */
dev->job_running = false
spin_unlock(&dev->irq_lock)
rkvenc_finish_job() <-- aborts new job
pm_runtime_resume_and_get()
Because rkvenc_irq_thread() uses non-synchronous cancel_delayed_work(),
rkvenc_watchdog_work() can already be running on another CPU and mistake the
newly starting job for the timed-out one before pm_runtime_resume_and_get()
and rkvenc_hw_run() have even run, accessing MMIO registers while clocks are
disabled and underflowing the runtime PM count.
> + src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
> + dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
> + if (!src || !dst)
> + goto finish_error;
> +
> + idr = rkvenc_gop_is_idr(ctx);
> +
> + if (idr)
> + ctx->gop.frames_since_idr = 0;
> +
> + rkvenc_apply_ctrls(ctx);
> + rkvenc_rc_pick(ctx, &qp, idr ? 0 : 1);
> + ctx->cur_qp = qp;
> + ctx->cur_intra = idr;
> +
> + if (idr)
> + rkvenc_refresh_header(ctx);
[Severity: Medium]
Does calling rkvenc_refresh_header(ctx) on every IDR frame desynchronize the
slice syntax registers from the bitstream parameter sets when new headers are
not emitted?
rkvenc_refresh_header() overwrites ctx->h264_seq or ctx->hevc_seq (which
feed the hardware slice registers) from the current V4L2 controls on every
IDR picture, even when V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE is used or when
V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME is used without
V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR. Also, as noted earlier,
rkvenc_apply_ctrls() overrides gop_size < 30 with the default 30 of
H264_I_PERIOD / HEVC_REFRESH_PERIOD, does not update ctx->rc_tavg when
V4L2_CID_MPEG_VIDEO_BITRATE changes, and rkvenc_hevc_setup() replaces a
hevc_max_merge value of 0 with 2.
[ ... ]
> + f.src.plane[0] = lower_32_bits(
> + vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0));
> + if (ctx->src_num_planes == 1)
> + f.src.plane[1] = f.src.plane[0] + ctx->bytesperline * ctx->height;
> + else
> + f.src.plane[1] = lower_32_bits(
> + vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 1) +
> + src->vb2_buf.planes[1].data_offset);
[Severity: High]
Can src->vb2_buf.planes[1].data_offset push f.src.plane[1] past the end of
the plane 1 DMA buffer?
Because rkvenc_buf_prepare() never checks that vb2_plane_size(vb, 1) -
vb->planes[1].data_offset is at least rkvenc_output_plane_size(ctx, 1),
queueing a 2-plane V4L2_PIX_FMT_NV12M buffer with a non-zero data_offset or
an undersized second plane programs RKVENC_ADR_SRC1 to DMA-read up to
ctx->bytesperline * ctx->height / 2 bytes out of bounds.
[ ... ]
> + v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
> + V4L2_CID_MPEG_VIDEO_AVERAGE_QP, 0, 51, 1, 0);
> +
> + if (hdl->error)
> + return hdl->error;
[Severity: Medium]
Does returning hdl->error here without calling v4l2_ctrl_handler_free(hdl)
leak the control handler and any controls allocated before the failure?
rkvenc_open()'s err_m2m path also does not free ctx->ctrl_hdl when
rkvenc_init_ctrls() fails.
[ ... ]
> +static void rkvenc_try_fmt_vid_out(struct rkvenc_ctx *ctx, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> + const struct rkvenc_fmt *fmt;
> + u32 bpl;
> +
> + fmt = rkvenc_find_fmt(rkvenc_output_fmts,
> + ARRAY_SIZE(rkvenc_output_fmts),
> + pix->pixelformat);
> + if (!fmt) {
> + fmt = &rkvenc_output_fmts[0];
> + pix->pixelformat = fmt->fourcc;
> + }
> +
> + pix->field = V4L2_FIELD_NONE;
> + pix->width = clamp(round_up(pix->width, 2), 16, ctx->dev->max_width);
> + pix->height = clamp(round_up(pix->height, 2), 16, ctx->dev->max_height);
[Severity: Low]
Can round_up(pix->width, 2) overflow u32 before clamp() if userspace passes
0xffffffff? Wrapping to 0 clamps the dimension to 16 instead of
ctx->dev->max_width (and likewise in rkvenc_try_fmt_vid_cap()). Also,
rkvenc_start_streaming() does not call
v4l2_m2m_update_start_streaming_state() to pair with
v4l2_m2m_update_stop_streaming_state() in rkvenc_stop_streaming().
[ ... ]
> +static void rkvenc_try_fmt_vid_cap(struct rkvenc_ctx *ctx, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> + const struct rkvenc_fmt *fmt;
> +
> + fmt = rkvenc_find_fmt(rkvenc_capture_fmts,
> + ARRAY_SIZE(rkvenc_capture_fmts),
> + pix->pixelformat);
> + if (!fmt) {
> + fmt = &rkvenc_capture_fmts[0];
> + pix->pixelformat = fmt->fourcc;
> + }
> +
> + pix->field = V4L2_FIELD_NONE;
> + pix->width = clamp(round_up(pix->width, 2), 16, ctx->dev->max_width);
> + pix->height = clamp(round_up(pix->height, 2), 16, ctx->dev->max_height);
> + pix->num_planes = 1;
> + pix->plane_fmt[0].bytesperline = 0;
> +
> + if (fmt->fourcc == V4L2_PIX_FMT_H264) {
> + u32 mbw = ALIGN(pix->width, 16) / 16;
> + u32 mbh = ALIGN(pix->height, 16) / 16;
> +
> + pix->plane_fmt[0].sizeimage =
> + min(mbw * mbh * 400, pix->width * pix->height / 2) +
> + RKVENC_HDR_MAX;
> + } else {
> + pix->plane_fmt[0].sizeimage =
> + pix->width * pix->height / 2 + RKVENC_HDR_MAX;
> + }
> +}
[Severity: Medium]
Should rkvenc_try_fmt_vid_cap() derive pix->width, pix->height, and
pix->plane_fmt[0].sizeimage from ctx->width and ctx->height instead of the
caller-supplied f->fmt.pix_mp?
Because rkvenc_s_fmt_vid_cap() does not update ctx->width or ctx->height,
calling VIDIOC_TRY_FMT or VIDIOC_S_FMT on the CAPTURE queue with different
dimensions (such as 0x0 when selecting the coded format) returns a width,
height, and sizeimage that disagree with VIDIOC_G_FMT(CAPTURE),
rkvenc_queue_setup(), and rkvenc_buf_prepare().
[ ... ]
> +static int rkvenc_s_fmt_vid_out(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
> + struct vb2_queue *vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
> +
> + if (vb2_is_busy(vq))
> + return -EBUSY;
> +
> + rkvenc_try_fmt_vid_out(ctx, f);
> + ctx->src_fourcc = f->fmt.pix_mp.pixelformat;
> + ctx->src_num_planes = f->fmt.pix_mp.num_planes;
> + ctx->width = f->fmt.pix_mp.width;
> + ctx->height = f->fmt.pix_mp.height;
> + ctx->bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline;
> + return 0;
> +}
[Severity: High]
Can changing the OUTPUT format while the CAPTURE queue is already streaming
overflow the reconstruction and reference DMA buffers in ctx->slot[]?
rkvenc_start_streaming() only allocates ctx->slot[] (via rkvenc_ref_alloc())
and validates dimensions when the CAPTURE queue starts streaming, while
returning 0 immediately for the OUTPUT queue. Because rkvenc_s_fmt_vid_out()
only checks vb2_is_busy() on the OUTPUT queue, userspace can start CAPTURE
streaming at a small resolution (e.g. 16x16), then call VIDIOC_S_FMT(OUTPUT)
with a larger resolution (up to 4096x2304) and start OUTPUT streaming,
causing the hardware encoder to DMA-write past the end of the undersized
ctx->slot[] buffers.
[ ... ]
> diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
> new file mode 100644
> index 0000000000000..00778e97686d7
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
> @@ -0,0 +1,489 @@
[ ... ]
> +int rkvenc_hw_run(struct rkvenc_dev *dev, const struct rkvenc_frame *f)
> +{
> + struct rkvenc_img *img = &dev->img;
> + void __iomem *regs = dev->regs;
> + unsigned int a;
> + int ret;
> +
> + ret = rkvenc_vepu541_img_build(f, img);
> + if (WARN_ON(ret))
> + return ret;
[Severity: Medium]
Can userspace trigger this WARN_ON(ret) via standard V4L2 controls or format
changes?
V4L2_CID_MPEG_VIDEO_H264_MIN_QP / MAX_QP (and the HEVC equivalents) are
independent controls with range 1..51 and no check enforcing min_qp <=
max_qp. Setting min_qp > max_qp passes inverted bounds to clamp_t() in
rkvenc_rc_pick() and causes rkvenc_frame_check() inside
rkvenc_vepu541_img_build() to return -EINVAL and hit WARN_ON(ret) on every
frame (as does setting a narrow H.264 resolution with width <= 32 and
height > 16 on OUTPUT after CAPTURE is already streaming).
[ ... ]
> +void rkvenc_hw_recover(struct rkvenc_dev *dev)
> +{
> + void __iomem *regs = dev->regs;
> +
> + if (rkvenc_hw_force_clear(dev))
> + return;
> +
> + dev_err(dev->dev, "job did not stop on force clear, resetting\n");
> +
> + writel(RKVENC_INT_ALL & ~RKVENC_INT_SCLR_DONE, regs + RKVENC_INT_MSK);
> + writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR);
> +
> + reinit_completion(&dev->sclr_done);
> + writel(RKVENC_ENC_CLR_SAFE, regs + RKVENC_ENC_CLR);
> + wait_for_completion_timeout(&dev->sclr_done,
> + msecs_to_jiffies(RKVENC_SAFE_CLEAR_MS));
> +
> + if (dev->num_resets) {
> + reset_control_bulk_assert(dev->num_resets, dev->resets);
> + udelay(RKVENC_RESET_PULSE_US);
> + reset_control_bulk_deassert(dev->num_resets, dev->resets);
> + }
> +
> + writel(0, regs + RKVENC_INT_MSK);
> +}
> +
> +static irqreturn_t rkvenc_irq(int irq, void *priv)
> +{
> + struct rkvenc_dev *dev = priv;
> + u32 sta;
> +
> + sta = readl(dev->regs + RKVENC_INT_STA);
> + if (!sta)
> + return IRQ_NONE;
> +
> + writel(sta, dev->regs + RKVENC_INT_CLR);
> + /* Flush the posted clear before the interrupt line is sampled again */
> + readl(dev->regs + RKVENC_INT_STA);
> +
> + if (sta & RKVENC_INT_SCLR_DONE)
> + complete(&dev->sclr_done);
> +
> + dev->irq_sta = sta;
> + return IRQ_WAKE_THREAD;
> +}
[Severity: High]
Can dev->sclr_done ever be completed by rkvenc_irq() when rkvenc_hw_recover()
is called from rkvenc_irq_thread()?
Because the IRQ is requested with IRQF_ONESHOT, the interrupt line remains
masked at the interrupt controller for the entire duration of
rkvenc_irq_thread(). When rkvenc_irq_thread() -> rkvenc_finish_job() ->
rkvenc_hw_recover() waits on dev->sclr_done, rkvenc_irq() cannot run until
rkvenc_irq_thread() exits, so wait_for_completion_timeout() always stalls for
the full 100 ms timeout.
Furthermore, when RKVENC_INT_SCLR_DONE does fire in rkvenc_irq(), it
overwrites dev->irq_sta = sta and returns IRQ_WAKE_THREAD instead of
IRQ_HANDLED, queuing a spurious run of rkvenc_irq_thread().
[ ... ]
> +static irqreturn_t rkvenc_irq_thread(int irq, void *priv)
> +{
> + struct rkvenc_dev *dev = priv;
> + struct rkvenc_ctx *ctx;
> + enum vb2_buffer_state state;
> + u32 int_sta, bytes, qp_sum;
> + unsigned long flags;
> +
> + /* Whoever clears job_running first, IRQ or watchdog, finishes the job */
> + spin_lock_irqsave(&dev->irq_lock, flags);
> + if (!dev->job_running) {
> + spin_unlock_irqrestore(&dev->irq_lock, flags);
> + return IRQ_HANDLED;
> + }
> + ctx = dev->job_ctx;
> + dev->job_running = false;
> + spin_unlock_irqrestore(&dev->irq_lock, flags);
> +
> + cancel_delayed_work(&dev->watchdog);
[Severity: High]
Can a spurious IRQ thread wake-up from RKVENC_INT_SCLR_DONE or a fast IRQ
before schedule_delayed_work() corrupt the next job's state?
<hardirq> rkvenc_irq_thread
rkvenc_irq() (SCLR_DONE)
dev->irq_sta = SCLR_DONE
return IRQ_WAKE_THREAD
rkvenc_finish_job()
rkvenc_device_run() (retry / next job)
dev->job_running = true
/* next threaded IRQ run starts */
rkvenc_irq_thread()
dev->job_running = false
/* ENC_DONE not set in irq_sta */
rkvenc_finish_job(..., ERROR) <-- aborts
In addition to the unlocked dev->job_running assignment in
rkvenc_device_run() and the non-synchronous cancel_delayed_work() above,
rkvenc_hw_run() writes RKVENC_ENC_STRT before calling
schedule_delayed_work(&dev->watchdog, ...). If a small frame finishes and
rkvenc_irq_thread() calls cancel_delayed_work() before rkvenc_hw_run() calls
schedule_delayed_work(), the watchdog timer remains armed after the job
completes.
[ ... ]
> + ret = devm_request_threaded_irq(dev, irq, rkvenc_irq,
> + rkvenc_irq_thread, IRQF_ONESHOT,
> + dev_name(dev), rkvenc);
> + if (ret) {
> + dev_err(dev, "Could not request encoder IRQ\n");
> + return ret;
> + }
[Severity: Medium]
Is registering the IRQ with devm_request_threaded_irq() before
pm_runtime_enable(dev) safe?
Because devm_request_threaded_irq() is called before pm_runtime_enable(dev),
devres will also free the IRQ after rkvenc_remove() has already called
pm_runtime_disable(&pdev->dev), and an early error return after
pm_runtime_enable(dev) leaves runtime PM enabled.
> +
> + pm_runtime_enable(dev);
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret)
> + return ret;
[Severity: Medium]
Does returning directly when pm_runtime_resume_and_get(dev) fails leave
runtime PM enabled on the device? Jumping to err_pm_disable would balance
pm_runtime_enable(dev).
[ ... ]
> +static void rkvenc_remove(struct platform_device *pdev)
> +{
> + struct rkvenc_dev *rkvenc = platform_get_drvdata(pdev);
> +
> + cancel_delayed_work_sync(&rkvenc->watchdog);
[Severity: High]
Can rkvenc->watchdog be re-armed after cancel_delayed_work_sync() returns in
rkvenc_remove()?
CPU0 CPU1
rkvenc_remove()
cancel_delayed_work_sync(&watchdog)
rkvenc_device_run() / retry
rkvenc_hw_run()
schedule_delayed_work(&watchdog, ...)
rkvenc_v4l2_cleanup()
...
/* devm frees rkvenc */
rkvenc_watchdog_work() <-- use-after-free
Because cancel_delayed_work_sync() runs before rkvenc_v4l2_cleanup() and
v4l2_m2m_release(), a new job or an in-flight overflow retry in
rkvenc_finish_job() can call rkvenc_hw_run() and re-schedule rkvenc->watchdog
before the V4L2 and M2M devices are torn down.
> + rkvenc_v4l2_cleanup(rkvenc);
> + v4l2_m2m_release(rkvenc->m2m_dev);
> + v4l2_device_unregister(&rkvenc->v4l2_dev);
> + pm_runtime_disable(&pdev->dev);
> +}
[Severity: Medium]
Should cancel_delayed_work_sync(&rkvenc->watchdog) (and stopping any active
IRQ/retry activity) happen after rkvenc_v4l2_cleanup() and
v4l2_m2m_release() have stopped new M2M jobs from starting, so that teardown
cannot leave rkvenc->watchdog queued when devres frees struct rkvenc_dev?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794666330.git.daniel@makrotopia.org?part=3
next prev parent reply other threads:[~2026-11-14 15:27 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
2026-11-14 14:54 ` Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
2026-11-14 14:54 ` Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree Daniel Golle
2026-11-14 14:54 ` Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 3/6] media: rockchip: Add RKVENC v1 video encoder driver Daniel Golle
2026-11-14 14:54 ` Daniel Golle
2026-11-14 15:27 ` sashiko-bot [this message]
2026-11-14 14:55 ` [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling Daniel Golle
2026-11-14 14:55 ` Daniel Golle
2026-11-14 15:21 ` sashiko-bot
2026-11-14 14:55 ` [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126 Daniel Golle
2026-11-14 14:55 ` Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points " Daniel Golle
2026-11-14 14:55 ` Daniel Golle
2026-11-14 15:15 ` sashiko-bot
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-214704@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=daniel@makrotopia.org \
--cc=devicetree@vger.kernel.org \
--cc=linux-clk@vger.kernel.org \
--cc=media-ci@linuxtv.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.