mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 13:22:23 +00:00
f1e393de38
The driver had to decide how many events to allocate when the v4l2_fh struct was created. It was possible to add more events afterwards, but there was no way to ensure that you wouldn't miss important events if the event queue would fill up for that filehandle. In addition, once there were no more free events, any new events were simply dropped on the floor. For the control event in particular this made life very difficult since control status/value changes could just be missed if the number of allocated events and the speed at which the application read events was too low to keep up with the number of generated events. The application would have no idea what the latest state was for a control since it could have missed the latest control change. So this patch makes some major changes in how events are allocated. Instead of allocating events per-filehandle they are now allocated when subscribing an event. So for that particular event type N events (determined by the driver) are allocated. Those events are reserved for that particular event type. This ensures that you will not miss events for a particular type altogether. In addition, if there are N events in use and a new event is raised, then the oldest event is dropped and the new one is added. So the latest event is always available. This can be further improved by adding the ability to merge the state of two events together, ensuring that no data is lost at all. This will be added in the next patch. This also makes it possible to allow the user to determine the number of events that will be allocated. This is not implemented at the moment, but would be trivial. Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
320 lines
7.8 KiB
C
320 lines
7.8 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);
|
|
#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);
|