* [PATCH v2 01/11] staging: media: cedrus: free MV-col buffers in buf_cleanup
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 02/11] staging: media: cedrus: reject too large H.264/HEVC SPS Jernej Skrabec
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
v4l2_m2m_ctx_release() releases the CAPTURE queue before the OUTPUT
queue. When userspace closes the file handle without STREAMOFF, the
CAPTURE buffers are already gone by the time the OUTPUT stop_streaming
calls the codec stop() callback, so the per-buffer H.264/HEVC MV-col
buffers are never freed. This is easily reproduced by killing a player
during H.264 or HEVC decoding. Each killed 1080p session leaks about
2.3 MiB.
Add a buf_cleanup codec op, called from the vb2 buf_cleanup of the
CAPTURE queue, and free the MV-col buffer there. stop() keeps using the
same helper.
Fixes: fec94f8c9954 ("media: cedrus: h264: Optimize mv col buffer allocation")
Fixes: 0ee952c2f484 ("media: cedrus: h265: Associate mv col buffers with buffer")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.h | 1 +
.../staging/media/sunxi/cedrus/cedrus_h264.c | 28 +++++++++++--------
.../staging/media/sunxi/cedrus/cedrus_h265.c | 28 +++++++++++--------
.../staging/media/sunxi/cedrus/cedrus_video.c | 10 +++++++
4 files changed, 43 insertions(+), 24 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index c4b1217c14b6..54f867949fba 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -167,6 +167,7 @@ struct cedrus_dec_ops {
int (*setup)(struct cedrus_ctx *ctx, struct cedrus_run *run);
int (*start)(struct cedrus_ctx *ctx);
void (*stop)(struct cedrus_ctx *ctx);
+ void (*buf_cleanup)(struct cedrus_ctx *ctx, struct cedrus_buffer *buf);
void (*trigger)(struct cedrus_ctx *ctx);
unsigned int (*extra_cap_size)(struct cedrus_ctx *ctx,
struct v4l2_pix_format *pix_fmt);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index fc54d993b11f..892193c12b2f 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -647,10 +647,23 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx)
return ret;
}
+static void cedrus_h264_buf_cleanup(struct cedrus_ctx *ctx,
+ struct cedrus_buffer *buf)
+{
+ if (buf->codec.h264.mv_col_buf_size > 0) {
+ dma_free_attrs(ctx->dev->dev,
+ buf->codec.h264.mv_col_buf_size,
+ buf->codec.h264.mv_col_buf,
+ buf->codec.h264.mv_col_buf_dma,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+
+ buf->codec.h264.mv_col_buf_size = 0;
+ }
+}
+
static void cedrus_h264_stop(struct cedrus_ctx *ctx)
{
struct cedrus_dev *dev = ctx->dev;
- struct cedrus_buffer *buf;
struct vb2_queue *vq;
unsigned int i;
@@ -662,17 +675,7 @@ static void cedrus_h264_stop(struct cedrus_ctx *ctx)
if (!vb)
continue;
- buf = vb2_to_cedrus_buffer(vb);
-
- if (buf->codec.h264.mv_col_buf_size > 0) {
- dma_free_attrs(dev->dev,
- buf->codec.h264.mv_col_buf_size,
- buf->codec.h264.mv_col_buf,
- buf->codec.h264.mv_col_buf_dma,
- DMA_ATTR_NO_KERNEL_MAPPING);
-
- buf->codec.h264.mv_col_buf_size = 0;
- }
+ cedrus_h264_buf_cleanup(ctx, vb2_to_cedrus_buffer(vb));
}
dma_free_attrs(dev->dev, CEDRUS_NEIGHBOR_INFO_BUF_SIZE,
@@ -710,5 +713,6 @@ struct cedrus_dec_ops cedrus_dec_ops_h264 = {
.setup = cedrus_h264_setup,
.start = cedrus_h264_start,
.stop = cedrus_h264_stop,
+ .buf_cleanup = cedrus_h264_buf_cleanup,
.trigger = cedrus_h264_trigger,
};
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
index 780da4a8b5af..6c643862ab73 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
@@ -858,10 +858,23 @@ static int cedrus_h265_start(struct cedrus_ctx *ctx)
return 0;
}
+static void cedrus_h265_buf_cleanup(struct cedrus_ctx *ctx,
+ struct cedrus_buffer *buf)
+{
+ if (buf->codec.h265.mv_col_buf_size > 0) {
+ dma_free_attrs(ctx->dev->dev,
+ buf->codec.h265.mv_col_buf_size,
+ buf->codec.h265.mv_col_buf,
+ buf->codec.h265.mv_col_buf_dma,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+
+ buf->codec.h265.mv_col_buf_size = 0;
+ }
+}
+
static void cedrus_h265_stop(struct cedrus_ctx *ctx)
{
struct cedrus_dev *dev = ctx->dev;
- struct cedrus_buffer *buf;
struct vb2_queue *vq;
unsigned int i;
@@ -873,17 +886,7 @@ static void cedrus_h265_stop(struct cedrus_ctx *ctx)
if (!vb)
continue;
- buf = vb2_to_cedrus_buffer(vb);
-
- if (buf->codec.h265.mv_col_buf_size > 0) {
- dma_free_attrs(dev->dev,
- buf->codec.h265.mv_col_buf_size,
- buf->codec.h265.mv_col_buf,
- buf->codec.h265.mv_col_buf_dma,
- DMA_ATTR_NO_KERNEL_MAPPING);
-
- buf->codec.h265.mv_col_buf_size = 0;
- }
+ cedrus_h265_buf_cleanup(ctx, vb2_to_cedrus_buffer(vb));
}
dma_free_attrs(dev->dev, CEDRUS_H265_NEIGHBOR_INFO_BUF_SIZE,
@@ -918,6 +921,7 @@ struct cedrus_dec_ops cedrus_dec_ops_h265 = {
.setup = cedrus_h265_setup,
.start = cedrus_h265_start,
.stop = cedrus_h265_stop,
+ .buf_cleanup = cedrus_h265_buf_cleanup,
.trigger = cedrus_h265_trigger,
.extra_cap_size = cedrus_h265_extra_cap_size,
};
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index ad4ec3490775..adc7f255d7cf 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -560,6 +560,15 @@ static void cedrus_stop_streaming(struct vb2_queue *vq)
cedrus_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
}
+static void cedrus_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct cedrus_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) &&
+ ctx->current_codec && ctx->current_codec->buf_cleanup)
+ ctx->current_codec->buf_cleanup(ctx, vb2_to_cedrus_buffer(vb));
+}
+
static void cedrus_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
@@ -579,6 +588,7 @@ static const struct vb2_ops cedrus_qops = {
.queue_setup = cedrus_queue_setup,
.buf_prepare = cedrus_buf_prepare,
.buf_queue = cedrus_buf_queue,
+ .buf_cleanup = cedrus_buf_cleanup,
.buf_out_validate = cedrus_buf_out_validate,
.buf_request_complete = cedrus_buf_request_complete,
.start_streaming = cedrus_start_streaming,
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 02/11] staging: media: cedrus: reject too large H.264/HEVC SPS
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 01/11] staging: media: cedrus: free MV-col buffers in buf_cleanup Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 03/11] staging: media: cedrus: h264: mark long-term references in the frame info Jernej Skrabec
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The H.264 decoder takes the picture size from the SPS, and the HEVC
decoder the position of the slices. An SPS beyond CEDRUS_MAX_WIDTH/HEIGHT,
or beyond the capture buffers, makes the hardware overrun memory and
hang. On H616, the JCT-VC PICSIZE_A..D streams (1056x8440, 8440x1056,
528x4216 and 4216x528) leave the hardware wedged, so that every
following job times out until reboot.
Reject an SPS beyond the hardware limits in try_ctrl, and move the
limits to cedrus_video.h for that.
Whether the picture fits the buffers can only be checked when a job
runs: the SPS can be set before the buffers are allocated, while the
OUTPUT format they are sized from can still change, and a request
doesn't have to carry an SPS at all. Check it in the setup callbacks,
so that such a job fails with an error buffer instead.
Fixes: 6eb9b758e307 ("media: cedrus: Add H264 decoding support")
Fixes: 86caab29da78 ("media: cedrus: Add HEVC/H.265 decoding support")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.c | 16 +++++++++
drivers/staging/media/sunxi/cedrus/cedrus.h | 34 +++++++++++++++++++
.../staging/media/sunxi/cedrus/cedrus_h264.c | 6 ++++
.../staging/media/sunxi/cedrus/cedrus_h265.c | 4 +++
.../staging/media/sunxi/cedrus/cedrus_video.c | 5 ---
.../staging/media/sunxi/cedrus/cedrus_video.h | 5 +++
6 files changed, 65 insertions(+), 5 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c
index c3331b4f3f02..197213911bf7 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.c
@@ -28,6 +28,15 @@
#include "cedrus_dec.h"
#include "cedrus_hw.h"
+/*
+ * Reject picture sizes beyond the hardware limits. Whether the picture fits
+ * the buffers is checked when a job is set up, see cedrus_check_pic_size().
+ */
+static bool cedrus_pic_size_supported(u32 width, u32 height)
+{
+ return width <= CEDRUS_MAX_WIDTH && height <= CEDRUS_MAX_HEIGHT;
+}
+
static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl)
{
if (ctrl->id == V4L2_CID_STATELESS_H264_SPS) {
@@ -42,6 +51,9 @@ static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl)
if (sps->bit_depth_luma_minus8 != 0)
/* Only 8-bit is supported */
return -EINVAL;
+ if (!cedrus_pic_size_supported(cedrus_h264_sps_width(sps),
+ cedrus_h264_sps_height(sps)))
+ return -EINVAL;
} else if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) {
const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps;
struct cedrus_ctx *ctx = container_of(ctrl->handler, struct cedrus_ctx, hdl);
@@ -52,6 +64,10 @@ static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl)
/* Only 4:2:0 is supported */
return -EINVAL;
+ if (!cedrus_pic_size_supported(sps->pic_width_in_luma_samples,
+ sps->pic_height_in_luma_samples))
+ return -EINVAL;
+
bit_depth = max(sps->bit_depth_luma_minus8,
sps->bit_depth_chroma_minus8) + 8;
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index 54f867949fba..c2318521d7e5 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -274,6 +274,40 @@ cedrus_is_capable(struct cedrus_ctx *ctx, unsigned int capabilities)
return (ctx->dev->capabilities & capabilities) == capabilities;
}
+/*
+ * The decoders take the picture size, or the position of the slices, from
+ * the bitstream headers. Check that the picture fits the buffers, which are
+ * sized from the OUTPUT format and padded to at least 16 pixels in both
+ * directions, so that the hardware doesn't overrun them and hang.
+ */
+static inline int cedrus_check_pic_size(struct cedrus_ctx *ctx,
+ unsigned int width, unsigned int height)
+{
+ if (width > ALIGN(ctx->src_fmt.width, 16) ||
+ height > ALIGN(ctx->src_fmt.height, 16))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline unsigned int
+cedrus_h264_sps_width(const struct v4l2_ctrl_h264_sps *sps)
+{
+ return (sps->pic_width_in_mbs_minus1 + 1) * 16;
+}
+
+static inline unsigned int
+cedrus_h264_sps_height(const struct v4l2_ctrl_h264_sps *sps)
+{
+ unsigned int height = (sps->pic_height_in_map_units_minus1 + 1) * 16;
+
+ /* Map units are macroblock pairs, unless only frames are coded. */
+ if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY))
+ height *= 2;
+
+ return height;
+}
+
void *cedrus_find_control_data(struct cedrus_ctx *ctx, u32 id);
u32 cedrus_get_num_of_controls(struct cedrus_ctx *ctx, u32 id);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index 892193c12b2f..e8229c60d3f5 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -518,9 +518,15 @@ static void cedrus_h264_irq_disable(struct cedrus_ctx *ctx)
static int cedrus_h264_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
{
+ const struct v4l2_ctrl_h264_sps *sps = run->h264.sps;
struct cedrus_dev *dev = ctx->dev;
int ret;
+ ret = cedrus_check_pic_size(ctx, cedrus_h264_sps_width(sps),
+ cedrus_h264_sps_height(sps));
+ if (ret)
+ return ret;
+
cedrus_engine_enable(ctx);
cedrus_write(dev, VE_H264_SDROT_CTRL, 0);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
index 6c643862ab73..aa772a8d71c8 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
@@ -445,6 +445,10 @@ static int cedrus_h265_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
cedrus_buf = vb2_to_cedrus_buffer(&run->dst->vb2_buf);
slice_bytes = vb2_get_plane_payload(&run->src->vb2_buf, 0);
+ if (cedrus_check_pic_size(ctx, sps->pic_width_in_luma_samples,
+ sps->pic_height_in_luma_samples))
+ return -EINVAL;
+
/*
* If entry points offsets are present, we should get them
* exactly the right amount.
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index adc7f255d7cf..2521315abdb7 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -29,11 +29,6 @@
#define CEDRUS_DECODE_SRC BIT(0)
#define CEDRUS_DECODE_DST BIT(1)
-#define CEDRUS_MIN_WIDTH 16U
-#define CEDRUS_MIN_HEIGHT 16U
-#define CEDRUS_MAX_WIDTH 4096U
-#define CEDRUS_MAX_HEIGHT 2304U
-
static struct cedrus_format cedrus_formats[] = {
{
.pixelformat = V4L2_PIX_FMT_MPEG2_SLICE,
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.h b/drivers/staging/media/sunxi/cedrus/cedrus_video.h
index 8e1afc16a6a1..367e52158f01 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.h
@@ -22,6 +22,11 @@ struct cedrus_format {
unsigned int capabilities;
};
+#define CEDRUS_MIN_WIDTH 16U
+#define CEDRUS_MIN_HEIGHT 16U
+#define CEDRUS_MAX_WIDTH 4096U
+#define CEDRUS_MAX_HEIGHT 2304U
+
extern const struct v4l2_ioctl_ops cedrus_ioctl_ops;
int cedrus_queue_init(void *priv, struct vb2_queue *src_vq,
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 03/11] staging: media: cedrus: h264: mark long-term references in the frame info
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 01/11] staging: media: cedrus: free MV-col buffers in buf_cleanup Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 02/11] staging: media: cedrus: reject too large H.264/HEVC SPS Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 04/11] staging: media: cedrus: mpeg2: use the field height for field pictures Jernej Skrabec
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The frame info of a reference has a reference type for each field, in
bits 1:0 for the top field and 5:4 for the bottom one, which is 1 for a
long-term reference. The driver never sets it, so the hardware handles
long-term references like short-term ones, also where they differ, like
in temporal direct and implicit weighted prediction. The vendor library
sets it.
Set it from the DPB entry of each reference. When the second field of a
pair is decoded into the buffer of the first one, the entry of the
output buffer keeps the reference type of the first field.
This fixes MR8_BT_B and MR9_BT_B of the H.264 conformance suite.
Fixes: 6eb9b758e307 ("media: cedrus: Add H264 decoding support")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
.../staging/media/sunxi/cedrus/cedrus_h264.c | 24 ++++++++++++++++++-
.../staging/media/sunxi/cedrus/cedrus_regs.h | 8 +++++++
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index e8229c60d3f5..97958ceee8d1 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -69,13 +69,19 @@ static void cedrus_fill_ref_pic(struct cedrus_ctx *ctx,
struct cedrus_buffer *buf,
unsigned int top_field_order_cnt,
unsigned int bottom_field_order_cnt,
+ u32 ref_type,
struct cedrus_h264_sram_ref_pic *pic)
{
struct vb2_buffer *vbuf = &buf->m2m_buf.vb.vb2_buf;
+ u32 frame_info;
pic->top_field_order_cnt = cpu_to_le32(top_field_order_cnt);
pic->bottom_field_order_cnt = cpu_to_le32(bottom_field_order_cnt);
- pic->frame_info = cpu_to_le32(buf->codec.h264.pic_type << 8);
+
+ frame_info = VE_H264_FRAME_INFO_TOP_REF_TYPE(ref_type) |
+ VE_H264_FRAME_INFO_BOT_REF_TYPE(ref_type) |
+ VE_H264_FRAME_INFO_PIC_TYPE(buf->codec.h264.pic_type);
+ pic->frame_info = cpu_to_le32(frame_info);
pic->luma_ptr = cpu_to_le32(cedrus_buf_addr(vbuf, &ctx->dst_fmt, 0));
pic->chroma_ptr = cpu_to_le32(cedrus_buf_addr(vbuf, &ctx->dst_fmt, 1));
@@ -83,12 +89,21 @@ static void cedrus_fill_ref_pic(struct cedrus_ctx *ctx,
pic->mv_col_bot_ptr = cpu_to_le32(cedrus_h264_mv_col_buf_addr(buf, 1));
}
+static u32 cedrus_h264_ref_type(const struct v4l2_h264_dpb_entry *dpb)
+{
+ if (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ return VE_H264_FRAME_INFO_REF_TYPE_LONG_TERM;
+
+ return VE_H264_FRAME_INFO_REF_TYPE_SHORT_TERM;
+}
+
static int cedrus_write_frame_list(struct cedrus_ctx *ctx,
struct cedrus_run *run)
{
struct cedrus_h264_sram_ref_pic pic_list[CEDRUS_H264_FRAME_NUM];
const struct v4l2_ctrl_h264_decode_params *decode = run->h264.decode_params;
const struct v4l2_ctrl_h264_sps *sps = run->h264.sps;
+ u32 output_ref_type = VE_H264_FRAME_INFO_REF_TYPE_SHORT_TERM;
struct vb2_queue *cap_q;
struct cedrus_buffer *output_buf;
struct cedrus_dev *dev = ctx->dev;
@@ -117,8 +132,13 @@ static int cedrus_write_frame_list(struct cedrus_ctx *ctx,
position = cedrus_buf->codec.h264.position;
used_dpbs |= BIT(position);
+ /*
+ * The second field of a pair is decoded into the buffer of the
+ * first one, which keeps its reference type.
+ */
if (run->dst->vb2_buf.timestamp == dpb->reference_ts) {
output = position;
+ output_ref_type = cedrus_h264_ref_type(dpb);
continue;
}
@@ -128,6 +148,7 @@ static int cedrus_write_frame_list(struct cedrus_ctx *ctx,
cedrus_fill_ref_pic(ctx, cedrus_buf,
dpb->top_field_order_cnt,
dpb->bottom_field_order_cnt,
+ cedrus_h264_ref_type(dpb),
&pic_list[position]);
}
@@ -174,6 +195,7 @@ static int cedrus_write_frame_list(struct cedrus_ctx *ctx,
cedrus_fill_ref_pic(ctx, output_buf,
decode->top_field_order_cnt,
decode->bottom_field_order_cnt,
+ output_ref_type,
&pic_list[position]);
cedrus_h264_write_sram(dev, CEDRUS_SRAM_H264_FRAMEBUFFER_LIST,
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index 05e6cbc548ab..961fe023bb43 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -616,6 +616,14 @@
#define VE_AVC_SRAM_PORT_OFFSET 0x2e0
#define VE_AVC_SRAM_PORT_DATA 0x2e4
+/* Frame info word of a frame buffer list entry in the AVC SRAM */
+#define VE_H264_FRAME_INFO_PIC_TYPE(v) SHIFT_AND_MASK_BITS(v, 9, 8)
+#define VE_H264_FRAME_INFO_BOT_REF_TYPE(v) SHIFT_AND_MASK_BITS(v, 5, 4)
+#define VE_H264_FRAME_INFO_TOP_REF_TYPE(v) SHIFT_AND_MASK_BITS(v, 1, 0)
+
+#define VE_H264_FRAME_INFO_REF_TYPE_SHORT_TERM 0
+#define VE_H264_FRAME_INFO_REF_TYPE_LONG_TERM 1
+
#define VE_VP8_PPS 0x214
#define VE_VP8_PPS_PIC_TYPE_P_FRAME BIT(31)
#define VE_VP8_PPS_LAST_SHARPNESS_LEVEL(v) SHIFT_AND_MASK_BITS(v, 30, 28)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 04/11] staging: media: cedrus: mpeg2: use the field height for field pictures
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (2 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 03/11] staging: media: cedrus: h264: mark long-term references in the frame info Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 05/11] staging: media: cedrus: mpeg2: derive the field order of " Jernej Skrabec
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
A field picture covers only half of the macroblock rows of a frame, but
the driver programs the coded and the boundary size of the whole frame
for it, as for a frame picture.
Program the height of a field for field pictures, like the vendor
library does. Like the vendor library, halve the boundary height after
aligning it to whole macroblocks, so that the boundary of a 1080 line
picture covers all 544 decoded lines of a field.
Fixes: 50e761516f2b ("media: platform: Add Cedrus VPU decoder driver")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
index 10e98f08aafc..8252a47994aa 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
@@ -58,6 +58,7 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
struct vb2_queue *vq;
const u8 *matrix;
unsigned int i;
+ bool field;
u32 reg;
seq = run->mpeg2.sequence;
@@ -108,13 +109,23 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
/* Set frame dimensions. */
+ /*
+ * Field pictures cover half of the frame's macroblock rows; the
+ * vendor library halves both the coded and the boundary height, the
+ * latter aligned to whole macroblocks.
+ */
+ field = pic->picture_structure != V4L2_MPEG2_PIC_FRAME;
+
reg = VE_DEC_MPEG_PICCODEDSIZE_WIDTH(seq->horizontal_size);
- reg |= VE_DEC_MPEG_PICCODEDSIZE_HEIGHT(seq->vertical_size);
+ reg |= VE_DEC_MPEG_PICCODEDSIZE_HEIGHT(field ?
+ ALIGN(seq->vertical_size, 32) / 2 : seq->vertical_size);
cedrus_write(dev, VE_DEC_MPEG_PICCODEDSIZE, reg);
reg = VE_DEC_MPEG_PICBOUNDSIZE_WIDTH(ctx->src_fmt.width);
- reg |= VE_DEC_MPEG_PICBOUNDSIZE_HEIGHT(ctx->src_fmt.height);
+ reg |= VE_DEC_MPEG_PICBOUNDSIZE_HEIGHT(field ?
+ ALIGN(ctx->src_fmt.height, 16) / 2 :
+ ctx->src_fmt.height);
cedrus_write(dev, VE_DEC_MPEG_PICBOUNDSIZE, reg);
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 05/11] staging: media: cedrus: mpeg2: derive the field order of field pictures
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (3 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 04/11] staging: media: cedrus: mpeg2: use the field height for field pictures Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 06/11] staging: media: cedrus: mpeg2: fix the references " Jernej Skrabec
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The hardware uses the top field first bit of the picture header to tell
the first field of a frame from the second one. However,
top_field_first is always 0 in field pictures, so userspace passes
V4L2_MPEG2_PIC_FLAG_TOP_FIELD_FIRST cleared, and the second field of
every top field first pair is predicted from the wrong reference.
Detect the second field in the driver instead: it has the opposite
parity of the previous field and is decoded into the same capture
buffer. This works with unmodified userspace.
Fixes: 50e761516f2b ("media: platform: Add Cedrus VPU decoder driver")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.h | 5 +++
.../staging/media/sunxi/cedrus/cedrus_mpeg2.c | 32 +++++++++++++++++--
2 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index c2318521d7e5..572e70a94b3f 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -144,6 +144,11 @@ struct cedrus_ctx {
void *entry_points_buf;
dma_addr_t entry_points_buf_addr;
} h265;
+ struct {
+ bool pair_pending;
+ unsigned int pair_dst;
+ u8 pair_structure;
+ } mpeg2;
struct {
unsigned int last_frame_p_type;
unsigned int last_filter_type;
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
index 8252a47994aa..315b0e5b3c88 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
@@ -58,7 +58,7 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
struct vb2_queue *vq;
const u8 *matrix;
unsigned int i;
- bool field;
+ bool field, tff;
u32 reg;
seq = run->mpeg2.sequence;
@@ -87,6 +87,26 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
cedrus_write(dev, VE_DEC_MPEG_IQMINPUT, reg);
}
+ /*
+ * top_field_first is always 0 in field pictures, but the hardware
+ * uses this bit to tell the first field of a frame from the second.
+ * A field is the second one when it has the opposite parity of the
+ * previous field and is decoded into the same capture buffer.
+ */
+ if (pic->picture_structure == V4L2_MPEG2_PIC_FRAME) {
+ tff = !!(pic->flags & V4L2_MPEG2_PIC_FLAG_TOP_FIELD_FIRST);
+ ctx->codec.mpeg2.pair_pending = false;
+ } else {
+ bool second = ctx->codec.mpeg2.pair_pending &&
+ ctx->codec.mpeg2.pair_structure != pic->picture_structure &&
+ ctx->codec.mpeg2.pair_dst == run->dst->vb2_buf.index;
+
+ tff = (pic->picture_structure == V4L2_MPEG2_PIC_TOP_FIELD) != second;
+ ctx->codec.mpeg2.pair_pending = !second;
+ ctx->codec.mpeg2.pair_structure = pic->picture_structure;
+ ctx->codec.mpeg2.pair_dst = run->dst->vb2_buf.index;
+ }
+
/* Set MPEG picture header. */
reg = VE_DEC_MPEG_MP12HDR_SLICE_TYPE(pic->picture_coding_type);
@@ -96,7 +116,7 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
reg |= VE_DEC_MPEG_MP12HDR_F_CODE(1, 1, pic->f_code[1][1]);
reg |= VE_DEC_MPEG_MP12HDR_INTRA_DC_PRECISION(pic->intra_dc_precision);
reg |= VE_DEC_MPEG_MP12HDR_INTRA_PICTURE_STRUCTURE(pic->picture_structure);
- reg |= VE_DEC_MPEG_MP12HDR_TOP_FIELD_FIRST(pic->flags & V4L2_MPEG2_PIC_FLAG_TOP_FIELD_FIRST);
+ reg |= VE_DEC_MPEG_MP12HDR_TOP_FIELD_FIRST(tff);
reg |= VE_DEC_MPEG_MP12HDR_FRAME_PRED_FRAME_DCT(pic->flags & V4L2_MPEG2_PIC_FLAG_FRAME_PRED_DCT);
reg |= VE_DEC_MPEG_MP12HDR_CONCEALMENT_MOTION_VECTORS(pic->flags & V4L2_MPEG2_PIC_FLAG_CONCEALMENT_MV);
reg |= VE_DEC_MPEG_MP12HDR_Q_SCALE_TYPE(pic->flags & V4L2_MPEG2_PIC_FLAG_Q_SCALE_TYPE);
@@ -188,6 +208,13 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
return 0;
}
+static int cedrus_mpeg2_start(struct cedrus_ctx *ctx)
+{
+ memset(&ctx->codec.mpeg2, 0, sizeof(ctx->codec.mpeg2));
+
+ return 0;
+}
+
static void cedrus_mpeg2_trigger(struct cedrus_ctx *ctx)
{
struct cedrus_dev *dev = ctx->dev;
@@ -205,5 +232,6 @@ struct cedrus_dec_ops cedrus_dec_ops_mpeg2 = {
.irq_disable = cedrus_mpeg2_irq_disable,
.irq_status = cedrus_mpeg2_irq_status,
.setup = cedrus_mpeg2_setup,
+ .start = cedrus_mpeg2_start,
.trigger = cedrus_mpeg2_trigger,
};
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 06/11] staging: media: cedrus: mpeg2: fix the references of field pictures
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (4 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 05/11] staging: media: cedrus: mpeg2: derive the field order of " Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 07/11] staging: media: cedrus: validate VP8 frame size Jernej Skrabec
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The second field of an I or P frame can be predicted from the first
field of the same frame, which the hardware takes from the backward
reference. The driver programs the backward reference passed by
userspace instead, which doesn't point to the frame being decoded, so
the second field is predicted from the wrong picture.
Use the current frame as the backward reference of I and P pictures,
like the vendor library does. Also fall back to the current frame
instead of address 0 when a reference buffer is missing, so the
hardware never reads from an invalid address.
Fixes: 50e761516f2b ("media: platform: Add Cedrus VPU decoder driver")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
.../staging/media/sunxi/cedrus/cedrus_mpeg2.c | 29 ++++++++++++++-----
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
index 315b0e5b3c88..9bd7f3ded83a 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
@@ -56,6 +56,7 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
dma_addr_t src_buf_addr, dst_luma_addr, dst_chroma_addr;
struct cedrus_dev *dev = ctx->dev;
struct vb2_queue *vq;
+ struct vb2_buffer *fwd, *bwd;
const u8 *matrix;
unsigned int i;
bool field, tff;
@@ -149,15 +150,29 @@ static int cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
cedrus_write(dev, VE_DEC_MPEG_PICBOUNDSIZE, reg);
- /* Forward and backward prediction reference buffers. */
+ /*
+ * Forward and backward prediction reference buffers. As the vendor
+ * library does, I and P pictures use the current frame as backward
+ * reference: the second field of a frame predicts from the first one.
+ * Missing references also fall back to the current frame.
+ */
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
- cedrus_write_ref_buf_addr(ctx, vq, pic->forward_ref_ts,
- VE_DEC_MPEG_FWD_REF_LUMA_ADDR,
- VE_DEC_MPEG_FWD_REF_CHROMA_ADDR);
- cedrus_write_ref_buf_addr(ctx, vq, pic->backward_ref_ts,
- VE_DEC_MPEG_BWD_REF_LUMA_ADDR,
- VE_DEC_MPEG_BWD_REF_CHROMA_ADDR);
+ fwd = vb2_find_buffer(vq, pic->forward_ref_ts) ?: &run->dst->vb2_buf;
+ if (pic->picture_coding_type == V4L2_MPEG2_PIC_CODING_TYPE_B)
+ bwd = vb2_find_buffer(vq, pic->backward_ref_ts) ?:
+ &run->dst->vb2_buf;
+ else
+ bwd = &run->dst->vb2_buf;
+
+ cedrus_write(dev, VE_DEC_MPEG_FWD_REF_LUMA_ADDR,
+ cedrus_dst_buf_addr(ctx, fwd, 0));
+ cedrus_write(dev, VE_DEC_MPEG_FWD_REF_CHROMA_ADDR,
+ cedrus_dst_buf_addr(ctx, fwd, 1));
+ cedrus_write(dev, VE_DEC_MPEG_BWD_REF_LUMA_ADDR,
+ cedrus_dst_buf_addr(ctx, bwd, 0));
+ cedrus_write(dev, VE_DEC_MPEG_BWD_REF_CHROMA_ADDR,
+ cedrus_dst_buf_addr(ctx, bwd, 1));
/* Destination luma and chroma buffers. */
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 07/11] staging: media: cedrus: validate VP8 frame size
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (5 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 06/11] staging: media: cedrus: mpeg2: fix the references " Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 08/11] staging: media: cedrus: vp8: use the vendor probability table size Jernej Skrabec
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
A VP8 key frame can change the frame size. If userspace doesn't
reallocate the capture buffers, the hardware writes and reads past the
buffers, which shows up as IOMMU page faults and decode timeouts.
Like for the H.264 and HEVC SPS, reject frames beyond the hardware
limits in try_ctrl, and fail the job in the setup callback if the frame
doesn't fit the buffers. The VP8 frame size register holds the size in
8-bit macroblock counts, so VP8 frames are limited to 4080 pixels in
width.
Fixes: 3c39a16d30d8 ("media: cedrus: Add support for VP8 decoding")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.c | 10 ++++++++++
drivers/staging/media/sunxi/cedrus/cedrus_vp8.c | 3 +++
2 files changed, 13 insertions(+)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c
index 197213911bf7..6c6aa45f5f96 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.c
@@ -28,6 +28,8 @@
#include "cedrus_dec.h"
#include "cedrus_hw.h"
+#define CEDRUS_VP8_MAX_WIDTH 4080U
+
/*
* Reject picture sizes beyond the hardware limits. Whether the picture fits
* the buffers is checked when a job is set up, see cedrus_check_pic_size().
@@ -54,6 +56,13 @@ static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl)
if (!cedrus_pic_size_supported(cedrus_h264_sps_width(sps),
cedrus_h264_sps_height(sps)))
return -EINVAL;
+ } else if (ctrl->id == V4L2_CID_STATELESS_VP8_FRAME) {
+ const struct v4l2_ctrl_vp8_frame *frame = ctrl->p_new.p_vp8_frame;
+
+ /* The VP8 frame size register holds 8-bit macroblock counts. */
+ if (!cedrus_pic_size_supported(frame->width, frame->height) ||
+ frame->width > CEDRUS_VP8_MAX_WIDTH)
+ return -EINVAL;
} else if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) {
const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps;
struct cedrus_ctx *ctx = container_of(ctrl->handler, struct cedrus_ctx, hdl);
@@ -248,6 +257,7 @@ static const struct cedrus_control cedrus_controls[] = {
{
.cfg = {
.id = V4L2_CID_STATELESS_VP8_FRAME,
+ .ops = &cedrus_ctrl_ops,
},
.capabilities = CEDRUS_CAPABILITY_VP8_DEC,
},
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
index 969677a3bbf9..fecca343bd92 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
@@ -662,6 +662,9 @@ static int cedrus_vp8_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
int header_size;
u32 reg;
+ if (cedrus_check_pic_size(ctx, slice->width, slice->height))
+ return -EINVAL;
+
cedrus_engine_enable(ctx);
cedrus_write(dev, VE_H264_CTRL, VE_H264_CTRL_VP8);
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 08/11] staging: media: cedrus: vp8: use the vendor probability table size
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (6 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 07/11] staging: media: cedrus: validate VP8 frame size Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 09/11] staging: media: cedrus: h616: use the 256-bit DDR mode Jernej Skrabec
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The driver allocates the VP8 probability table with 0x2400 bytes, which
is the size the old Allwinner A10 library uses. All newer vendor
libraries allocate 0x5400 bytes and never write the area past 0x11b0
from the CPU. The hardware uses that area as scratch space, most likely
for the segment map, and its usage grows with the frame size: a
3840x2160 stream makes the hardware access memory past 0x2400 and
causes IOMMU page faults.
Allocate the table with 0x5400 bytes, like the vendor libraries do.
Fixes: 3c39a16d30d8 ("media: cedrus: Add support for VP8 decoding")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus_vp8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
index fecca343bd92..84bf18fd231d 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
@@ -25,7 +25,7 @@
#include "cedrus_hw.h"
#include "cedrus_regs.h"
-#define CEDRUS_ENTROPY_PROBS_SIZE 0x2400
+#define CEDRUS_ENTROPY_PROBS_SIZE 0x5400
#define VP8_PROB_HALF 128
#define QUANT_DELTA_COUNT 5
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 09/11] staging: media: cedrus: h616: use the 256-bit DDR mode
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (7 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 08/11] staging: media: cedrus: vp8: use the vendor probability table size Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 10/11] staging: media: cedrus: h264: clear the DRAM buffer sizes on start Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 11/11] staging: media: cedrus: vp8: support frames wider than 2048 pixels Jernej Skrabec
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
With the DDR mode in VE_MODE set to 128-bit, which the driver uses on
all SoCs, the H616 H.264 engine reads corrupted reference chroma, while
luma is fine. H.264 and VP8 P frames come out with bit-exact luma, but
with about 22 dB PSNR chroma. H.265 and MPEG-2 are unaffected.
The vendor library selects the DDR mode based on the VE IC version. It
uses the 256-bit mode without the 2 MiB reconstruction write mode on
the H616, and keeps the 128-bit mode with the 2 MiB reconstruction
write mode on older SoCs, like the H6. Doing the same on the H616 makes
all codecs bit-exact (tested with BA_MW_D, CABA2_Sony_E,
vp80-00-comprehensive-002, AMP_A_Samsung_7 and MEI.stream16).
Make the VE_MODE DDR settings a variant property and add an H616
variant, which uses the 256-bit mode. Replace the FIXME about the
DDR mode with a description of what the vendor library does.
Fixes: 1f24d4307515 ("staging: media: sunxi: cedrus: add H616 variant")
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.c | 14 +++++++++++++-
drivers/staging/media/sunxi/cedrus/cedrus.h | 2 ++
drivers/staging/media/sunxi/cedrus/cedrus_hw.c | 14 ++++++++++----
3 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c
index 6c6aa45f5f96..09b789640646 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.c
@@ -27,6 +27,7 @@
#include "cedrus_video.h"
#include "cedrus_dec.h"
#include "cedrus_hw.h"
+#include "cedrus_regs.h"
#define CEDRUS_VP8_MAX_WIDTH 4080U
@@ -679,6 +680,17 @@ static const struct cedrus_variant sun50i_h6_cedrus_variant = {
.mod_rate = 600000000,
};
+static const struct cedrus_variant sun50i_h616_cedrus_variant = {
+ .capabilities = CEDRUS_CAPABILITY_UNTILED |
+ CEDRUS_CAPABILITY_MPEG2_DEC |
+ CEDRUS_CAPABILITY_H264_DEC |
+ CEDRUS_CAPABILITY_H265_DEC |
+ CEDRUS_CAPABILITY_H265_10_DEC |
+ CEDRUS_CAPABILITY_VP8_DEC,
+ .mod_rate = 600000000,
+ .ve_mode_ddr = VE_MODE_DDR_MODE_BW_256,
+};
+
static const struct of_device_id cedrus_dt_match[] = {
{
.compatible = "allwinner,sun4i-a10-video-engine",
@@ -726,7 +738,7 @@ static const struct of_device_id cedrus_dt_match[] = {
},
{
.compatible = "allwinner,sun50i-h616-video-engine",
- .data = &sun50i_h6_cedrus_variant,
+ .data = &sun50i_h616_cedrus_variant,
},
{ /* sentinel */ }
};
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index 572e70a94b3f..78efd3938c77 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -181,6 +181,7 @@ struct cedrus_dec_ops {
struct cedrus_variant {
unsigned int capabilities;
unsigned int mod_rate;
+ u32 ve_mode_ddr;
};
struct cedrus_dev {
@@ -204,6 +205,7 @@ struct cedrus_dev {
struct reset_control *rstc;
unsigned int capabilities;
+ u32 ve_mode_ddr;
struct delayed_work watchdog_work;
};
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
index 444fb53878d1..b66d855daaf8 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
@@ -36,11 +36,16 @@ int cedrus_engine_enable(struct cedrus_ctx *ctx)
u32 reg = 0;
/*
- * FIXME: This is only valid on 32-bits DDR's, we should test
- * it on the A13/A33.
+ * The DDR mode depends on the SoC. Like the vendor library, use the
+ * 128-bit mode with the 2 MiB reconstruction write mode, unless the
+ * variant says otherwise.
*/
- reg |= VE_MODE_REC_WR_MODE_2MB;
- reg |= VE_MODE_DDR_MODE_BW_128;
+ if (ctx->dev->ve_mode_ddr) {
+ reg |= ctx->dev->ve_mode_ddr;
+ } else {
+ reg |= VE_MODE_REC_WR_MODE_2MB;
+ reg |= VE_MODE_DDR_MODE_BW_128;
+ }
switch (ctx->src_fmt.pixelformat) {
case V4L2_PIX_FMT_MPEG2_SLICE:
@@ -254,6 +259,7 @@ int cedrus_hw_probe(struct cedrus_dev *dev)
return -EINVAL;
dev->capabilities = variant->capabilities;
+ dev->ve_mode_ddr = variant->ve_mode_ddr;
irq_dec = platform_get_irq(dev->pdev, 0);
if (irq_dec <= 0)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 10/11] staging: media: cedrus: h264: clear the DRAM buffer sizes on start
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (8 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 09/11] staging: media: cedrus: h616: use the 256-bit DDR mode Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
2026-11-09 17:50 ` [PATCH v2 11/11] staging: media: cedrus: vp8: support frames wider than 2048 pixels Jernej Skrabec
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
Above 2048 pixels, the H.264 decoder puts its deblocking and intra
prediction buffers into DRAM, and stop() frees them when their sizes
are not zero. start() only sets the sizes for wide pictures and nothing
clears them, so a narrow stream after a wide one on the same file
handle frees the buffers of the wide one again. The codec data is also
shared with the other codecs, whose data can end up in the sizes.
Clear the sizes at the start of start().
Fixes: 03e612e701a6 ("media: cedrus: Fix H264 4k support")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus_h264.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index 97958ceee8d1..e7a3bfbc3531 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -573,6 +573,14 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx)
unsigned int pic_info_size;
int ret;
+ /*
+ * The sizes tell stop() whether the DRAM buffers below were
+ * allocated. Clear them, as they are only set for wide pictures and
+ * the codec data is shared with the other codecs.
+ */
+ ctx->codec.h264.deblk_buf_size = 0;
+ ctx->codec.h264.intra_pred_buf_size = 0;
+
/*
* NOTE: All buffers allocated here are only used by HW, so we
* can add DMA_ATTR_NO_KERNEL_MAPPING flag when allocating them.
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 11/11] staging: media: cedrus: vp8: support frames wider than 2048 pixels
2026-11-09 17:50 [PATCH v2 00/11] staging: media: cedrus: assorted fixes and VP8 4K support Jernej Skrabec
` (9 preceding siblings ...)
2026-11-09 17:50 ` [PATCH v2 10/11] staging: media: cedrus: h264: clear the DRAM buffer sizes on start Jernej Skrabec
@ 2026-11-09 17:50 ` Jernej Skrabec
10 siblings, 0 replies; 12+ messages in thread
From: Jernej Skrabec @ 2026-11-09 17:50 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Maxime Ripard, Paul Kocialkowski, Greg Kroah-Hartman,
Chen-Yu Tsai, Samuel Holland, Nicolas Dufresne, linux-media,
linux-staging, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
Above 2048 pixels, the deblocking and intra prediction row buffers don't
fit into the internal SRAM anymore. Put them into DRAM like H.264 does.
The vendor library sizes the deblocking buffer as 24 bytes per column.
The buffer sizes also tell whether the buffers are in use. Set them only
once both buffers are allocated, and clear them first, as the codec data
is shared with the other codecs.
Tested on H616 with 2560x1440 and 3840x2160 libvpx streams, which are
bit-exact with the FFmpeg software decoder.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/staging/media/sunxi/cedrus/cedrus.h | 7 ++
.../staging/media/sunxi/cedrus/cedrus_vp8.c | 67 +++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index 78efd3938c77..e26bbbf2e55d 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -156,6 +156,13 @@ struct cedrus_ctx {
u8 *entropy_probs_buf;
dma_addr_t entropy_probs_buf_dma;
+
+ void *deblk_buf;
+ dma_addr_t deblk_buf_dma;
+ ssize_t deblk_buf_size;
+ void *intra_pred_buf;
+ dma_addr_t intra_pred_buf_dma;
+ ssize_t intra_pred_buf_size;
} vp8;
} codec;
};
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
index 84bf18fd231d..bb32232d37ff 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_vp8.c
@@ -669,6 +669,20 @@ static int cedrus_vp8_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
cedrus_write(dev, VE_H264_CTRL, VE_H264_CTRL_VP8);
+ if (ctx->codec.vp8.deblk_buf_size) {
+ cedrus_write(dev, VE_BUF_CTRL,
+ VE_BUF_CTRL_INTRAPRED_MIXED_RAM |
+ VE_BUF_CTRL_DBLK_MIXED_RAM);
+ cedrus_write(dev, VE_DBLK_DRAM_BUF_ADDR,
+ ctx->codec.vp8.deblk_buf_dma);
+ cedrus_write(dev, VE_INTRAPRED_DRAM_BUF_ADDR,
+ ctx->codec.vp8.intra_pred_buf_dma);
+ } else {
+ cedrus_write(dev, VE_BUF_CTRL,
+ VE_BUF_CTRL_INTRAPRED_INT_SRAM |
+ VE_BUF_CTRL_DBLK_INT_SRAM);
+ }
+
cedrus_vp8_update_probs(slice, ctx->codec.vp8.entropy_probs_buf);
reg = slice->first_part_size * 8;
@@ -837,6 +851,11 @@ static int cedrus_vp8_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
static int cedrus_vp8_start(struct cedrus_ctx *ctx)
{
struct cedrus_dev *dev = ctx->dev;
+ unsigned int deblk_size, intra_pred_size;
+
+ /* The codec data is shared with the other codecs. */
+ ctx->codec.vp8.deblk_buf_size = 0;
+ ctx->codec.vp8.intra_pred_buf_size = 0;
ctx->codec.vp8.entropy_probs_buf =
dma_alloc_coherent(dev->dev, CEDRUS_ENTROPY_PROBS_SIZE,
@@ -852,7 +871,42 @@ static int cedrus_vp8_start(struct cedrus_ctx *ctx)
memcpy(&ctx->codec.vp8.entropy_probs_buf[2048],
prob_table_init, sizeof(prob_table_init));
+ /*
+ * Above 2048 pixels the deblocking and intra prediction row buffers
+ * no longer fit into the internal SRAM. The vendor library sizes the
+ * deblocking buffer as 24 bytes per (32 aligned) column.
+ */
+ if (ctx->src_fmt.width > 2048) {
+ deblk_size = ALIGN(ctx->src_fmt.width, 32) * 24;
+ ctx->codec.vp8.deblk_buf =
+ dma_alloc_attrs(dev->dev, deblk_size,
+ &ctx->codec.vp8.deblk_buf_dma,
+ GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!ctx->codec.vp8.deblk_buf)
+ goto err_probs;
+
+ intra_pred_size = ALIGN(ctx->src_fmt.width, 64) * 5 * 2;
+ ctx->codec.vp8.intra_pred_buf =
+ dma_alloc_attrs(dev->dev, intra_pred_size,
+ &ctx->codec.vp8.intra_pred_buf_dma,
+ GFP_KERNEL, DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!ctx->codec.vp8.intra_pred_buf)
+ goto err_deblk;
+
+ ctx->codec.vp8.deblk_buf_size = deblk_size;
+ ctx->codec.vp8.intra_pred_buf_size = intra_pred_size;
+ }
+
return 0;
+
+err_deblk:
+ dma_free_attrs(dev->dev, deblk_size, ctx->codec.vp8.deblk_buf,
+ ctx->codec.vp8.deblk_buf_dma, DMA_ATTR_NO_KERNEL_MAPPING);
+err_probs:
+ dma_free_coherent(dev->dev, CEDRUS_ENTROPY_PROBS_SIZE,
+ ctx->codec.vp8.entropy_probs_buf,
+ ctx->codec.vp8.entropy_probs_buf_dma);
+ return -ENOMEM;
}
static void cedrus_vp8_stop(struct cedrus_ctx *ctx)
@@ -864,6 +918,19 @@ static void cedrus_vp8_stop(struct cedrus_ctx *ctx)
dma_free_coherent(dev->dev, CEDRUS_ENTROPY_PROBS_SIZE,
ctx->codec.vp8.entropy_probs_buf,
ctx->codec.vp8.entropy_probs_buf_dma);
+
+ if (ctx->codec.vp8.deblk_buf_size) {
+ dma_free_attrs(dev->dev, ctx->codec.vp8.deblk_buf_size,
+ ctx->codec.vp8.deblk_buf,
+ ctx->codec.vp8.deblk_buf_dma,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ dma_free_attrs(dev->dev, ctx->codec.vp8.intra_pred_buf_size,
+ ctx->codec.vp8.intra_pred_buf,
+ ctx->codec.vp8.intra_pred_buf_dma,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ ctx->codec.vp8.deblk_buf_size = 0;
+ ctx->codec.vp8.intra_pred_buf_size = 0;
+ }
}
static void cedrus_vp8_trigger(struct cedrus_ctx *ctx)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread