mirror of
https://gitee.com/bianbu-linux/linux-6.6
synced 2025-07-24 01:54:03 -04:00
If vimc module is removed while streaming is in progress, sensor subdev unregister runs into general protection fault when it tries to unregister media entities. This is a common subdev problem related to releasing pads from v4l2_device_unregister_subdev() before calling unregister. Unregister references pads during unregistering subdev. The sd release handler is the right place for releasing all sd resources including pads. The release handlers currently release all resources except the pads. Fix v4l2_device_unregister_subdev() not release pads and release pads from the sd_int_op release handlers. kernel: [ 4136.715839] general protection fault: 0000 [#1] SMP PTI kernel: [ 4136.715847] CPU: 2 PID: 1972 Comm: bash Not tainted 5.3.0-rc2+ #4 kernel: [ 4136.715850] Hardware name: Dell Inc. OptiPlex 790/0HY9JP, BIOS A18 09/24/2013 kernel: [ 4136.715858] RIP: 0010:media_gobj_destroy.part.16+0x1f/0x60 kernel: [ 4136.715863] Code: ff 66 2e 0f 1f 84 00 00 00 00 00 66 66 66 66 90 55 48 89 fe 48 89 e5 53 48 89 fb 48 c7 c7 00 7f cf b0 e8 24 fa ff ff 48 8b 03 <48> 83 80 a0 00 00 00 01 48 8b 43 18 48 8b 53 10 48 89 42 08 48 89 kernel: [ 4136.715866] RSP: 0018:ffff9b2248fe3cb0 EFLAGS: 00010246 kernel: [ 4136.715870] RAX: bcf2bfbfa0d63c2f RBX: ffff88c3eb37e9c0 RCX: 00000000802a0018 kernel: [ 4136.715873] RDX: ffff88c3e4f6a078 RSI: ffff88c3eb37e9c0 RDI: ffffffffb0cf7f00 kernel: [ 4136.715876] RBP: ffff9b2248fe3cb8 R08: 0000000001000002 R09: ffffffffb0492b00 kernel: [ 4136.715879] R10: ffff9b2248fe3c28 R11: 0000000000000001 R12: 0000000000000038 kernel: [ 4136.715881] R13: ffffffffc09a1628 R14: ffff88c3e4f6a028 R15: fffffffffffffff2 kernel: [ 4136.715885] FS: 00007f8389647740(0000) GS:ffff88c465500000(0000) knlGS:0000000000000000 kernel: [ 4136.715888] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 kernel: [ 4136.715891] CR2: 000055d008f80fd8 CR3: 00000001996ec005 CR4: 00000000000606e0 kernel: [ 4136.715894] Call Trace: kernel: [ 4136.715903] media_gobj_destroy+0x14/0x20 kernel: [ 4136.715908] __media_device_unregister_entity+0xb3/0xe0 kernel: [ 4136.715915] media_device_unregister_entity+0x30/0x40 kernel: [ 4136.715920] v4l2_device_unregister_subdev+0xa8/0xe0 kernel: [ 4136.715928] vimc_ent_sd_unregister+0x1e/0x30 [vimc] kernel: [ 4136.715933] vimc_sen_rm+0x16/0x20 [vimc] kernel: [ 4136.715938] vimc_remove+0x3e/0xa0 [vimc] kernel: [ 4136.715945] platform_drv_remove+0x25/0x50 kernel: [ 4136.715951] device_release_driver_internal+0xe0/0x1b0 kernel: [ 4136.715956] device_driver_detach+0x14/0x20 kernel: [ 4136.715960] unbind_store+0xd1/0x130 kernel: [ 4136.715965] drv_attr_store+0x27/0x40 kernel: [ 4136.715971] sysfs_kf_write+0x48/0x60 kernel: [ 4136.715976] kernfs_fop_write+0x128/0x1b0 kernel: [ 4136.715982] __vfs_write+0x1b/0x40 kernel: [ 4136.715987] vfs_write+0xc3/0x1d0 kernel: [ 4136.715993] ksys_write+0xaa/0xe0 kernel: [ 4136.715999] __x64_sys_write+0x1a/0x20 kernel: [ 4136.716005] do_syscall_64+0x5a/0x130 kernel: [ 4136.716010] entry_SYSCALL_64_after_hwframe+0x4 Signed-off-by: Shuah Khan <skhan@linuxfoundation.org> Acked-by: Helen Koike <helen.koike@collabora.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
429 lines
10 KiB
C
429 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* vimc-common.c Virtual Media Controller Driver
|
|
*
|
|
* Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
|
|
#include "vimc-common.h"
|
|
|
|
/*
|
|
* NOTE: non-bayer formats need to come first (necessary for enum_mbus_code
|
|
* in the scaler)
|
|
*/
|
|
static const struct vimc_pix_map vimc_pix_map_list[] = {
|
|
/* TODO: add all missing formats */
|
|
|
|
/* RGB formats */
|
|
{
|
|
.code = MEDIA_BUS_FMT_BGR888_1X24,
|
|
.pixelformat = V4L2_PIX_FMT_BGR24,
|
|
.bpp = 3,
|
|
.bayer = false,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_RGB888_1X24,
|
|
.pixelformat = V4L2_PIX_FMT_RGB24,
|
|
.bpp = 3,
|
|
.bayer = false,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_ARGB8888_1X32,
|
|
.pixelformat = V4L2_PIX_FMT_ARGB32,
|
|
.bpp = 4,
|
|
.bayer = false,
|
|
},
|
|
|
|
/* Bayer formats */
|
|
{
|
|
.code = MEDIA_BUS_FMT_SBGGR8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SBGGR8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGBRG8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGBRG8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGRBG8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGRBG8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SRGGB8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SRGGB8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SBGGR10_1X10,
|
|
.pixelformat = V4L2_PIX_FMT_SBGGR10,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGBRG10_1X10,
|
|
.pixelformat = V4L2_PIX_FMT_SGBRG10,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGRBG10_1X10,
|
|
.pixelformat = V4L2_PIX_FMT_SGRBG10,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SRGGB10_1X10,
|
|
.pixelformat = V4L2_PIX_FMT_SRGGB10,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
|
|
/* 10bit raw bayer a-law compressed to 8 bits */
|
|
{
|
|
.code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
|
|
/* 10bit raw bayer DPCM compressed to 8 bits */
|
|
{
|
|
.code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8,
|
|
.pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8,
|
|
.bpp = 1,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SBGGR12_1X12,
|
|
.pixelformat = V4L2_PIX_FMT_SBGGR12,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGBRG12_1X12,
|
|
.pixelformat = V4L2_PIX_FMT_SGBRG12,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SGRBG12_1X12,
|
|
.pixelformat = V4L2_PIX_FMT_SGRBG12,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
{
|
|
.code = MEDIA_BUS_FMT_SRGGB12_1X12,
|
|
.pixelformat = V4L2_PIX_FMT_SRGGB12,
|
|
.bpp = 2,
|
|
.bayer = true,
|
|
},
|
|
};
|
|
|
|
const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i)
|
|
{
|
|
if (i >= ARRAY_SIZE(vimc_pix_map_list))
|
|
return NULL;
|
|
|
|
return &vimc_pix_map_list[i];
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_pix_map_by_index);
|
|
|
|
const struct vimc_pix_map *vimc_pix_map_by_code(u32 code)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) {
|
|
if (vimc_pix_map_list[i].code == code)
|
|
return &vimc_pix_map_list[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_pix_map_by_code);
|
|
|
|
const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) {
|
|
if (vimc_pix_map_list[i].pixelformat == pixelformat)
|
|
return &vimc_pix_map_list[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_pix_map_by_pixelformat);
|
|
|
|
/* Helper function to allocate and initialize pads */
|
|
struct media_pad *vimc_pads_init(u16 num_pads, const unsigned long *pads_flag)
|
|
{
|
|
struct media_pad *pads;
|
|
unsigned int i;
|
|
|
|
/* Allocate memory for the pads */
|
|
pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL);
|
|
if (!pads)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* Initialize the pads */
|
|
for (i = 0; i < num_pads; i++) {
|
|
pads[i].index = i;
|
|
pads[i].flags = pads_flag[i];
|
|
}
|
|
|
|
return pads;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_pads_init);
|
|
|
|
int vimc_pipeline_s_stream(struct media_entity *ent, int enable)
|
|
{
|
|
struct v4l2_subdev *sd;
|
|
struct media_pad *pad;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ent->num_pads; i++) {
|
|
if (ent->pads[i].flags & MEDIA_PAD_FL_SOURCE)
|
|
continue;
|
|
|
|
/* Start the stream in the subdevice direct connected */
|
|
pad = media_entity_remote_pad(&ent->pads[i]);
|
|
if (!pad)
|
|
continue;
|
|
|
|
if (!is_media_entity_v4l2_subdev(pad->entity))
|
|
return -EINVAL;
|
|
|
|
sd = media_entity_to_v4l2_subdev(pad->entity);
|
|
ret = v4l2_subdev_call(sd, video, s_stream, enable);
|
|
if (ret && ret != -ENOIOCTLCMD)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_pipeline_s_stream);
|
|
|
|
static int vimc_get_mbus_format(struct media_pad *pad,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
if (is_media_entity_v4l2_subdev(pad->entity)) {
|
|
struct v4l2_subdev *sd =
|
|
media_entity_to_v4l2_subdev(pad->entity);
|
|
int ret;
|
|
|
|
fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE;
|
|
fmt->pad = pad->index;
|
|
|
|
ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
} else if (is_media_entity_v4l2_video_device(pad->entity)) {
|
|
struct video_device *vdev = container_of(pad->entity,
|
|
struct video_device,
|
|
entity);
|
|
struct vimc_ent_device *ved = video_get_drvdata(vdev);
|
|
const struct vimc_pix_map *vpix;
|
|
struct v4l2_pix_format vdev_fmt;
|
|
|
|
if (!ved->vdev_get_format)
|
|
return -ENOIOCTLCMD;
|
|
|
|
ved->vdev_get_format(ved, &vdev_fmt);
|
|
vpix = vimc_pix_map_by_pixelformat(vdev_fmt.pixelformat);
|
|
v4l2_fill_mbus_format(&fmt->format, &vdev_fmt, vpix->code);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int vimc_link_validate(struct media_link *link)
|
|
{
|
|
struct v4l2_subdev_format source_fmt, sink_fmt;
|
|
int ret;
|
|
|
|
ret = vimc_get_mbus_format(link->source, &source_fmt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = vimc_get_mbus_format(link->sink, &sink_fmt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pr_info("vimc link validate: "
|
|
"%s:src:%dx%d (0x%x, %d, %d, %d, %d) "
|
|
"%s:snk:%dx%d (0x%x, %d, %d, %d, %d)\n",
|
|
/* src */
|
|
link->source->entity->name,
|
|
source_fmt.format.width, source_fmt.format.height,
|
|
source_fmt.format.code, source_fmt.format.colorspace,
|
|
source_fmt.format.quantization, source_fmt.format.xfer_func,
|
|
source_fmt.format.ycbcr_enc,
|
|
/* sink */
|
|
link->sink->entity->name,
|
|
sink_fmt.format.width, sink_fmt.format.height,
|
|
sink_fmt.format.code, sink_fmt.format.colorspace,
|
|
sink_fmt.format.quantization, sink_fmt.format.xfer_func,
|
|
sink_fmt.format.ycbcr_enc);
|
|
|
|
/* The width, height and code must match. */
|
|
if (source_fmt.format.width != sink_fmt.format.width
|
|
|| source_fmt.format.height != sink_fmt.format.height
|
|
|| source_fmt.format.code != sink_fmt.format.code)
|
|
return -EPIPE;
|
|
|
|
/*
|
|
* The field order must match, or the sink field order must be NONE
|
|
* to support interlaced hardware connected to bridges that support
|
|
* progressive formats only.
|
|
*/
|
|
if (source_fmt.format.field != sink_fmt.format.field &&
|
|
sink_fmt.format.field != V4L2_FIELD_NONE)
|
|
return -EPIPE;
|
|
|
|
/*
|
|
* If colorspace is DEFAULT, then assume all the colorimetry is also
|
|
* DEFAULT, return 0 to skip comparing the other colorimetry parameters
|
|
*/
|
|
if (source_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT
|
|
|| sink_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT)
|
|
return 0;
|
|
|
|
/* Colorspace must match. */
|
|
if (source_fmt.format.colorspace != sink_fmt.format.colorspace)
|
|
return -EPIPE;
|
|
|
|
/* Colorimetry must match if they are not set to DEFAULT */
|
|
if (source_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT
|
|
&& sink_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT
|
|
&& source_fmt.format.ycbcr_enc != sink_fmt.format.ycbcr_enc)
|
|
return -EPIPE;
|
|
|
|
if (source_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT
|
|
&& sink_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT
|
|
&& source_fmt.format.quantization != sink_fmt.format.quantization)
|
|
return -EPIPE;
|
|
|
|
if (source_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT
|
|
&& sink_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT
|
|
&& source_fmt.format.xfer_func != sink_fmt.format.xfer_func)
|
|
return -EPIPE;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_link_validate);
|
|
|
|
static const struct media_entity_operations vimc_ent_sd_mops = {
|
|
.link_validate = vimc_link_validate,
|
|
};
|
|
|
|
int vimc_ent_sd_register(struct vimc_ent_device *ved,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_device *v4l2_dev,
|
|
const char *const name,
|
|
u32 function,
|
|
u16 num_pads,
|
|
const unsigned long *pads_flag,
|
|
const struct v4l2_subdev_internal_ops *sd_int_ops,
|
|
const struct v4l2_subdev_ops *sd_ops)
|
|
{
|
|
int ret;
|
|
|
|
/* Allocate the pads. Should be released from the sd_int_op release */
|
|
ved->pads = vimc_pads_init(num_pads, pads_flag);
|
|
if (IS_ERR(ved->pads))
|
|
return PTR_ERR(ved->pads);
|
|
|
|
/* Fill the vimc_ent_device struct */
|
|
ved->ent = &sd->entity;
|
|
|
|
/* Initialize the subdev */
|
|
v4l2_subdev_init(sd, sd_ops);
|
|
sd->internal_ops = sd_int_ops;
|
|
sd->entity.function = function;
|
|
sd->entity.ops = &vimc_ent_sd_mops;
|
|
sd->owner = THIS_MODULE;
|
|
strscpy(sd->name, name, sizeof(sd->name));
|
|
v4l2_set_subdevdata(sd, ved);
|
|
|
|
/* Expose this subdev to user space */
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
if (sd->ctrl_handler)
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
|
|
|
|
/* Initialize the media entity */
|
|
ret = media_entity_pads_init(&sd->entity, num_pads, ved->pads);
|
|
if (ret)
|
|
goto err_clean_pads;
|
|
|
|
/* Register the subdev with the v4l2 and the media framework */
|
|
ret = v4l2_device_register_subdev(v4l2_dev, sd);
|
|
if (ret) {
|
|
dev_err(v4l2_dev->dev,
|
|
"%s: subdev register failed (err=%d)\n",
|
|
name, ret);
|
|
goto err_clean_m_ent;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_clean_m_ent:
|
|
media_entity_cleanup(&sd->entity);
|
|
err_clean_pads:
|
|
vimc_pads_cleanup(ved->pads);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_ent_sd_register);
|
|
|
|
void vimc_ent_sd_unregister(struct vimc_ent_device *ved, struct v4l2_subdev *sd)
|
|
{
|
|
media_entity_cleanup(ved->ent);
|
|
v4l2_device_unregister_subdev(sd);
|
|
}
|
|
EXPORT_SYMBOL_GPL(vimc_ent_sd_unregister);
|