linux/drivers/media/platform/chips-media/wave5/wave5-helper.c
Jackson Lee e66ff2b08e media: chips-media: wave5: Fix Null reference while testing fluster
When multi instances are created/destroyed, many interrupts happens
and structures for decoder are removed.
"struct vpu_instance" this structure is shared for all flow in the decoder,
so if the structure is not protected by lock, Null dereference
could happens sometimes.
IRQ Handler was spilt to two phases and Lock was added as well.

Fixes: 9707a6254a ("media: chips-media: wave5: Add the v4l2 layer")
Cc: stable@vger.kernel.org
Signed-off-by: Jackson Lee <jackson.lee@chipsnmedia.com>
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Brandon Brnich <b-brnich@ti.com>
Signed-off-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
2026-01-05 15:56:31 +01:00

270 lines
7.6 KiB
C

// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
* Wave5 series multi-standard codec IP - decoder interface
*
* Copyright (C) 2021-2023 CHIPS&MEDIA INC
*/
#include "wave5-helper.h"
#define DEFAULT_BS_SIZE(width, height) ((width) * (height) / 8 * 3)
const char *state_to_str(enum vpu_instance_state state)
{
switch (state) {
case VPU_INST_STATE_NONE:
return "NONE";
case VPU_INST_STATE_OPEN:
return "OPEN";
case VPU_INST_STATE_INIT_SEQ:
return "INIT_SEQ";
case VPU_INST_STATE_PIC_RUN:
return "PIC_RUN";
case VPU_INST_STATE_STOP:
return "STOP";
default:
return "UNKNOWN";
}
}
int wave5_kfifo_alloc(struct vpu_instance *inst)
{
return kfifo_alloc(&inst->irq_status, 16 * sizeof(int), GFP_KERNEL);
}
void wave5_cleanup_instance(struct vpu_instance *inst, struct file *filp)
{
int i;
/*
* For Wave515 SRAM memory is allocated at
* wave5_vpu_dec_register_device() and freed at
* wave5_vpu_dec_unregister_device().
*/
if (list_is_singular(&inst->list) &&
inst->dev->product_code != WAVE515_CODE)
wave5_vdi_free_sram(inst->dev);
for (i = 0; i < inst->fbc_buf_count; i++)
wave5_vpu_dec_reset_framebuffer(inst, i);
wave5_vdi_free_dma_memory(inst->dev, &inst->bitstream_vbuf);
v4l2_ctrl_handler_free(&inst->v4l2_ctrl_hdl);
if (inst->v4l2_fh.vdev) {
v4l2_fh_del(&inst->v4l2_fh, filp);
v4l2_fh_exit(&inst->v4l2_fh);
}
kfifo_free(&inst->irq_status);
ida_free(&inst->dev->inst_ida, inst->id);
kfree(inst->codec_info);
kfree(inst);
}
int wave5_vpu_release_device(struct file *filp,
int (*close_func)(struct vpu_instance *inst, u32 *fail_res),
char *name)
{
struct vpu_instance *inst = file_to_vpu_inst(filp);
int ret = 0;
unsigned long flags;
v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx);
/*
* To prevent Null reference exception, the existing irq handler were
* separated to two modules.
* One is to queue interrupt reason into the irq handler,
* the other is irq_thread to call the wave5_vpu_dec_finish_decode
* to get decoded frame.
* The list of instances should be protected between all flow of the
* decoding process, but to protect the list in the irq_handler, spin lock
* should be used, and mutex should be used in the irq_thread because spin lock
* is not able to be used because mutex is already being used
* in the wave5_vpu_dec_finish_decode.
* So the spin lock and mutex were used to protect the list in the release function.
*/
ret = mutex_lock_interruptible(&inst->dev->irq_lock);
if (ret)
return ret;
spin_lock_irqsave(&inst->dev->irq_spinlock, flags);
list_del_init(&inst->list);
spin_unlock_irqrestore(&inst->dev->irq_spinlock, flags);
mutex_unlock(&inst->dev->irq_lock);
if (inst->state != VPU_INST_STATE_NONE) {
u32 fail_res;
ret = close_func(inst, &fail_res);
if (fail_res == WAVE5_SYSERR_VPU_STILL_RUNNING) {
dev_err(inst->dev->dev, "%s close failed, device is still running\n",
name);
return -EBUSY;
}
if (ret && ret != -EIO) {
dev_err(inst->dev->dev, "%s close, fail: %d\n", name, ret);
return ret;
}
}
wave5_cleanup_instance(inst, filp);
return ret;
}
int wave5_vpu_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq,
const struct vb2_ops *ops)
{
struct vpu_instance *inst = priv;
int ret;
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
src_vq->mem_ops = &vb2_dma_contig_memops;
src_vq->ops = ops;
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
src_vq->buf_struct_size = sizeof(struct vpu_src_buffer);
src_vq->drv_priv = inst;
src_vq->lock = &inst->dev->dev_lock;
src_vq->dev = inst->dev->v4l2_dev.dev;
ret = vb2_queue_init(src_vq);
if (ret)
return ret;
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
dst_vq->mem_ops = &vb2_dma_contig_memops;
dst_vq->ops = ops;
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
dst_vq->buf_struct_size = sizeof(struct vpu_src_buffer);
dst_vq->drv_priv = inst;
dst_vq->lock = &inst->dev->dev_lock;
dst_vq->dev = inst->dev->v4l2_dev.dev;
ret = vb2_queue_init(dst_vq);
if (ret)
return ret;
return 0;
}
int wave5_vpu_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub)
{
struct vpu_instance *inst = wave5_to_vpu_inst(fh);
bool is_decoder = inst->type == VPU_INST_TYPE_DEC;
dev_dbg(inst->dev->dev, "%s: [%s] type: %u id: %u | flags: %u\n", __func__,
is_decoder ? "decoder" : "encoder", sub->type, sub->id, sub->flags);
switch (sub->type) {
case V4L2_EVENT_EOS:
return v4l2_event_subscribe(fh, sub, 0, NULL);
case V4L2_EVENT_SOURCE_CHANGE:
if (is_decoder)
return v4l2_src_change_event_subscribe(fh, sub);
return -EINVAL;
case V4L2_EVENT_CTRL:
return v4l2_ctrl_subscribe_event(fh, sub);
default:
return -EINVAL;
}
}
int wave5_vpu_g_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
{
struct vpu_instance *inst = file_to_vpu_inst(file);
int i;
f->fmt.pix_mp.width = inst->src_fmt.width;
f->fmt.pix_mp.height = inst->src_fmt.height;
f->fmt.pix_mp.pixelformat = inst->src_fmt.pixelformat;
f->fmt.pix_mp.field = inst->src_fmt.field;
f->fmt.pix_mp.flags = inst->src_fmt.flags;
f->fmt.pix_mp.num_planes = inst->src_fmt.num_planes;
for (i = 0; i < f->fmt.pix_mp.num_planes; i++) {
f->fmt.pix_mp.plane_fmt[i].bytesperline = inst->src_fmt.plane_fmt[i].bytesperline;
f->fmt.pix_mp.plane_fmt[i].sizeimage = inst->src_fmt.plane_fmt[i].sizeimage;
}
f->fmt.pix_mp.colorspace = inst->colorspace;
f->fmt.pix_mp.ycbcr_enc = inst->ycbcr_enc;
f->fmt.pix_mp.quantization = inst->quantization;
f->fmt.pix_mp.xfer_func = inst->xfer_func;
return 0;
}
const struct vpu_format *wave5_find_vpu_fmt(unsigned int v4l2_pix_fmt,
const struct vpu_format fmt_list[MAX_FMTS])
{
unsigned int index;
for (index = 0; index < MAX_FMTS; index++) {
if (fmt_list[index].v4l2_pix_fmt == v4l2_pix_fmt)
return &fmt_list[index];
}
return NULL;
}
const struct vpu_format *wave5_find_vpu_fmt_by_idx(unsigned int idx,
const struct vpu_format fmt_list[MAX_FMTS])
{
if (idx >= MAX_FMTS)
return NULL;
if (!fmt_list[idx].v4l2_pix_fmt)
return NULL;
return &fmt_list[idx];
}
enum wave_std wave5_to_vpu_std(unsigned int v4l2_pix_fmt, enum vpu_instance_type type)
{
switch (v4l2_pix_fmt) {
case V4L2_PIX_FMT_H264:
return type == VPU_INST_TYPE_DEC ? W_AVC_DEC : W_AVC_ENC;
case V4L2_PIX_FMT_HEVC:
return type == VPU_INST_TYPE_DEC ? W_HEVC_DEC : W_HEVC_ENC;
default:
return STD_UNKNOWN;
}
}
void wave5_return_bufs(struct vb2_queue *q, u32 state)
{
struct vpu_instance *inst = vb2_get_drv_priv(q);
struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx;
struct v4l2_ctrl_handler v4l2_ctrl_hdl = inst->v4l2_ctrl_hdl;
struct vb2_v4l2_buffer *vbuf;
for (;;) {
if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
vbuf = v4l2_m2m_src_buf_remove(m2m_ctx);
else
vbuf = v4l2_m2m_dst_buf_remove(m2m_ctx);
if (!vbuf)
return;
v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, &v4l2_ctrl_hdl);
v4l2_m2m_buf_done(vbuf, state);
}
}
void wave5_update_pix_fmt(struct v4l2_pix_format_mplane *pix_mp,
int pix_fmt_type,
unsigned int width,
unsigned int height,
const struct v4l2_frmsize_stepwise *frmsize)
{
v4l2_apply_frmsize_constraints(&width, &height, frmsize);
if (pix_fmt_type == VPU_FMT_TYPE_CODEC) {
pix_mp->width = width;
pix_mp->height = height;
pix_mp->num_planes = 1;
pix_mp->plane_fmt[0].bytesperline = 0;
pix_mp->plane_fmt[0].sizeimage = max(DEFAULT_BS_SIZE(width, height),
pix_mp->plane_fmt[0].sizeimage);
} else {
v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, width, height);
}
pix_mp->flags = 0;
pix_mp->field = V4L2_FIELD_NONE;
}