From: sashiko-bot@kernel.org
To: "Zhentao Guo" <zhentao.guo@amlogic.com>
Cc: linux-amlogic@lists.infradead.org, devicetree@vger.kernel.org,
robh@kernel.org, conor+dt@kernel.org, neil.armstrong@linaro.org,
media-ci@linuxtv.org
Subject: Re: [PATCH v12 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
Date: Thu, 22 Oct 2026 06:01:18 +0000 [thread overview]
Message-ID: <sashiko-outbox-181046@kernel.org> (raw)
In-Reply-To: <20261022-b4-s4-vdec-upstream-v12-5-616240978792@amlogic.com>
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] media: amlogic: vdec: OOB read and canvas leaks in h264 reference list handling
- [Critical] media: amlogic: vdec: colocate buffer OOB DMA write in h264_config_decode_buf()
- [High] media: amlogic: vdec: aml_vdec_init_dec_inst() returns -1 on queue restart
- [High] media: amlogic: vdec: invalid IS_ERR() check on meson_sm_get() in probe
- [High] media: amlogic: vdec: poc_is_in_dpb() and get_dpb_by_poc() ignore active flag
- [High] media: amlogic: vdec: dos clock in vdec_clock_gate_init() is never enabled
- [High] media: amlogic: vdec: ignored power and clock errors in aml_vdec_hw.c
- [High] media: amlogic: vdec: aml_vdec_drv_remove() powers off hw before unregistering
- [High] media: amlogic: vdec: broken TEE deferral and error codes in aml_vdec_tee_fw.c
- [High] media: amlogic: vdec: infinite poll under ctx_mutex in h264_config_decode_buf()
- [Medium] media: amlogic: vdec: missing REGMAP_MMIO and MEDIA_CONTROLLER selects
- [Medium] media: amlogic: vdec: aml_vdec_drv_probe() registers video_device too early
- [Medium] media: amlogic: vdec: missing set_pic_info() in reset_output_fmts()
--
commit 7d77b0e5560dd6f7c9fdd465c00fffdff91c7e55
Author: Zhentao Guo <zhentao.guo@amlogic.com>
arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
This series adds the Amlogic S4 V4L2 stateless H.264 video decoder driver in
commit 18a11b2c70a7ef55c42ed897bc1624397ec3430c ("decoder: Add V4L2 stateless
H.264 decoder driver") and enables the corresponding vdec, canvas, and optee
device tree nodes in meson-s4.dtsi.
> diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig
> new file mode 100644
> index 0000000000000..2c5dd84529621
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +
> +config VIDEO_AMLOGIC_VDEC
> + tristate "Amlogic Video Decoder Driver"
> + depends on ARCH_MESON || COMPILE_TEST
> + depends on VIDEO_DEV
> + depends on V4L_MEM2MEM_DRIVERS
> + depends on TEE
> + depends on MESON_SM
> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_H264
> + select V4L2_MEM2MEM_DEV
> + select MESON_CANVAS
[Severity: Medium]
Should VIDEO_AMLOGIC_VDEC also select REGMAP_MMIO and MEDIA_CONTROLLER here?
In drivers/media/platform/amlogic/vdec/aml_vdec_hw.c,
dev_request_hw_resources() calls devm_regmap_init_mmio(), which fails to link
when CONFIG_REGMAP_MMIO is disabled.
In addition, aml_vdec_drv_probe() in
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c registers a media device
with aml_m2m_media_ops (vb2_request_validate and v4l2_m2m_request_queue) for
stateless H.264 decoding, which compiles to no-op stubs if
CONFIG_MEDIA_CONTROLLER is disabled. In
drivers/media/platform/amlogic/vdec/aml_vdec.c, should aml_vdec_queue_init()
also set src_vq->requires_requests = true alongside
src_vq->supports_requests = true so non-request buffers are rejected?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c
> new file mode 100644
> index 0000000000000..e65ebb2ce311f
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c
[ ... ]
> +static int aml_vdec_init_dec_inst(struct aml_vdec_ctx *ctx)
> +{
> + struct aml_vdec_dev *dev = ctx->dev;
> + struct aml_video_fmt *fmt_out = &ctx->dec_fmt[AML_FMT_SRC];
> + int ret = -1;
> +
> + ctx->codec_ops = &dev->pvdec_data->codec_ops[fmt_out->codec_type];
> + /*
> + * The codec instance is only released when both queues are
> + * streamed off: do not allocate a new one (leaking the previous
> + * instance) when the output queue is restarted while the
> + * capture queue is still streaming.
> + */
> + if (ctx->codec_ops->init && !ctx->codec_priv) {
> + ret = ctx->codec_ops->init(ctx);
> + if (ret < 0)
> + return ret;
> + }
> + ctx->curr_dec_type = fmt_out->codec_type;
> + dev_info(&dev->plat_dev->dev, "%s set curr_dec_type %s\n",
> + __func__, dec_type_to_name(ctx->curr_dec_type));
> +
> + return ret;
> +}
[Severity: High]
When the output queue is stopped and restarted while the capture queue remains
streaming, ctx->codec_priv is already non-NULL, so aml_vdec_init_dec_inst()
skips ctx->codec_ops->init(ctx) and returns the initial ret = -1.
In vb2ops_vdec_start_streaming():
if (aml_vdec_init_dec_inst(ctx) < 0) {
while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_QUEUED);
return -EINVAL;
}
Won't this cause vb2ops_vdec_start_streaming() to always fail with -EINVAL on
output queue restart unless ret is initialized to 0 in
aml_vdec_init_dec_inst()?
[ ... ]
> +static int vdec_try_fmt_mp(struct aml_vdec_ctx *ctx, enum v4l2_buf_type type,
> + struct v4l2_pix_format_mplane *pix_mp,
> + const struct aml_video_fmt *dec_fmt)
> +{
> + int i, align;
> +
> + pix_mp->field = V4L2_FIELD_NONE;
> + align = dec_fmt->align;
> +
> + if (V4L2_TYPE_IS_OUTPUT(type)) {
> + pix_mp->num_planes = dec_fmt->num_planes;
> + pix_mp->pixelformat = dec_fmt->fourcc;
> + if (!pix_mp->plane_fmt[0].sizeimage)
> + pix_mp->plane_fmt[0].sizeimage =
> + (pix_mp->height * pix_mp->width * 3) / 2;
> + pix_mp->height = ALIGN(pix_mp->height, align);
> + pix_mp->width = ALIGN(pix_mp->width, align);
> + } else {
> + pix_mp->height = ctx->pix_fmt[AML_FMT_SRC].height;
> + pix_mp->width = ctx->pix_fmt[AML_FMT_SRC].width;
> + }
> +
> + v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height,
> + &dec_fmt->stepwise);
[ ... ]
> +static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> + struct v4l2_pix_format_mplane *cap_fmt = &ctx->pix_fmt[AML_FMT_DST];
> + const struct aml_video_fmt *out_fmt;
> + struct vb2_queue *vq, *peer_vq;
> + int ret;
> +
> + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> + if (vb2_is_busy(vq) &&
> + pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat)
> + return -EBUSY;
> +
> + /*
> + * Since s_fmt output will reset the width and height in the capture queue,
> + * s_fmt_output with resolution change should not take effect when capture
> + * queue is busy.
> + */
> + peer_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> + if (vb2_is_busy(peer_vq) &&
> + (pix_mp->width != cap_fmt->width ||
> + pix_mp->height != cap_fmt->height))
> + return -EBUSY;
[ ... ]
> +static void reset_output_fmts(struct aml_vdec_ctx *ctx)
> +{
> + struct aml_vdec_dev *dev = ctx->dev;
> + const struct aml_video_fmt *out_fmt;
> + struct v4l2_pix_format_mplane fmt;
> +
> + /* reset default output fmt to V4L2_PIX_FMT_H264_SLICE */
> + out_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_H264_SLICE);
> + if (!out_fmt)
> + return;
> +
> + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
> +
> + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane));
> +
> + fmt.height = out_fmt->stepwise.min_height;
> + fmt.width = out_fmt->stepwise.min_width;
> + /* reset bytesperline to 0 for output fmts */
> + fmt.plane_fmt[0].bytesperline = 0;
> + fmt.colorspace = V4L2_COLORSPACE_DEFAULT;
> + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &fmt,
> + out_fmt);
> +
> + ctx->pix_fmt[AML_FMT_SRC] = fmt;
> +}
[Severity: Medium]
In fops_vcodec_open() -> aml_vdec_reset_fmts() -> reset_output_fmts(),
set_pic_info() is not called after vdec_try_fmt_mp(). If userspace starts
streaming without calling vdec_s_fmt_output(), won't ctx->pic_info.fb_size[0]
remain 0, causing h264_config_decode_spec() and h264_config_ref_spec() in
drivers/media/platform/amlogic/vdec/h264.c to set c_dma_addr equal to
y_dma_addr for single-planar NV12/NV21 capture buffers?
Also in vdec_try_fmt_mp(), pix_mp->plane_fmt[0].sizeimage is calculated from
pix_mp->height * pix_mp->width before ALIGN() and
v4l2_apply_frmsize_constraints() clamp the dimensions, and in
vdec_s_fmt_output(), pix_mp->width and pix_mp->height are compared against the
64-aligned cap_fmt dimensions before vdec_try_fmt_mp() aligns them. Should
sizeimage and the busy resolution check use the aligned and clamped dimensions
instead?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
> new file mode 100644
> index 0000000000000..1870d56659cf4
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
[ ... ]
> + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to register video device");
> + goto err_vid_dev_register;
> + }
> +
> + dev->mdev.dev = &pdev->dev;
> + strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model));
> + media_device_init(&dev->mdev);
> + dev->mdev.ops = &aml_m2m_media_ops;
> + dev->v4l2_dev.mdev = &dev->mdev;
> +
> + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec,
> + MEDIA_ENT_F_PROC_VIDEO_DECODER);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n");
> + goto error_m2m_mc_register;
> + }
> +
> + ret = media_device_register(&dev->mdev);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to register media device");
> + goto err_media_dev_register;
> + }
> + vdec_enable(dev->dec_hw);
> + return 0;
[Severity: Medium]
In aml_vdec_drv_probe(), video_register_device() exposes the video node to
userspace before dev->v4l2_dev.mdev is initialized, before the media
controller entities are registered, and before vdec_enable(dev->dec_hw) powers
on the VDEC hardware domain and clock.
Could a userspace process open the video device immediately after
video_register_device() and race with media controller setup or trigger
m2mops_vdec_device_run() before vdec_enable() has run?
[ ... ]
> +static void aml_vdec_drv_remove(struct platform_device *pdev)
> +{
> + struct aml_vdec_dev *dev = platform_get_drvdata(pdev);
> +
> + /*
> + * The IRQ is devm-managed and only released after remove()
> + * returns: mask it and wait for any in-flight threaded handler
> + * before powering off the VDEC domain, otherwise the handler
> + * could access powered-down registers.
> + */
> + disable_irq(dev->dec_hw->dec_irq);
> + vdec_disable(dev->dec_hw);
> + media_device_unregister(&dev->mdev);
> + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
> + video_unregister_device(dev->vfd);
> + media_device_cleanup(&dev->mdev);
> +
> + v4l2_m2m_release(dev->m2m_dev_dec);
> + dev->pvdec_data->destroy_hw_resource(dev);
> + v4l2_device_unregister(&dev->v4l2_dev);
> +}
[Severity: High]
In aml_vdec_drv_remove(), disable_irq() and vdec_disable(dev->dec_hw) power
off the VDEC domain and disable the VDEC clock before unregistering the media
and video devices or releasing M2M contexts.
If a decode job is in flight in aml_h264_dec_run() or an open file handle is
subsequently closed via fops_vcodec_release() -> vb2ops_vdec_stop_streaming()
-> aml_h264_exit(), won't read_dos_reg() and aml_stop_vdec_hw() perform MMIO
accesses to DOS_BUS registers that have already been powered down and
unclocked?
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
> new file mode 100644
> index 0000000000000..043f0bbf32516
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
[ ... ]
> +void dos_enable(struct aml_vdec_hw *hw)
> +{
> + dos_reg_write_bits(hw, DOS_GCLK_EN0, 0x3ff, 0, 10);
> +
> + regmap_write(hw->map[DOS_BUS], GCLK_EN, 0x3ff);
> +
> + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0);
> +}
[ ... ]
> +static int vdec_clock_gate_init(struct aml_vdec_hw *hw)
> +{
> + hw->gates[CLK_DOS].id = "dos";
> + hw->gates[CLK_VDEC].id = "vdec";
> + hw->gates[CLK_HVDEC].id = "hvdec";
> +
> + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates);
> +}
[Severity: High]
vdec_clock_gate_init() acquires hw->gates[CLK_DOS] ("dos"), but
pm_vdec_power_domain_power_on() and pm_vdec_power_domain_power_off() only
enable and disable the "vdec" and "hvdec" clocks, and dos_enable() only writes
internal DOS registers.
Where is hw->gates[CLK_DOS].clk prepared and enabled before dos_local_config()
and dos_enable() access DOS_BUS registers? If CLKID_DOS is gated by the
bootloader or clk_disable_unused, won't these MMIO accesses hang or abort?
[ ... ]
> +static int pm_vdec_power_domain_init(struct aml_vdec_hw *hw)
> +{
> + int i, err;
> + const struct power_manager_s *pm = hw->pm;
> + struct pm_pd_s *pd = pm->pd_data;
> +
> + mutex_init(&hw->pm_mutex);
> +
> + for (i = 0; i < VDEC_MAX; i++) {
> + pd[i].dev = dev_pm_domain_attach_by_name(hw->dev, pd[i].name);
> + if (IS_ERR_OR_NULL(pd[i].dev)) {
> + err = PTR_ERR(pd[i].dev);
> + dev_dbg(hw->dev, "Get %s failed, pm-domain: %d\n",
> + pd[i].name, err);
> + pd[i].dev = NULL;
> + continue;
> + }
[ ... ]
> +static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id)
> +{
> + const struct power_manager_s *pm = hw->pm;
> + struct device *dev = pm->pd_data[id].dev;
> + struct clk_bulk_data *gate_node = NULL;
> + int ret = 0;
> +
> + if (id == VDEC)
> + gate_node = vdec_get_clk_by_name(hw, "vdec");
> + else if (id == HVDEC)
> + gate_node = vdec_get_clk_by_name(hw, "hvdec");
> +
> + if (gate_node) {
> + ret = clk_prepare_enable(gate_node->clk);
> + if (ret) {
> + dev_err(hw->dev, "failed to enable %s clock: %d\n",
> + gate_node->id, ret);
> + } else {
> + if (id == VDEC) {
> + clk_set_rate(gate_node->clk, 499999992);
> + dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n",
> + clk_get_rate(gate_node->clk));
> + }
> + dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id);
> + }
> + } else {
> + dev_info(hw->dev, "clk %d, unreachable\n", id);
> + ret = -ENODEV;
> + }
> +
> + if (!ret && dev) {
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret < 0)
> + dev_err(hw->dev, "failed to power on %s domain: %d\n",
> + pm->pd_data[id].name, ret);
> + else
> + dev_dbg(dev, "dev: %p link %p the %-15s power on\n",
> + dev, pm->pd_data[id].link, pm->pd_data[id].name);
> + }
> +
> + /*
> + * Do not touch the DOS registers when the clock or the power
> + * domain could not be enabled, the MMIO access would abort.
> + */
> + if (!ret)
> + dos_local_config(hw, 1, id);
> +}
> +
> +static void pm_vdec_power_domain_power_off(struct aml_vdec_hw *hw, int id)
> +{
> + const struct power_manager_s *pm = hw->pm;
> + struct device *dev = pm->pd_data[id].dev;
> + struct clk_bulk_data *gate_node = NULL;
> +
> + dos_local_config(hw, 0, id);
> +
> + if (dev) {
> + pm_runtime_put_sync(dev);
> + dev_dbg(dev, "dev: %p link %p the %-15s power off\n",
> + dev, pm->pd_data[id].link, pm->pd_data[id].name);
> + }
> +
> + if (id == VDEC)
> + gate_node = vdec_get_clk_by_name(hw, "vdec");
> + else if (id == HVDEC)
> + gate_node = vdec_get_clk_by_name(hw, "hvdec");
> +
> + if (gate_node) {
> + clk_disable_unprepare(gate_node->clk);
> + dev_dbg(hw->dev, "the %-15s clock off\n", gate_node->id);
> + } else {
> + dev_info(hw->dev, "clk %d, unreachable\n", id);
> + }
> +}
> +
> +static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core)
> +{
> + if (core >= VDEC_MAX)
> + return;
> +
> + mutex_lock(&hw->pm_mutex);
> + if (!hw->pm->pd_data[core].dev)
> + goto out;
> +
> + hw->pm->pd_data[core].ref_count++;
> + if (hw->pm->pd_data[core].ref_count > 1)
> + goto out;
> +
> + hw->pm->power_on(hw, core);
> +
> +out:
> + mutex_unlock(&hw->pm_mutex);
> +}
[Severity: High]
There appear to be two error-handling issues in the power domain and clock
helpers here:
1. In pm_vdec_power_domain_init(), if dev_pm_domain_attach_by_name() returns
an error pointer (such as -EPROBE_DEFER) or NULL, pd[i].dev is set to NULL and
the loop continues while returning 0. Then vdec_poweron() sees
!hw->pm->pd_data[core].dev and exits without enabling the clock or power
domain, while vdec_enable() still returns 0. Won't subsequent MMIO writes in
m2mops_vdec_device_run() -> dos_enable() access unpowered and unclocked DOS
registers?
2. In pm_vdec_power_domain_power_on(), if clk_prepare_enable(gate_node->clk)
succeeds and pm_runtime_resume_and_get(dev) fails, gate_node->clk is left
enabled and the failure is not propagated to vdec_poweron() or vdec_enable(),
leaving ref_count at 1. When vdec_disable() -> vdec_poweroff() ->
pm_vdec_power_domain_power_off() later runs, won't it call
dos_local_config(hw, 0, id) on the unpowered domain and call
pm_runtime_put_sync(dev) despite pm_runtime_resume_and_get(dev) having failed?
[ ... ]
> + hw->canvas = meson_canvas_get(&pdev->dev);
> + if (IS_ERR_OR_NULL(hw->canvas))
> + return PTR_ERR(hw->canvas);
> +
> + sm_np = of_parse_phandle(pdev->dev.of_node, "amlogic,secure-monitor", 0);
> + if (!sm_np)
> + return -ENODEV;
> +
> + hw->sec_fw = meson_sm_get(sm_np);
> + of_node_put(sm_np);
> + if (IS_ERR(hw->sec_fw))
> + return PTR_ERR(hw->sec_fw);
[Severity: High]
In drivers/firmware/meson/meson_sm.c, meson_sm_get() returns NULL (not an
ERR_PTR) when the secure-monitor platform device has not probed yet.
Because IS_ERR(NULL) is false, dev_request_hw_resources() succeeds with
hw->sec_fw == NULL instead of returning -EPROBE_DEFER. When decoding starts in
m2mops_vdec_device_run() -> load_firmware() -> meson_sm_call(hw->sec_fw, ...),
won't meson_sm_call() dereference a NULL hw->sec_fw pointer on fw->chip?
Similarly, above that check, if meson_canvas_get() were to return NULL,
if (IS_ERR_OR_NULL(hw->canvas)) return PTR_ERR(hw->canvas); would return 0.
Should hw->canvas use IS_ERR(hw->canvas) and hw->sec_fw check !hw->sec_fw and
return -EPROBE_DEFER?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
> new file mode 100644
> index 0000000000000..9af9eae88fb89
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
[ ... ]
> + /* Open session */
> + memcpy(sess_arg.uuid, uuid.b, TEE_IOCTL_UUID_LEN);
> + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
> + sess_arg.num_params = TEE_PARAM_NUM;
> + ret = tee_client_open_session(ctx, &sess_arg, param_init);
> + if (ret < 0 || sess_arg.ret != TEEC_SUCCESS) {
> + dev_info(hw->dev,
> + "%s open session failed, cmd = %u, ret = %d, res = 0x%x, origin = 0x%x\n",
> + __func__, cmd, ret, sess_arg.ret, sess_arg.ret_origin);
> + if (!ret)
> + ret = sess_arg.ret;
> + goto free_shm;
> + }
> +
> + inv_arg.func = cmd;
> + inv_arg.session = sess_arg.session;
> + inv_arg.num_params = TEE_PARAM_NUM;
> +
> + while (sent_size < fw_size) {
> + memset(shm_va, 0, ONCE_SENT_SIZE);
> + if (fw_size - sent_size > ONCE_SENT_SIZE) {
> + memcpy(shm_va, (firmware_data + sent_size),
> + ONCE_SENT_SIZE);
> + param_invoke[0].u.memref.size = ONCE_SENT_SIZE;
> + } else {
> + memcpy(shm_va, (firmware_data + sent_size),
> + fw_size - sent_size);
> + param_invoke[0].u.memref.size = (fw_size - sent_size);
> + }
> + param_invoke[0].u.memref.shm = shm;
> + ret = tee_client_invoke_func(ctx, &inv_arg, param_invoke);
> + if (ret < 0 || (inv_arg.ret != TEEC_SUCCESS && inv_arg.ret != TEEC_ERROR_BUSY)) {
> + dev_info(hw->dev,
> + "%s invoke func failed, cmd = %u, ret= %d, res = 0x%x, origin = 0x%x\n",
> + __func__, cmd, ret, inv_arg.ret,
> + inv_arg.ret_origin);
> + if (!ret)
> + ret = inv_arg.ret;
> + goto close_session;
> + }
> + sent_size += param_invoke[0].u.memref.size;
> + }
[ ... ]
> + ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
> + if (IS_ERR(ctx)) {
> + dev_info(hw->dev, "Failed to open TEE context\n");
> + ret = PTR_ERR(ctx);
> + goto free_firmware;
> + }
[Severity: High]
In aml_vdec_drv_probe() -> dev_request_hw_resources() -> aml_tee_fw_preload()
-> pass_firmware_to_tee():
1. When the OP-TEE driver has not registered its TEE device yet,
tee_client_open_context() returns ERR_PTR(-ENOENT). Propagating -ENOENT causes
aml_vdec_drv_probe() to fail permanently instead of deferring probe with
-EPROBE_DEFER.
2. In tee_pta_invoke_cmd(), when tee_client_open_session() or
tee_client_invoke_func() returns 0 with sess_arg.ret != TEEC_SUCCESS or
inv_arg.ret != TEEC_SUCCESS, ret is assigned the raw u32 TEEC status code
rather than a negative errno (which aml_vdec_drv_probe() only checks for
ret < 0).
3. In tee_pta_invoke_cmd(), when inv_arg.ret == TEEC_ERROR_BUSY, the error
branch is skipped and sent_size += param_invoke[0].u.memref.size advances past
the current 128 KB chunk without retrying it. Won't this silently drop a
chunk of firmware data?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c
> new file mode 100644
> index 0000000000000..7ec7af73d44c7
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/h264.c
[ ... ]
> +static int poc_is_in_dpb(int poc, const struct v4l2_h264_dpb_entry *dpb)
> +{
> + int i;
> + int ret = 0;
> +
> + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
> + if (poc == dpb[i].top_field_order_cnt) {
> + ret = 1;
> + break;
> + }
> + }
> +
> + return ret;
> +}
[Severity: High]
In poc_is_in_dpb(), dpb[i].top_field_order_cnt is matched across all 16
entries of dpb[] without checking dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE.
The same issue also occurs in get_dpb_by_poc() further below:
static struct v4l2_h264_dpb_entry *
get_dpb_by_poc(struct v4l2_ctrl_h264_decode_params *decode, int poc)
{
int i;
for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
if (decode->dpb[i].top_field_order_cnt == poc)
return &decode->dpb[i];
}
return NULL;
}
Because unused DPB entries have flags == 0 and top_field_order_cnt == 0 (and
evicted entries may retain their stale top_field_order_cnt with the active
flag cleared):
1. In h264_config_decode_spec() -> clear_unused_col_buf(), poc_is_in_dpb()
matches inactive DPB slots, preventing canvas_pos and colocated_buf slots from
being released on non-IDR frames and exhausting the 17 canvas_pos slots
(especially on multi-slice frames where h264_config_decode_spec() is called on
every slice).
2. In h264_config_decode_buf(), if an inactive DPB slot with
top_field_order_cnt == 0 precedes an active reference picture with POC 0,
get_dpb_by_poc(decode, 0) returns the inactive slot, so
if (dpb && (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) evaluates to false
and writes 0 into H264_BUFFER_INFO_DATA for the active reference frame.
Should both poc_is_in_dpb() and get_dpb_by_poc() require
(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)?
[ ... ]
> +static int get_refidx_by_picnum(struct aml_h264_ctx *h264_ctx, int pic_num,
> + int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0[0];
> + else
> + ref_list = &h264_ctx->ref_list1[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num)
> + return i;
> + }
> +
> + return -1;
> +}
> +
> +static struct h264_decode_buf_spec *get_st_refpic_by_num(struct aml_h264_ctx *h264_ctx,
> + int pic_num, int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0_unreordered[0];
> + else
> + ref_list = &h264_ctx->ref_list1_unreordered[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 0)
> + return &ref_list[i];
> + }
> +
> + return NULL;
> +}
> +
> +static struct h264_decode_buf_spec *get_lt_refpic_by_num(struct aml_h264_ctx *h264_ctx,
> + int pic_num, int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0_unreordered[0];
> + else
> + ref_list = &h264_ctx->ref_list1_unreordered[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 1)
> + return &ref_list[i];
> + }
> +
> + return NULL;
> +}
> +
> +static void reorder_short_term(struct slice *curr_slice, int cur_list,
> + int pic_num_lx, int *ref_idx_lx)
> +{
[ ... ]
> + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--)
> + memcpy(&ref_list_reordered[c_idx], &ref_list_reordered[c_idx - 1],
> + sizeof(struct h264_decode_buf_spec));
> +
> + memcpy(&ref_list_reordered[*ref_idx_lx], pic_lx, sizeof(struct h264_decode_buf_spec));
> + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__,
> + *ref_idx_lx, pic_lx, ref_list_reordered[*ref_idx_lx].dpb->pic_num);
> + *ref_idx_lx = *ref_idx_lx + 1;
> +
> + n_idx = *ref_idx_lx;
> + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) {
> + if (ref_list_reordered[c_idx].long_term_flag || !ref_list_reordered[c_idx].dpb ||
> + ref_list_reordered[c_idx].dpb->pic_num != pic_num_lx)
> + memcpy(&ref_list_reordered[n_idx++], &ref_list_reordered[c_idx],
> + sizeof(struct h264_decode_buf_spec));
> + }
> +
> + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active;
> +}
[Severity: Critical]
Can reference list reordering and canvas allocation read out of bounds,
dereference a NULL dpb pointer, and leak hardware canvases?
1. In reorder_short_term() and reorder_long_term(), when
num_ref_idx_lx_active == 16 (V4L2_H264_NUM_DPB_ENTRIES), the shift loop copies
ref_list[15] into ref_list[16] and sets list_size[cur_list] = 16. Because
h264_release_decode_spec() only clears indices 0 .. list_size[cur_list] - 1,
ref_list[16].dpb remains non-NULL. On a subsequent call where
ref_list[0..16].dpb are all non-NULL, the loops in get_refidx_by_picnum(),
get_st_refpic_by_num(), and get_lt_refpic_by_num() have no upper bound on i
and read past the end of ref_list0[] / ref_list1[].
2. When num_ref_idx_lx_active exceeds the number of valid entries built by
h264_config_ref_lists() (or when h264_config_ref_spec() fails on an entry),
reorder_short_term() and reorder_long_term() set
h264_ctx->list_size[cur_list] = num_ref_idx_lx_active. Then get_canvas_index()
allocates hardware canvases via config_decode_canvas() for every entry up to
list_size[cur_list] without checking buf->used or buf->valid (also writing DMA
address 0 to ANC0_CANVAS_ADDR for canvas_pos 0), whereas
h264_release_decode_spec() only frees canvases when if (buf->used) is true,
permanently leaking canvases from the meson_canvas pool. In addition, in
h264_threaded_isr_func(), h264_release_decode_spec() is only called before
get_canvas_index() when first_mb_in_slice != 0, so a second
H264_SLICE_HEADER_DONE interrupt with first_mb_in_slice == 0 leaks the
canvases allocated for the first slice.
3. In h264_config_decode_buf(), when h264_ctx->list_size[1] > 0,
colocate_pic = &h264_ctx->ref_list1[0] dereferences
colocate_pic->dpb->top_field_order_cnt without checking colocate_pic->valid or
colocate_pic->dpb != NULL.
[ ... ]
> + read_poll_timeout(read_dos_reg, reg_val,
> + !(reg_val & 0x800),
> + 10, 0, true, hw, H264_CO_MB_RW_CTL);
[Severity: High]
In h264_config_decode_buf(), read_poll_timeout() is called with timeout_us = 0
while vdec_threaded_isr_handler() holds hw->ctx_mutex.
Since timeout_us = 0 polls indefinitely in read_poll_timeout(), if bit 11
(0x800) of H264_CO_MB_RW_CTL does not clear, won't vdec_threaded_isr_handler()
spin forever under hw->ctx_mutex and cause aml_h264_dec_run() to deadlock on
mutex_lock(&dec_hw->ctx_mutex) when wait_event_interruptible_timeout()
expires?
> +
> + /* col buf for curr frame */
> + colocate_adr_offset = COL_SIZE_FOR_ONE_MB;
> + if (use_mode_8x8_flag)
> + colocate_adr_offset >>= 2;
> + colocate_adr_offset *= curr_slice->first_mb_in_slice;
> +
> + if (h264_ctx->curr_spec.col_buf_index >= 0 &&
> + h264_ctx->curr_spec.col_buf_index < h264_ctx->colocated_buf_num) {
> + /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
> + colocate_wr_adr = (unsigned int)h264_ctx->collated_cma_addr +
> + ((h264_ctx->one_col_buf_size *
> + h264_ctx->curr_spec.col_buf_index) >> (use_mode_8x8_flag ? 2 : 0));
> + if (colocate_adr_offset > h264_ctx->one_col_buf_size ||
> + colocate_wr_adr + h264_ctx->one_col_buf_size >
> + (unsigned int)h264_ctx->collated_cma_addr_end) {
> + dev_err(hw->dev,
> + "Error, colocate buf is not enough, index is %d\n",
> + h264_ctx->curr_spec.col_buf_index);
> + return -1;
> + }
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR,
> + (colocate_wr_adr + colocate_adr_offset));
> + dev_dbg(hw->dev, "col buffer addr = 0x%x col_buf_index %d\n",
> + (colocate_wr_adr + colocate_adr_offset),
> + h264_ctx->curr_spec.col_buf_index);
> + } else {
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, 0xffffffff);
> + dev_dbg(hw->dev, "col buffer addr = 0xffffffff\n");
> + }
> +
> + if (h264_ctx->list_size[1] > 0) {
> + struct h264_decode_buf_spec *colocate_pic =
> + &h264_ctx->ref_list1[0];
> + struct h264_decode_buf_spec *curr_pic = &h264_ctx->curr_spec;
> + int l10_structure = 2; /* for pic struct == FRAME, default to 2 */
> + int cur_colocate_ref_type;
> + unsigned int colocate_rd_adr;
> + unsigned int colocate_rd_adr_offset = 0;
> + unsigned int val;
> +
> + cur_colocate_ref_type =
> + (abs(curr_pic->poc - colocate_pic->dpb->top_field_order_cnt) <
> + abs(curr_pic->poc - colocate_pic->dpb->bottom_field_order_cnt)) ? 0 : 1;
> + colocate_rd_adr_offset = COL_SIZE_FOR_ONE_MB;
> + if (use_mode_8x8_flag)
> + colocate_rd_adr_offset >>= 2;
> +
> + colocate_rd_adr_offset *= curr_slice->first_mb_in_slice;
> + if (colocate_pic->col_buf_index >= 0 &&
> + colocate_pic->col_buf_index < h264_ctx->colocated_buf_num) {
> + /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
> + colocate_rd_adr = (unsigned int)h264_ctx->collated_cma_addr +
> + ((h264_ctx->one_col_buf_size *
> + colocate_pic->col_buf_index) >> (use_mode_8x8_flag
> + ? 2 : 0));
> + if (colocate_rd_adr_offset > h264_ctx->one_col_buf_size ||
> + colocate_rd_adr + h264_ctx->one_col_buf_size >
> + (unsigned int)h264_ctx->collated_cma_addr_end) {
> + dev_err(hw->dev,
> + "Error, colocate rd buf is not enough, index is %d\n",
> + colocate_pic->col_buf_index);
> + return -1;
> + }
> + val = ((colocate_rd_adr_offset + colocate_rd_adr) >> 3) |
> + (cur_colocate_ref_type << 29) |
> + (l10_structure << 30);
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_RD_ADDR, val);
[Severity: Critical]
In h264_config_decode_buf(), can a slice with a non-zero first_mb_in_slice
program H264_CO_MB_WR_ADDR and H264_CO_MB_RD_ADDR to DMA past the end of the
colocated buffer slot or past h264_ctx->collated_cma_addr_end?
When use_mode_8x8_flag is set, each slot's stride is
h264_ctx->one_col_buf_size >> 2, but colocate_adr_offset and
colocate_rd_adr_offset are compared against the unshifted
h264_ctx->one_col_buf_size, allowing the offset to cross into the next 3
colocated buffer slots.
Furthermore, the end-address check tests
colocate_wr_adr + h264_ctx->one_col_buf_size > h264_ctx->collated_cma_addr_end
without adding colocate_adr_offset (and likewise for colocate_rd_adr without
colocate_rd_adr_offset), so when col_buf_index is at the end of the buffer and
colocate_adr_offset > 0, colocate_wr_adr + colocate_adr_offset can point up to
one_col_buf_size bytes past collated_cma_addr_end.
[ ... ]
> +}
[ ... ]
> --
> 2.43.0
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-b4-s4-vdec-upstream-v12-0-616240978792@amlogic.com?part=5
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
WARNING: multiple messages have this Message-ID (diff)
From: sashiko-bot@kernel.org
To: "Zhentao Guo" <zhentao.guo@amlogic.com>
Cc: linux-amlogic@lists.infradead.org, devicetree@vger.kernel.org,
robh@kernel.org, conor+dt@kernel.org, neil.armstrong@linaro.org,
media-ci@linuxtv.org
Subject: Re: [PATCH v12 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
Date: Thu, 22 Oct 2026 06:01:18 +0000 [thread overview]
Message-ID: <sashiko-outbox-181046@kernel.org> (raw)
In-Reply-To: <20261022-b4-s4-vdec-upstream-v12-5-616240978792@amlogic.com>
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] media: amlogic: vdec: OOB read and canvas leaks in h264 reference list handling
- [Critical] media: amlogic: vdec: colocate buffer OOB DMA write in h264_config_decode_buf()
- [High] media: amlogic: vdec: aml_vdec_init_dec_inst() returns -1 on queue restart
- [High] media: amlogic: vdec: invalid IS_ERR() check on meson_sm_get() in probe
- [High] media: amlogic: vdec: poc_is_in_dpb() and get_dpb_by_poc() ignore active flag
- [High] media: amlogic: vdec: dos clock in vdec_clock_gate_init() is never enabled
- [High] media: amlogic: vdec: ignored power and clock errors in aml_vdec_hw.c
- [High] media: amlogic: vdec: aml_vdec_drv_remove() powers off hw before unregistering
- [High] media: amlogic: vdec: broken TEE deferral and error codes in aml_vdec_tee_fw.c
- [High] media: amlogic: vdec: infinite poll under ctx_mutex in h264_config_decode_buf()
- [Medium] media: amlogic: vdec: missing REGMAP_MMIO and MEDIA_CONTROLLER selects
- [Medium] media: amlogic: vdec: aml_vdec_drv_probe() registers video_device too early
- [Medium] media: amlogic: vdec: missing set_pic_info() in reset_output_fmts()
--
commit 7d77b0e5560dd6f7c9fdd465c00fffdff91c7e55
Author: Zhentao Guo <zhentao.guo@amlogic.com>
arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
This series adds the Amlogic S4 V4L2 stateless H.264 video decoder driver in
commit 18a11b2c70a7ef55c42ed897bc1624397ec3430c ("decoder: Add V4L2 stateless
H.264 decoder driver") and enables the corresponding vdec, canvas, and optee
device tree nodes in meson-s4.dtsi.
> diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig
> new file mode 100644
> index 0000000000000..2c5dd84529621
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +
> +config VIDEO_AMLOGIC_VDEC
> + tristate "Amlogic Video Decoder Driver"
> + depends on ARCH_MESON || COMPILE_TEST
> + depends on VIDEO_DEV
> + depends on V4L_MEM2MEM_DRIVERS
> + depends on TEE
> + depends on MESON_SM
> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_H264
> + select V4L2_MEM2MEM_DEV
> + select MESON_CANVAS
[Severity: Medium]
Should VIDEO_AMLOGIC_VDEC also select REGMAP_MMIO and MEDIA_CONTROLLER here?
In drivers/media/platform/amlogic/vdec/aml_vdec_hw.c,
dev_request_hw_resources() calls devm_regmap_init_mmio(), which fails to link
when CONFIG_REGMAP_MMIO is disabled.
In addition, aml_vdec_drv_probe() in
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c registers a media device
with aml_m2m_media_ops (vb2_request_validate and v4l2_m2m_request_queue) for
stateless H.264 decoding, which compiles to no-op stubs if
CONFIG_MEDIA_CONTROLLER is disabled. In
drivers/media/platform/amlogic/vdec/aml_vdec.c, should aml_vdec_queue_init()
also set src_vq->requires_requests = true alongside
src_vq->supports_requests = true so non-request buffers are rejected?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c
> new file mode 100644
> index 0000000000000..e65ebb2ce311f
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c
[ ... ]
> +static int aml_vdec_init_dec_inst(struct aml_vdec_ctx *ctx)
> +{
> + struct aml_vdec_dev *dev = ctx->dev;
> + struct aml_video_fmt *fmt_out = &ctx->dec_fmt[AML_FMT_SRC];
> + int ret = -1;
> +
> + ctx->codec_ops = &dev->pvdec_data->codec_ops[fmt_out->codec_type];
> + /*
> + * The codec instance is only released when both queues are
> + * streamed off: do not allocate a new one (leaking the previous
> + * instance) when the output queue is restarted while the
> + * capture queue is still streaming.
> + */
> + if (ctx->codec_ops->init && !ctx->codec_priv) {
> + ret = ctx->codec_ops->init(ctx);
> + if (ret < 0)
> + return ret;
> + }
> + ctx->curr_dec_type = fmt_out->codec_type;
> + dev_info(&dev->plat_dev->dev, "%s set curr_dec_type %s\n",
> + __func__, dec_type_to_name(ctx->curr_dec_type));
> +
> + return ret;
> +}
[Severity: High]
When the output queue is stopped and restarted while the capture queue remains
streaming, ctx->codec_priv is already non-NULL, so aml_vdec_init_dec_inst()
skips ctx->codec_ops->init(ctx) and returns the initial ret = -1.
In vb2ops_vdec_start_streaming():
if (aml_vdec_init_dec_inst(ctx) < 0) {
while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_QUEUED);
return -EINVAL;
}
Won't this cause vb2ops_vdec_start_streaming() to always fail with -EINVAL on
output queue restart unless ret is initialized to 0 in
aml_vdec_init_dec_inst()?
[ ... ]
> +static int vdec_try_fmt_mp(struct aml_vdec_ctx *ctx, enum v4l2_buf_type type,
> + struct v4l2_pix_format_mplane *pix_mp,
> + const struct aml_video_fmt *dec_fmt)
> +{
> + int i, align;
> +
> + pix_mp->field = V4L2_FIELD_NONE;
> + align = dec_fmt->align;
> +
> + if (V4L2_TYPE_IS_OUTPUT(type)) {
> + pix_mp->num_planes = dec_fmt->num_planes;
> + pix_mp->pixelformat = dec_fmt->fourcc;
> + if (!pix_mp->plane_fmt[0].sizeimage)
> + pix_mp->plane_fmt[0].sizeimage =
> + (pix_mp->height * pix_mp->width * 3) / 2;
> + pix_mp->height = ALIGN(pix_mp->height, align);
> + pix_mp->width = ALIGN(pix_mp->width, align);
> + } else {
> + pix_mp->height = ctx->pix_fmt[AML_FMT_SRC].height;
> + pix_mp->width = ctx->pix_fmt[AML_FMT_SRC].width;
> + }
> +
> + v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height,
> + &dec_fmt->stepwise);
[ ... ]
> +static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> + struct v4l2_pix_format_mplane *cap_fmt = &ctx->pix_fmt[AML_FMT_DST];
> + const struct aml_video_fmt *out_fmt;
> + struct vb2_queue *vq, *peer_vq;
> + int ret;
> +
> + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> + if (vb2_is_busy(vq) &&
> + pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat)
> + return -EBUSY;
> +
> + /*
> + * Since s_fmt output will reset the width and height in the capture queue,
> + * s_fmt_output with resolution change should not take effect when capture
> + * queue is busy.
> + */
> + peer_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> + if (vb2_is_busy(peer_vq) &&
> + (pix_mp->width != cap_fmt->width ||
> + pix_mp->height != cap_fmt->height))
> + return -EBUSY;
[ ... ]
> +static void reset_output_fmts(struct aml_vdec_ctx *ctx)
> +{
> + struct aml_vdec_dev *dev = ctx->dev;
> + const struct aml_video_fmt *out_fmt;
> + struct v4l2_pix_format_mplane fmt;
> +
> + /* reset default output fmt to V4L2_PIX_FMT_H264_SLICE */
> + out_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_H264_SLICE);
> + if (!out_fmt)
> + return;
> +
> + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
> +
> + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane));
> +
> + fmt.height = out_fmt->stepwise.min_height;
> + fmt.width = out_fmt->stepwise.min_width;
> + /* reset bytesperline to 0 for output fmts */
> + fmt.plane_fmt[0].bytesperline = 0;
> + fmt.colorspace = V4L2_COLORSPACE_DEFAULT;
> + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &fmt,
> + out_fmt);
> +
> + ctx->pix_fmt[AML_FMT_SRC] = fmt;
> +}
[Severity: Medium]
In fops_vcodec_open() -> aml_vdec_reset_fmts() -> reset_output_fmts(),
set_pic_info() is not called after vdec_try_fmt_mp(). If userspace starts
streaming without calling vdec_s_fmt_output(), won't ctx->pic_info.fb_size[0]
remain 0, causing h264_config_decode_spec() and h264_config_ref_spec() in
drivers/media/platform/amlogic/vdec/h264.c to set c_dma_addr equal to
y_dma_addr for single-planar NV12/NV21 capture buffers?
Also in vdec_try_fmt_mp(), pix_mp->plane_fmt[0].sizeimage is calculated from
pix_mp->height * pix_mp->width before ALIGN() and
v4l2_apply_frmsize_constraints() clamp the dimensions, and in
vdec_s_fmt_output(), pix_mp->width and pix_mp->height are compared against the
64-aligned cap_fmt dimensions before vdec_try_fmt_mp() aligns them. Should
sizeimage and the busy resolution check use the aligned and clamped dimensions
instead?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
> new file mode 100644
> index 0000000000000..1870d56659cf4
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
[ ... ]
> + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to register video device");
> + goto err_vid_dev_register;
> + }
> +
> + dev->mdev.dev = &pdev->dev;
> + strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model));
> + media_device_init(&dev->mdev);
> + dev->mdev.ops = &aml_m2m_media_ops;
> + dev->v4l2_dev.mdev = &dev->mdev;
> +
> + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec,
> + MEDIA_ENT_F_PROC_VIDEO_DECODER);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n");
> + goto error_m2m_mc_register;
> + }
> +
> + ret = media_device_register(&dev->mdev);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to register media device");
> + goto err_media_dev_register;
> + }
> + vdec_enable(dev->dec_hw);
> + return 0;
[Severity: Medium]
In aml_vdec_drv_probe(), video_register_device() exposes the video node to
userspace before dev->v4l2_dev.mdev is initialized, before the media
controller entities are registered, and before vdec_enable(dev->dec_hw) powers
on the VDEC hardware domain and clock.
Could a userspace process open the video device immediately after
video_register_device() and race with media controller setup or trigger
m2mops_vdec_device_run() before vdec_enable() has run?
[ ... ]
> +static void aml_vdec_drv_remove(struct platform_device *pdev)
> +{
> + struct aml_vdec_dev *dev = platform_get_drvdata(pdev);
> +
> + /*
> + * The IRQ is devm-managed and only released after remove()
> + * returns: mask it and wait for any in-flight threaded handler
> + * before powering off the VDEC domain, otherwise the handler
> + * could access powered-down registers.
> + */
> + disable_irq(dev->dec_hw->dec_irq);
> + vdec_disable(dev->dec_hw);
> + media_device_unregister(&dev->mdev);
> + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
> + video_unregister_device(dev->vfd);
> + media_device_cleanup(&dev->mdev);
> +
> + v4l2_m2m_release(dev->m2m_dev_dec);
> + dev->pvdec_data->destroy_hw_resource(dev);
> + v4l2_device_unregister(&dev->v4l2_dev);
> +}
[Severity: High]
In aml_vdec_drv_remove(), disable_irq() and vdec_disable(dev->dec_hw) power
off the VDEC domain and disable the VDEC clock before unregistering the media
and video devices or releasing M2M contexts.
If a decode job is in flight in aml_h264_dec_run() or an open file handle is
subsequently closed via fops_vcodec_release() -> vb2ops_vdec_stop_streaming()
-> aml_h264_exit(), won't read_dos_reg() and aml_stop_vdec_hw() perform MMIO
accesses to DOS_BUS registers that have already been powered down and
unclocked?
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
> new file mode 100644
> index 0000000000000..043f0bbf32516
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
[ ... ]
> +void dos_enable(struct aml_vdec_hw *hw)
> +{
> + dos_reg_write_bits(hw, DOS_GCLK_EN0, 0x3ff, 0, 10);
> +
> + regmap_write(hw->map[DOS_BUS], GCLK_EN, 0x3ff);
> +
> + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0);
> +}
[ ... ]
> +static int vdec_clock_gate_init(struct aml_vdec_hw *hw)
> +{
> + hw->gates[CLK_DOS].id = "dos";
> + hw->gates[CLK_VDEC].id = "vdec";
> + hw->gates[CLK_HVDEC].id = "hvdec";
> +
> + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates);
> +}
[Severity: High]
vdec_clock_gate_init() acquires hw->gates[CLK_DOS] ("dos"), but
pm_vdec_power_domain_power_on() and pm_vdec_power_domain_power_off() only
enable and disable the "vdec" and "hvdec" clocks, and dos_enable() only writes
internal DOS registers.
Where is hw->gates[CLK_DOS].clk prepared and enabled before dos_local_config()
and dos_enable() access DOS_BUS registers? If CLKID_DOS is gated by the
bootloader or clk_disable_unused, won't these MMIO accesses hang or abort?
[ ... ]
> +static int pm_vdec_power_domain_init(struct aml_vdec_hw *hw)
> +{
> + int i, err;
> + const struct power_manager_s *pm = hw->pm;
> + struct pm_pd_s *pd = pm->pd_data;
> +
> + mutex_init(&hw->pm_mutex);
> +
> + for (i = 0; i < VDEC_MAX; i++) {
> + pd[i].dev = dev_pm_domain_attach_by_name(hw->dev, pd[i].name);
> + if (IS_ERR_OR_NULL(pd[i].dev)) {
> + err = PTR_ERR(pd[i].dev);
> + dev_dbg(hw->dev, "Get %s failed, pm-domain: %d\n",
> + pd[i].name, err);
> + pd[i].dev = NULL;
> + continue;
> + }
[ ... ]
> +static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id)
> +{
> + const struct power_manager_s *pm = hw->pm;
> + struct device *dev = pm->pd_data[id].dev;
> + struct clk_bulk_data *gate_node = NULL;
> + int ret = 0;
> +
> + if (id == VDEC)
> + gate_node = vdec_get_clk_by_name(hw, "vdec");
> + else if (id == HVDEC)
> + gate_node = vdec_get_clk_by_name(hw, "hvdec");
> +
> + if (gate_node) {
> + ret = clk_prepare_enable(gate_node->clk);
> + if (ret) {
> + dev_err(hw->dev, "failed to enable %s clock: %d\n",
> + gate_node->id, ret);
> + } else {
> + if (id == VDEC) {
> + clk_set_rate(gate_node->clk, 499999992);
> + dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n",
> + clk_get_rate(gate_node->clk));
> + }
> + dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id);
> + }
> + } else {
> + dev_info(hw->dev, "clk %d, unreachable\n", id);
> + ret = -ENODEV;
> + }
> +
> + if (!ret && dev) {
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret < 0)
> + dev_err(hw->dev, "failed to power on %s domain: %d\n",
> + pm->pd_data[id].name, ret);
> + else
> + dev_dbg(dev, "dev: %p link %p the %-15s power on\n",
> + dev, pm->pd_data[id].link, pm->pd_data[id].name);
> + }
> +
> + /*
> + * Do not touch the DOS registers when the clock or the power
> + * domain could not be enabled, the MMIO access would abort.
> + */
> + if (!ret)
> + dos_local_config(hw, 1, id);
> +}
> +
> +static void pm_vdec_power_domain_power_off(struct aml_vdec_hw *hw, int id)
> +{
> + const struct power_manager_s *pm = hw->pm;
> + struct device *dev = pm->pd_data[id].dev;
> + struct clk_bulk_data *gate_node = NULL;
> +
> + dos_local_config(hw, 0, id);
> +
> + if (dev) {
> + pm_runtime_put_sync(dev);
> + dev_dbg(dev, "dev: %p link %p the %-15s power off\n",
> + dev, pm->pd_data[id].link, pm->pd_data[id].name);
> + }
> +
> + if (id == VDEC)
> + gate_node = vdec_get_clk_by_name(hw, "vdec");
> + else if (id == HVDEC)
> + gate_node = vdec_get_clk_by_name(hw, "hvdec");
> +
> + if (gate_node) {
> + clk_disable_unprepare(gate_node->clk);
> + dev_dbg(hw->dev, "the %-15s clock off\n", gate_node->id);
> + } else {
> + dev_info(hw->dev, "clk %d, unreachable\n", id);
> + }
> +}
> +
> +static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core)
> +{
> + if (core >= VDEC_MAX)
> + return;
> +
> + mutex_lock(&hw->pm_mutex);
> + if (!hw->pm->pd_data[core].dev)
> + goto out;
> +
> + hw->pm->pd_data[core].ref_count++;
> + if (hw->pm->pd_data[core].ref_count > 1)
> + goto out;
> +
> + hw->pm->power_on(hw, core);
> +
> +out:
> + mutex_unlock(&hw->pm_mutex);
> +}
[Severity: High]
There appear to be two error-handling issues in the power domain and clock
helpers here:
1. In pm_vdec_power_domain_init(), if dev_pm_domain_attach_by_name() returns
an error pointer (such as -EPROBE_DEFER) or NULL, pd[i].dev is set to NULL and
the loop continues while returning 0. Then vdec_poweron() sees
!hw->pm->pd_data[core].dev and exits without enabling the clock or power
domain, while vdec_enable() still returns 0. Won't subsequent MMIO writes in
m2mops_vdec_device_run() -> dos_enable() access unpowered and unclocked DOS
registers?
2. In pm_vdec_power_domain_power_on(), if clk_prepare_enable(gate_node->clk)
succeeds and pm_runtime_resume_and_get(dev) fails, gate_node->clk is left
enabled and the failure is not propagated to vdec_poweron() or vdec_enable(),
leaving ref_count at 1. When vdec_disable() -> vdec_poweroff() ->
pm_vdec_power_domain_power_off() later runs, won't it call
dos_local_config(hw, 0, id) on the unpowered domain and call
pm_runtime_put_sync(dev) despite pm_runtime_resume_and_get(dev) having failed?
[ ... ]
> + hw->canvas = meson_canvas_get(&pdev->dev);
> + if (IS_ERR_OR_NULL(hw->canvas))
> + return PTR_ERR(hw->canvas);
> +
> + sm_np = of_parse_phandle(pdev->dev.of_node, "amlogic,secure-monitor", 0);
> + if (!sm_np)
> + return -ENODEV;
> +
> + hw->sec_fw = meson_sm_get(sm_np);
> + of_node_put(sm_np);
> + if (IS_ERR(hw->sec_fw))
> + return PTR_ERR(hw->sec_fw);
[Severity: High]
In drivers/firmware/meson/meson_sm.c, meson_sm_get() returns NULL (not an
ERR_PTR) when the secure-monitor platform device has not probed yet.
Because IS_ERR(NULL) is false, dev_request_hw_resources() succeeds with
hw->sec_fw == NULL instead of returning -EPROBE_DEFER. When decoding starts in
m2mops_vdec_device_run() -> load_firmware() -> meson_sm_call(hw->sec_fw, ...),
won't meson_sm_call() dereference a NULL hw->sec_fw pointer on fw->chip?
Similarly, above that check, if meson_canvas_get() were to return NULL,
if (IS_ERR_OR_NULL(hw->canvas)) return PTR_ERR(hw->canvas); would return 0.
Should hw->canvas use IS_ERR(hw->canvas) and hw->sec_fw check !hw->sec_fw and
return -EPROBE_DEFER?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
> new file mode 100644
> index 0000000000000..9af9eae88fb89
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
[ ... ]
> + /* Open session */
> + memcpy(sess_arg.uuid, uuid.b, TEE_IOCTL_UUID_LEN);
> + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
> + sess_arg.num_params = TEE_PARAM_NUM;
> + ret = tee_client_open_session(ctx, &sess_arg, param_init);
> + if (ret < 0 || sess_arg.ret != TEEC_SUCCESS) {
> + dev_info(hw->dev,
> + "%s open session failed, cmd = %u, ret = %d, res = 0x%x, origin = 0x%x\n",
> + __func__, cmd, ret, sess_arg.ret, sess_arg.ret_origin);
> + if (!ret)
> + ret = sess_arg.ret;
> + goto free_shm;
> + }
> +
> + inv_arg.func = cmd;
> + inv_arg.session = sess_arg.session;
> + inv_arg.num_params = TEE_PARAM_NUM;
> +
> + while (sent_size < fw_size) {
> + memset(shm_va, 0, ONCE_SENT_SIZE);
> + if (fw_size - sent_size > ONCE_SENT_SIZE) {
> + memcpy(shm_va, (firmware_data + sent_size),
> + ONCE_SENT_SIZE);
> + param_invoke[0].u.memref.size = ONCE_SENT_SIZE;
> + } else {
> + memcpy(shm_va, (firmware_data + sent_size),
> + fw_size - sent_size);
> + param_invoke[0].u.memref.size = (fw_size - sent_size);
> + }
> + param_invoke[0].u.memref.shm = shm;
> + ret = tee_client_invoke_func(ctx, &inv_arg, param_invoke);
> + if (ret < 0 || (inv_arg.ret != TEEC_SUCCESS && inv_arg.ret != TEEC_ERROR_BUSY)) {
> + dev_info(hw->dev,
> + "%s invoke func failed, cmd = %u, ret= %d, res = 0x%x, origin = 0x%x\n",
> + __func__, cmd, ret, inv_arg.ret,
> + inv_arg.ret_origin);
> + if (!ret)
> + ret = inv_arg.ret;
> + goto close_session;
> + }
> + sent_size += param_invoke[0].u.memref.size;
> + }
[ ... ]
> + ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
> + if (IS_ERR(ctx)) {
> + dev_info(hw->dev, "Failed to open TEE context\n");
> + ret = PTR_ERR(ctx);
> + goto free_firmware;
> + }
[Severity: High]
In aml_vdec_drv_probe() -> dev_request_hw_resources() -> aml_tee_fw_preload()
-> pass_firmware_to_tee():
1. When the OP-TEE driver has not registered its TEE device yet,
tee_client_open_context() returns ERR_PTR(-ENOENT). Propagating -ENOENT causes
aml_vdec_drv_probe() to fail permanently instead of deferring probe with
-EPROBE_DEFER.
2. In tee_pta_invoke_cmd(), when tee_client_open_session() or
tee_client_invoke_func() returns 0 with sess_arg.ret != TEEC_SUCCESS or
inv_arg.ret != TEEC_SUCCESS, ret is assigned the raw u32 TEEC status code
rather than a negative errno (which aml_vdec_drv_probe() only checks for
ret < 0).
3. In tee_pta_invoke_cmd(), when inv_arg.ret == TEEC_ERROR_BUSY, the error
branch is skipped and sent_size += param_invoke[0].u.memref.size advances past
the current 128 KB chunk without retrying it. Won't this silently drop a
chunk of firmware data?
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c
> new file mode 100644
> index 0000000000000..7ec7af73d44c7
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/h264.c
[ ... ]
> +static int poc_is_in_dpb(int poc, const struct v4l2_h264_dpb_entry *dpb)
> +{
> + int i;
> + int ret = 0;
> +
> + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
> + if (poc == dpb[i].top_field_order_cnt) {
> + ret = 1;
> + break;
> + }
> + }
> +
> + return ret;
> +}
[Severity: High]
In poc_is_in_dpb(), dpb[i].top_field_order_cnt is matched across all 16
entries of dpb[] without checking dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE.
The same issue also occurs in get_dpb_by_poc() further below:
static struct v4l2_h264_dpb_entry *
get_dpb_by_poc(struct v4l2_ctrl_h264_decode_params *decode, int poc)
{
int i;
for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
if (decode->dpb[i].top_field_order_cnt == poc)
return &decode->dpb[i];
}
return NULL;
}
Because unused DPB entries have flags == 0 and top_field_order_cnt == 0 (and
evicted entries may retain their stale top_field_order_cnt with the active
flag cleared):
1. In h264_config_decode_spec() -> clear_unused_col_buf(), poc_is_in_dpb()
matches inactive DPB slots, preventing canvas_pos and colocated_buf slots from
being released on non-IDR frames and exhausting the 17 canvas_pos slots
(especially on multi-slice frames where h264_config_decode_spec() is called on
every slice).
2. In h264_config_decode_buf(), if an inactive DPB slot with
top_field_order_cnt == 0 precedes an active reference picture with POC 0,
get_dpb_by_poc(decode, 0) returns the inactive slot, so
if (dpb && (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) evaluates to false
and writes 0 into H264_BUFFER_INFO_DATA for the active reference frame.
Should both poc_is_in_dpb() and get_dpb_by_poc() require
(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)?
[ ... ]
> +static int get_refidx_by_picnum(struct aml_h264_ctx *h264_ctx, int pic_num,
> + int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0[0];
> + else
> + ref_list = &h264_ctx->ref_list1[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num)
> + return i;
> + }
> +
> + return -1;
> +}
> +
> +static struct h264_decode_buf_spec *get_st_refpic_by_num(struct aml_h264_ctx *h264_ctx,
> + int pic_num, int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0_unreordered[0];
> + else
> + ref_list = &h264_ctx->ref_list1_unreordered[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 0)
> + return &ref_list[i];
> + }
> +
> + return NULL;
> +}
> +
> +static struct h264_decode_buf_spec *get_lt_refpic_by_num(struct aml_h264_ctx *h264_ctx,
> + int pic_num, int curr_list)
> +{
> + int i;
> + struct h264_decode_buf_spec *ref_list;
> +
> + if (curr_list == 0)
> + ref_list = &h264_ctx->ref_list0_unreordered[0];
> + else
> + ref_list = &h264_ctx->ref_list1_unreordered[0];
> +
> + for (i = 0; ref_list[i].dpb; i++) {
> + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 1)
> + return &ref_list[i];
> + }
> +
> + return NULL;
> +}
> +
> +static void reorder_short_term(struct slice *curr_slice, int cur_list,
> + int pic_num_lx, int *ref_idx_lx)
> +{
[ ... ]
> + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--)
> + memcpy(&ref_list_reordered[c_idx], &ref_list_reordered[c_idx - 1],
> + sizeof(struct h264_decode_buf_spec));
> +
> + memcpy(&ref_list_reordered[*ref_idx_lx], pic_lx, sizeof(struct h264_decode_buf_spec));
> + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__,
> + *ref_idx_lx, pic_lx, ref_list_reordered[*ref_idx_lx].dpb->pic_num);
> + *ref_idx_lx = *ref_idx_lx + 1;
> +
> + n_idx = *ref_idx_lx;
> + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) {
> + if (ref_list_reordered[c_idx].long_term_flag || !ref_list_reordered[c_idx].dpb ||
> + ref_list_reordered[c_idx].dpb->pic_num != pic_num_lx)
> + memcpy(&ref_list_reordered[n_idx++], &ref_list_reordered[c_idx],
> + sizeof(struct h264_decode_buf_spec));
> + }
> +
> + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active;
> +}
[Severity: Critical]
Can reference list reordering and canvas allocation read out of bounds,
dereference a NULL dpb pointer, and leak hardware canvases?
1. In reorder_short_term() and reorder_long_term(), when
num_ref_idx_lx_active == 16 (V4L2_H264_NUM_DPB_ENTRIES), the shift loop copies
ref_list[15] into ref_list[16] and sets list_size[cur_list] = 16. Because
h264_release_decode_spec() only clears indices 0 .. list_size[cur_list] - 1,
ref_list[16].dpb remains non-NULL. On a subsequent call where
ref_list[0..16].dpb are all non-NULL, the loops in get_refidx_by_picnum(),
get_st_refpic_by_num(), and get_lt_refpic_by_num() have no upper bound on i
and read past the end of ref_list0[] / ref_list1[].
2. When num_ref_idx_lx_active exceeds the number of valid entries built by
h264_config_ref_lists() (or when h264_config_ref_spec() fails on an entry),
reorder_short_term() and reorder_long_term() set
h264_ctx->list_size[cur_list] = num_ref_idx_lx_active. Then get_canvas_index()
allocates hardware canvases via config_decode_canvas() for every entry up to
list_size[cur_list] without checking buf->used or buf->valid (also writing DMA
address 0 to ANC0_CANVAS_ADDR for canvas_pos 0), whereas
h264_release_decode_spec() only frees canvases when if (buf->used) is true,
permanently leaking canvases from the meson_canvas pool. In addition, in
h264_threaded_isr_func(), h264_release_decode_spec() is only called before
get_canvas_index() when first_mb_in_slice != 0, so a second
H264_SLICE_HEADER_DONE interrupt with first_mb_in_slice == 0 leaks the
canvases allocated for the first slice.
3. In h264_config_decode_buf(), when h264_ctx->list_size[1] > 0,
colocate_pic = &h264_ctx->ref_list1[0] dereferences
colocate_pic->dpb->top_field_order_cnt without checking colocate_pic->valid or
colocate_pic->dpb != NULL.
[ ... ]
> + read_poll_timeout(read_dos_reg, reg_val,
> + !(reg_val & 0x800),
> + 10, 0, true, hw, H264_CO_MB_RW_CTL);
[Severity: High]
In h264_config_decode_buf(), read_poll_timeout() is called with timeout_us = 0
while vdec_threaded_isr_handler() holds hw->ctx_mutex.
Since timeout_us = 0 polls indefinitely in read_poll_timeout(), if bit 11
(0x800) of H264_CO_MB_RW_CTL does not clear, won't vdec_threaded_isr_handler()
spin forever under hw->ctx_mutex and cause aml_h264_dec_run() to deadlock on
mutex_lock(&dec_hw->ctx_mutex) when wait_event_interruptible_timeout()
expires?
> +
> + /* col buf for curr frame */
> + colocate_adr_offset = COL_SIZE_FOR_ONE_MB;
> + if (use_mode_8x8_flag)
> + colocate_adr_offset >>= 2;
> + colocate_adr_offset *= curr_slice->first_mb_in_slice;
> +
> + if (h264_ctx->curr_spec.col_buf_index >= 0 &&
> + h264_ctx->curr_spec.col_buf_index < h264_ctx->colocated_buf_num) {
> + /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
> + colocate_wr_adr = (unsigned int)h264_ctx->collated_cma_addr +
> + ((h264_ctx->one_col_buf_size *
> + h264_ctx->curr_spec.col_buf_index) >> (use_mode_8x8_flag ? 2 : 0));
> + if (colocate_adr_offset > h264_ctx->one_col_buf_size ||
> + colocate_wr_adr + h264_ctx->one_col_buf_size >
> + (unsigned int)h264_ctx->collated_cma_addr_end) {
> + dev_err(hw->dev,
> + "Error, colocate buf is not enough, index is %d\n",
> + h264_ctx->curr_spec.col_buf_index);
> + return -1;
> + }
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR,
> + (colocate_wr_adr + colocate_adr_offset));
> + dev_dbg(hw->dev, "col buffer addr = 0x%x col_buf_index %d\n",
> + (colocate_wr_adr + colocate_adr_offset),
> + h264_ctx->curr_spec.col_buf_index);
> + } else {
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, 0xffffffff);
> + dev_dbg(hw->dev, "col buffer addr = 0xffffffff\n");
> + }
> +
> + if (h264_ctx->list_size[1] > 0) {
> + struct h264_decode_buf_spec *colocate_pic =
> + &h264_ctx->ref_list1[0];
> + struct h264_decode_buf_spec *curr_pic = &h264_ctx->curr_spec;
> + int l10_structure = 2; /* for pic struct == FRAME, default to 2 */
> + int cur_colocate_ref_type;
> + unsigned int colocate_rd_adr;
> + unsigned int colocate_rd_adr_offset = 0;
> + unsigned int val;
> +
> + cur_colocate_ref_type =
> + (abs(curr_pic->poc - colocate_pic->dpb->top_field_order_cnt) <
> + abs(curr_pic->poc - colocate_pic->dpb->bottom_field_order_cnt)) ? 0 : 1;
> + colocate_rd_adr_offset = COL_SIZE_FOR_ONE_MB;
> + if (use_mode_8x8_flag)
> + colocate_rd_adr_offset >>= 2;
> +
> + colocate_rd_adr_offset *= curr_slice->first_mb_in_slice;
> + if (colocate_pic->col_buf_index >= 0 &&
> + colocate_pic->col_buf_index < h264_ctx->colocated_buf_num) {
> + /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
> + colocate_rd_adr = (unsigned int)h264_ctx->collated_cma_addr +
> + ((h264_ctx->one_col_buf_size *
> + colocate_pic->col_buf_index) >> (use_mode_8x8_flag
> + ? 2 : 0));
> + if (colocate_rd_adr_offset > h264_ctx->one_col_buf_size ||
> + colocate_rd_adr + h264_ctx->one_col_buf_size >
> + (unsigned int)h264_ctx->collated_cma_addr_end) {
> + dev_err(hw->dev,
> + "Error, colocate rd buf is not enough, index is %d\n",
> + colocate_pic->col_buf_index);
> + return -1;
> + }
> + val = ((colocate_rd_adr_offset + colocate_rd_adr) >> 3) |
> + (cur_colocate_ref_type << 29) |
> + (l10_structure << 30);
> + regmap_write(hw->map[DOS_BUS], H264_CO_MB_RD_ADDR, val);
[Severity: Critical]
In h264_config_decode_buf(), can a slice with a non-zero first_mb_in_slice
program H264_CO_MB_WR_ADDR and H264_CO_MB_RD_ADDR to DMA past the end of the
colocated buffer slot or past h264_ctx->collated_cma_addr_end?
When use_mode_8x8_flag is set, each slot's stride is
h264_ctx->one_col_buf_size >> 2, but colocate_adr_offset and
colocate_rd_adr_offset are compared against the unshifted
h264_ctx->one_col_buf_size, allowing the offset to cross into the next 3
colocated buffer slots.
Furthermore, the end-address check tests
colocate_wr_adr + h264_ctx->one_col_buf_size > h264_ctx->collated_cma_addr_end
without adding colocate_adr_offset (and likewise for colocate_rd_adr without
colocate_rd_adr_offset), so when col_buf_index is at the end of the buffer and
colocate_adr_offset > 0, colocate_wr_adr + colocate_adr_offset can point up to
one_col_buf_size bytes past collated_cma_addr_end.
[ ... ]
> +}
[ ... ]
> --
> 2.43.0
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-b4-s4-vdec-upstream-v12-0-616240978792@amlogic.com?part=5
next prev parent reply other threads:[~2026-10-22 6:01 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 5:26 [PATCH v12 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-10-22 5:26 ` Zhentao Guo via B4 Relay
2026-10-22 5:26 ` Zhentao Guo
2026-10-22 5:26 ` [PATCH v12 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
2026-10-22 5:26 ` Zhentao Guo
2026-10-22 5:26 ` Zhentao Guo via B4 Relay
2026-10-22 5:27 ` [PATCH v12 2/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo
2026-10-22 5:27 ` [PATCH v12 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo
2026-10-22 5:27 ` Zhentao Guo via B4 Relay
2026-10-30 11:27 ` Krzysztof Kozlowski
2026-10-30 11:27 ` Krzysztof Kozlowski
2026-10-30 11:57 ` Zhentao Guo
2026-10-30 11:57 ` Zhentao Guo
2026-10-22 5:27 ` [PATCH v12 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo
2026-10-30 11:30 ` Krzysztof Kozlowski
2026-10-30 11:30 ` Krzysztof Kozlowski
2026-10-30 11:51 ` Zhentao Guo
2026-10-30 11:51 ` Zhentao Guo
2026-10-22 5:27 ` [PATCH v12 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo
2026-10-22 5:27 ` Zhentao Guo via B4 Relay
2026-10-22 6:01 ` sashiko-bot [this message]
2026-10-22 6:01 ` sashiko-bot
2026-10-23 11:25 ` Zhentao Guo
2026-10-23 11:25 ` Zhentao Guo
2026-10-22 5:27 ` [PATCH v12 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
2026-10-22 5:27 ` Zhentao Guo
2026-10-22 5:27 ` Zhentao Guo via B4 Relay
2026-10-22 5:53 ` sashiko-bot
2026-10-22 5:53 ` sashiko-bot
2026-10-23 9:46 ` Zhentao Guo
2026-10-23 9:46 ` Zhentao Guo
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-181046@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=linux-amlogic@lists.infradead.org \
--cc=media-ci@linuxtv.org \
--cc=neil.armstrong@linaro.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=zhentao.guo@amlogic.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 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.