mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-15 03:46:47 +07:00
d7fb5c361c
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>
394 lines
10 KiB
C
394 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* vimc-sensor.c Virtual Media Controller Driver
|
|
*
|
|
* Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
|
|
*/
|
|
|
|
#include <linux/v4l2-mediabus.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/v4l2-subdev.h>
|
|
#include <media/tpg/v4l2-tpg.h>
|
|
|
|
#include "vimc-common.h"
|
|
|
|
struct vimc_sen_device {
|
|
struct vimc_ent_device ved;
|
|
struct v4l2_subdev sd;
|
|
struct device *dev;
|
|
struct tpg_data tpg;
|
|
struct task_struct *kthread_sen;
|
|
u8 *frame;
|
|
/* The active format */
|
|
struct v4l2_mbus_framefmt mbus_format;
|
|
struct v4l2_ctrl_handler hdl;
|
|
};
|
|
|
|
static const struct v4l2_mbus_framefmt fmt_default = {
|
|
.width = 640,
|
|
.height = 480,
|
|
.code = MEDIA_BUS_FMT_RGB888_1X24,
|
|
.field = V4L2_FIELD_NONE,
|
|
.colorspace = V4L2_COLORSPACE_DEFAULT,
|
|
};
|
|
|
|
static int vimc_sen_init_cfg(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < sd->entity.num_pads; i++) {
|
|
struct v4l2_mbus_framefmt *mf;
|
|
|
|
mf = v4l2_subdev_get_try_format(sd, cfg, i);
|
|
*mf = fmt_default;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vimc_sen_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
const struct vimc_pix_map *vpix = vimc_pix_map_by_index(code->index);
|
|
|
|
if (!vpix)
|
|
return -EINVAL;
|
|
|
|
code->code = vpix->code;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vimc_sen_enum_frame_size(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_frame_size_enum *fse)
|
|
{
|
|
const struct vimc_pix_map *vpix;
|
|
|
|
if (fse->index)
|
|
return -EINVAL;
|
|
|
|
/* Only accept code in the pix map table */
|
|
vpix = vimc_pix_map_by_code(fse->code);
|
|
if (!vpix)
|
|
return -EINVAL;
|
|
|
|
fse->min_width = VIMC_FRAME_MIN_WIDTH;
|
|
fse->max_width = VIMC_FRAME_MAX_WIDTH;
|
|
fse->min_height = VIMC_FRAME_MIN_HEIGHT;
|
|
fse->max_height = VIMC_FRAME_MAX_HEIGHT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vimc_sen_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct vimc_sen_device *vsen =
|
|
container_of(sd, struct vimc_sen_device, sd);
|
|
|
|
fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ?
|
|
*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) :
|
|
vsen->mbus_format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vimc_sen_tpg_s_format(struct vimc_sen_device *vsen)
|
|
{
|
|
const struct vimc_pix_map *vpix =
|
|
vimc_pix_map_by_code(vsen->mbus_format.code);
|
|
|
|
tpg_reset_source(&vsen->tpg, vsen->mbus_format.width,
|
|
vsen->mbus_format.height, vsen->mbus_format.field);
|
|
tpg_s_bytesperline(&vsen->tpg, 0, vsen->mbus_format.width * vpix->bpp);
|
|
tpg_s_buf_height(&vsen->tpg, vsen->mbus_format.height);
|
|
tpg_s_fourcc(&vsen->tpg, vpix->pixelformat);
|
|
/* TODO: add support for V4L2_FIELD_ALTERNATE */
|
|
tpg_s_field(&vsen->tpg, vsen->mbus_format.field, false);
|
|
tpg_s_colorspace(&vsen->tpg, vsen->mbus_format.colorspace);
|
|
tpg_s_ycbcr_enc(&vsen->tpg, vsen->mbus_format.ycbcr_enc);
|
|
tpg_s_quantization(&vsen->tpg, vsen->mbus_format.quantization);
|
|
tpg_s_xfer_func(&vsen->tpg, vsen->mbus_format.xfer_func);
|
|
}
|
|
|
|
static void vimc_sen_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
|
|
{
|
|
const struct vimc_pix_map *vpix;
|
|
|
|
/* Only accept code in the pix map table */
|
|
vpix = vimc_pix_map_by_code(fmt->code);
|
|
if (!vpix)
|
|
fmt->code = fmt_default.code;
|
|
|
|
fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH,
|
|
VIMC_FRAME_MAX_WIDTH) & ~1;
|
|
fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT,
|
|
VIMC_FRAME_MAX_HEIGHT) & ~1;
|
|
|
|
/* TODO: add support for V4L2_FIELD_ALTERNATE */
|
|
if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
|
|
fmt->field = fmt_default.field;
|
|
|
|
vimc_colorimetry_clamp(fmt);
|
|
}
|
|
|
|
static int vimc_sen_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct vimc_sen_device *vsen = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *mf;
|
|
|
|
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
/* Do not change the format while stream is on */
|
|
if (vsen->frame)
|
|
return -EBUSY;
|
|
|
|
mf = &vsen->mbus_format;
|
|
} else {
|
|
mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
|
|
}
|
|
|
|
/* Set the new format */
|
|
vimc_sen_adjust_fmt(&fmt->format);
|
|
|
|
dev_dbg(vsen->dev, "%s: format update: "
|
|
"old:%dx%d (0x%x, %d, %d, %d, %d) "
|
|
"new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsen->sd.name,
|
|
/* old */
|
|
mf->width, mf->height, mf->code,
|
|
mf->colorspace, mf->quantization,
|
|
mf->xfer_func, mf->ycbcr_enc,
|
|
/* new */
|
|
fmt->format.width, fmt->format.height, fmt->format.code,
|
|
fmt->format.colorspace, fmt->format.quantization,
|
|
fmt->format.xfer_func, fmt->format.ycbcr_enc);
|
|
|
|
*mf = fmt->format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_pad_ops vimc_sen_pad_ops = {
|
|
.init_cfg = vimc_sen_init_cfg,
|
|
.enum_mbus_code = vimc_sen_enum_mbus_code,
|
|
.enum_frame_size = vimc_sen_enum_frame_size,
|
|
.get_fmt = vimc_sen_get_fmt,
|
|
.set_fmt = vimc_sen_set_fmt,
|
|
};
|
|
|
|
static void *vimc_sen_process_frame(struct vimc_ent_device *ved,
|
|
const void *sink_frame)
|
|
{
|
|
struct vimc_sen_device *vsen = container_of(ved, struct vimc_sen_device,
|
|
ved);
|
|
|
|
tpg_fill_plane_buffer(&vsen->tpg, 0, 0, vsen->frame);
|
|
return vsen->frame;
|
|
}
|
|
|
|
static int vimc_sen_s_stream(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
struct vimc_sen_device *vsen =
|
|
container_of(sd, struct vimc_sen_device, sd);
|
|
|
|
if (enable) {
|
|
const struct vimc_pix_map *vpix;
|
|
unsigned int frame_size;
|
|
|
|
if (vsen->kthread_sen)
|
|
/* tpg is already executing */
|
|
return 0;
|
|
|
|
/* Calculate the frame size */
|
|
vpix = vimc_pix_map_by_code(vsen->mbus_format.code);
|
|
frame_size = vsen->mbus_format.width * vpix->bpp *
|
|
vsen->mbus_format.height;
|
|
|
|
/*
|
|
* Allocate the frame buffer. Use vmalloc to be able to
|
|
* allocate a large amount of memory
|
|
*/
|
|
vsen->frame = vmalloc(frame_size);
|
|
if (!vsen->frame)
|
|
return -ENOMEM;
|
|
|
|
/* configure the test pattern generator */
|
|
vimc_sen_tpg_s_format(vsen);
|
|
|
|
} else {
|
|
|
|
vfree(vsen->frame);
|
|
vsen->frame = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_core_ops vimc_sen_core_ops = {
|
|
.log_status = v4l2_ctrl_subdev_log_status,
|
|
.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
|
|
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
|
|
};
|
|
|
|
static const struct v4l2_subdev_video_ops vimc_sen_video_ops = {
|
|
.s_stream = vimc_sen_s_stream,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops vimc_sen_ops = {
|
|
.core = &vimc_sen_core_ops,
|
|
.pad = &vimc_sen_pad_ops,
|
|
.video = &vimc_sen_video_ops,
|
|
};
|
|
|
|
static int vimc_sen_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct vimc_sen_device *vsen =
|
|
container_of(ctrl->handler, struct vimc_sen_device, hdl);
|
|
|
|
switch (ctrl->id) {
|
|
case VIMC_CID_TEST_PATTERN:
|
|
tpg_s_pattern(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_HFLIP:
|
|
tpg_s_hflip(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_VFLIP:
|
|
tpg_s_vflip(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_BRIGHTNESS:
|
|
tpg_s_brightness(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_CONTRAST:
|
|
tpg_s_contrast(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_HUE:
|
|
tpg_s_hue(&vsen->tpg, ctrl->val);
|
|
break;
|
|
case V4L2_CID_SATURATION:
|
|
tpg_s_saturation(&vsen->tpg, ctrl->val);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops vimc_sen_ctrl_ops = {
|
|
.s_ctrl = vimc_sen_s_ctrl,
|
|
};
|
|
|
|
static void vimc_sen_release(struct v4l2_subdev *sd)
|
|
{
|
|
struct vimc_sen_device *vsen =
|
|
container_of(sd, struct vimc_sen_device, sd);
|
|
|
|
v4l2_ctrl_handler_free(&vsen->hdl);
|
|
tpg_free(&vsen->tpg);
|
|
vimc_pads_cleanup(vsen->ved.pads);
|
|
kfree(vsen);
|
|
}
|
|
|
|
static const struct v4l2_subdev_internal_ops vimc_sen_int_ops = {
|
|
.release = vimc_sen_release,
|
|
};
|
|
|
|
void vimc_sen_rm(struct vimc_device *vimc, struct vimc_ent_device *ved)
|
|
{
|
|
struct vimc_sen_device *vsen;
|
|
|
|
vsen = container_of(ved, struct vimc_sen_device, ved);
|
|
vimc_ent_sd_unregister(ved, &vsen->sd);
|
|
}
|
|
|
|
/* Image Processing Controls */
|
|
static const struct v4l2_ctrl_config vimc_sen_ctrl_class = {
|
|
.flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY,
|
|
.id = VIMC_CID_VIMC_CLASS,
|
|
.name = "VIMC Controls",
|
|
.type = V4L2_CTRL_TYPE_CTRL_CLASS,
|
|
};
|
|
|
|
static const struct v4l2_ctrl_config vimc_sen_ctrl_test_pattern = {
|
|
.ops = &vimc_sen_ctrl_ops,
|
|
.id = VIMC_CID_TEST_PATTERN,
|
|
.name = "Test Pattern",
|
|
.type = V4L2_CTRL_TYPE_MENU,
|
|
.max = TPG_PAT_NOISE,
|
|
.qmenu = tpg_pattern_strings,
|
|
};
|
|
|
|
struct vimc_ent_device *vimc_sen_add(struct vimc_device *vimc,
|
|
const char *vcfg_name)
|
|
{
|
|
struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
|
|
struct vimc_sen_device *vsen;
|
|
int ret;
|
|
|
|
/* Allocate the vsen struct */
|
|
vsen = kzalloc(sizeof(*vsen), GFP_KERNEL);
|
|
if (!vsen)
|
|
return NULL;
|
|
|
|
v4l2_ctrl_handler_init(&vsen->hdl, 4);
|
|
|
|
v4l2_ctrl_new_custom(&vsen->hdl, &vimc_sen_ctrl_class, NULL);
|
|
v4l2_ctrl_new_custom(&vsen->hdl, &vimc_sen_ctrl_test_pattern, NULL);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_HFLIP, 0, 1, 1, 0);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_CONTRAST, 0, 255, 1, 128);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_HUE, -128, 127, 1, 0);
|
|
v4l2_ctrl_new_std(&vsen->hdl, &vimc_sen_ctrl_ops,
|
|
V4L2_CID_SATURATION, 0, 255, 1, 128);
|
|
vsen->sd.ctrl_handler = &vsen->hdl;
|
|
if (vsen->hdl.error) {
|
|
ret = vsen->hdl.error;
|
|
goto err_free_vsen;
|
|
}
|
|
|
|
/* Initialize ved and sd */
|
|
ret = vimc_ent_sd_register(&vsen->ved, &vsen->sd, v4l2_dev,
|
|
vcfg_name,
|
|
MEDIA_ENT_F_CAM_SENSOR, 1,
|
|
(const unsigned long[1]) {MEDIA_PAD_FL_SOURCE},
|
|
&vimc_sen_int_ops, &vimc_sen_ops);
|
|
if (ret)
|
|
goto err_free_hdl;
|
|
|
|
vsen->ved.process_frame = vimc_sen_process_frame;
|
|
vsen->dev = &vimc->pdev.dev;
|
|
|
|
/* Initialize the frame format */
|
|
vsen->mbus_format = fmt_default;
|
|
|
|
/* Initialize the test pattern generator */
|
|
tpg_init(&vsen->tpg, vsen->mbus_format.width,
|
|
vsen->mbus_format.height);
|
|
ret = tpg_alloc(&vsen->tpg, VIMC_FRAME_MAX_WIDTH);
|
|
if (ret)
|
|
goto err_unregister_ent_sd;
|
|
|
|
return &vsen->ved;
|
|
|
|
err_unregister_ent_sd:
|
|
vimc_ent_sd_unregister(&vsen->ved, &vsen->sd);
|
|
err_free_hdl:
|
|
v4l2_ctrl_handler_free(&vsen->hdl);
|
|
err_free_vsen:
|
|
kfree(vsen);
|
|
|
|
return NULL;
|
|
}
|