forked from Minki/linux
69967a71ae
Ioctls on the subdevs node currently don't dispatch the register access debug driver callbacks. Add the dispatching with the same security checks are for non subdev video nodes (i.e. only capable(CAP_SYS_ADMIN may call the register access ioctls). Signed-off-by: Martin Hostettler <martin@neutronstar.dyndns.org> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
339 lines
8.2 KiB
C
339 lines
8.2 KiB
C
/*
|
|
* V4L2 sub-device
|
|
*
|
|
* Copyright (C) 2010 Nokia Corporation
|
|
*
|
|
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
* Sakari Ailus <sakari.ailus@iki.fi>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/ioctl.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-fh.h>
|
|
#include <media/v4l2-event.h>
|
|
|
|
static int subdev_fh_init(struct v4l2_subdev_fh *fh, struct v4l2_subdev *sd)
|
|
{
|
|
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
|
|
/* Allocate try format and crop in the same memory block */
|
|
fh->try_fmt = kzalloc((sizeof(*fh->try_fmt) + sizeof(*fh->try_crop))
|
|
* sd->entity.num_pads, GFP_KERNEL);
|
|
if (fh->try_fmt == NULL)
|
|
return -ENOMEM;
|
|
|
|
fh->try_crop = (struct v4l2_rect *)
|
|
(fh->try_fmt + sd->entity.num_pads);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void subdev_fh_free(struct v4l2_subdev_fh *fh)
|
|
{
|
|
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
|
|
kfree(fh->try_fmt);
|
|
fh->try_fmt = NULL;
|
|
fh->try_crop = NULL;
|
|
#endif
|
|
}
|
|
|
|
static int subdev_open(struct file *file)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
|
|
struct v4l2_subdev_fh *subdev_fh;
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
struct media_entity *entity = NULL;
|
|
#endif
|
|
int ret;
|
|
|
|
subdev_fh = kzalloc(sizeof(*subdev_fh), GFP_KERNEL);
|
|
if (subdev_fh == NULL)
|
|
return -ENOMEM;
|
|
|
|
ret = subdev_fh_init(subdev_fh, sd);
|
|
if (ret) {
|
|
kfree(subdev_fh);
|
|
return ret;
|
|
}
|
|
|
|
v4l2_fh_init(&subdev_fh->vfh, vdev);
|
|
v4l2_fh_add(&subdev_fh->vfh);
|
|
file->private_data = &subdev_fh->vfh;
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
if (sd->v4l2_dev->mdev) {
|
|
entity = media_entity_get(&sd->entity);
|
|
if (!entity) {
|
|
ret = -EBUSY;
|
|
goto err;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (sd->internal_ops && sd->internal_ops->open) {
|
|
ret = sd->internal_ops->open(sd, subdev_fh);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
if (entity)
|
|
media_entity_put(entity);
|
|
#endif
|
|
v4l2_fh_del(&subdev_fh->vfh);
|
|
v4l2_fh_exit(&subdev_fh->vfh);
|
|
subdev_fh_free(subdev_fh);
|
|
kfree(subdev_fh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int subdev_close(struct file *file)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
|
|
struct v4l2_fh *vfh = file->private_data;
|
|
struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh);
|
|
|
|
if (sd->internal_ops && sd->internal_ops->close)
|
|
sd->internal_ops->close(sd, subdev_fh);
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
if (sd->v4l2_dev->mdev)
|
|
media_entity_put(&sd->entity);
|
|
#endif
|
|
v4l2_fh_del(vfh);
|
|
v4l2_fh_exit(vfh);
|
|
subdev_fh_free(subdev_fh);
|
|
kfree(subdev_fh);
|
|
file->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
|
|
struct v4l2_fh *vfh = file->private_data;
|
|
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
|
|
struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh);
|
|
#endif
|
|
|
|
switch (cmd) {
|
|
case VIDIOC_QUERYCTRL:
|
|
return v4l2_queryctrl(vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_QUERYMENU:
|
|
return v4l2_querymenu(vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_G_CTRL:
|
|
return v4l2_g_ctrl(vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_S_CTRL:
|
|
return v4l2_s_ctrl(vfh, vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_G_EXT_CTRLS:
|
|
return v4l2_g_ext_ctrls(vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_S_EXT_CTRLS:
|
|
return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_TRY_EXT_CTRLS:
|
|
return v4l2_try_ext_ctrls(vfh->ctrl_handler, arg);
|
|
|
|
case VIDIOC_DQEVENT:
|
|
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS))
|
|
return -ENOIOCTLCMD;
|
|
|
|
return v4l2_event_dequeue(vfh, arg, file->f_flags & O_NONBLOCK);
|
|
|
|
case VIDIOC_SUBSCRIBE_EVENT:
|
|
return v4l2_subdev_call(sd, core, subscribe_event, vfh, arg);
|
|
|
|
case VIDIOC_UNSUBSCRIBE_EVENT:
|
|
return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg);
|
|
|
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
|
case VIDIOC_DBG_G_REGISTER:
|
|
{
|
|
struct v4l2_dbg_register *p = arg;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
return v4l2_subdev_call(sd, core, g_register, p);
|
|
}
|
|
case VIDIOC_DBG_S_REGISTER:
|
|
{
|
|
struct v4l2_dbg_register *p = arg;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
return v4l2_subdev_call(sd, core, s_register, p);
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
|
|
case VIDIOC_SUBDEV_G_FMT: {
|
|
struct v4l2_subdev_format *format = arg;
|
|
|
|
if (format->which != V4L2_SUBDEV_FORMAT_TRY &&
|
|
format->which != V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
return -EINVAL;
|
|
|
|
if (format->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, get_fmt, subdev_fh, format);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_S_FMT: {
|
|
struct v4l2_subdev_format *format = arg;
|
|
|
|
if (format->which != V4L2_SUBDEV_FORMAT_TRY &&
|
|
format->which != V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
return -EINVAL;
|
|
|
|
if (format->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, set_fmt, subdev_fh, format);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_G_CROP: {
|
|
struct v4l2_subdev_crop *crop = arg;
|
|
|
|
if (crop->which != V4L2_SUBDEV_FORMAT_TRY &&
|
|
crop->which != V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
return -EINVAL;
|
|
|
|
if (crop->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, get_crop, subdev_fh, crop);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_S_CROP: {
|
|
struct v4l2_subdev_crop *crop = arg;
|
|
|
|
if (crop->which != V4L2_SUBDEV_FORMAT_TRY &&
|
|
crop->which != V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
return -EINVAL;
|
|
|
|
if (crop->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, set_crop, subdev_fh, crop);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_ENUM_MBUS_CODE: {
|
|
struct v4l2_subdev_mbus_code_enum *code = arg;
|
|
|
|
if (code->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh,
|
|
code);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: {
|
|
struct v4l2_subdev_frame_size_enum *fse = arg;
|
|
|
|
if (fse->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh,
|
|
fse);
|
|
}
|
|
|
|
case VIDIOC_SUBDEV_G_FRAME_INTERVAL:
|
|
return v4l2_subdev_call(sd, video, g_frame_interval, arg);
|
|
|
|
case VIDIOC_SUBDEV_S_FRAME_INTERVAL:
|
|
return v4l2_subdev_call(sd, video, s_frame_interval, arg);
|
|
|
|
case VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: {
|
|
struct v4l2_subdev_frame_interval_enum *fie = arg;
|
|
|
|
if (fie->pad >= sd->entity.num_pads)
|
|
return -EINVAL;
|
|
|
|
return v4l2_subdev_call(sd, pad, enum_frame_interval, subdev_fh,
|
|
fie);
|
|
}
|
|
#endif
|
|
default:
|
|
return v4l2_subdev_call(sd, core, ioctl, cmd, arg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long subdev_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
return video_usercopy(file, cmd, arg, subdev_do_ioctl);
|
|
}
|
|
|
|
static unsigned int subdev_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
|
|
struct v4l2_fh *fh = file->private_data;
|
|
|
|
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS))
|
|
return POLLERR;
|
|
|
|
poll_wait(file, &fh->wait, wait);
|
|
|
|
if (v4l2_event_pending(fh))
|
|
return POLLPRI;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct v4l2_file_operations v4l2_subdev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = subdev_open,
|
|
.unlocked_ioctl = subdev_ioctl,
|
|
.release = subdev_close,
|
|
.poll = subdev_poll,
|
|
};
|
|
|
|
void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)
|
|
{
|
|
INIT_LIST_HEAD(&sd->list);
|
|
BUG_ON(!ops);
|
|
sd->ops = ops;
|
|
sd->v4l2_dev = NULL;
|
|
sd->flags = 0;
|
|
sd->name[0] = '\0';
|
|
sd->grp_id = 0;
|
|
sd->dev_priv = NULL;
|
|
sd->host_priv = NULL;
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
sd->entity.name = sd->name;
|
|
sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(v4l2_subdev_init);
|