linux/drivers/media/v4l2-core/v4l2-subdev.c
Laurent Pinchart 24acf8b211 [media] v4l: subdev: Extend default link validation to cover field order
The field order must match between the source and sink pads, or the sink
pad field order must be NONE. This allows connecting an interlaced
source to a bridge that has no hardware support for interlaced formats.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Tested-by: Enrico Butera <ebutera@users.sourceforge.net>
Acked-by: Sakari Ailus <sakari.ailus@iki.fi>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
2014-08-21 15:25:12 -05:00

588 lines
14 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 <linux/export.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)
fh->pad = kzalloc(sizeof(*fh->pad) * sd->entity.num_pads, GFP_KERNEL);
if (fh->pad == NULL)
return -ENOMEM;
#endif
return 0;
}
static void subdev_fh_free(struct v4l2_subdev_fh *fh)
{
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
kfree(fh->pad);
fh->pad = 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;
}
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
static int check_format(struct v4l2_subdev *sd,
struct v4l2_subdev_format *format)
{
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 0;
}
static int check_crop(struct v4l2_subdev *sd, struct v4l2_subdev_crop *crop)
{
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 0;
}
static int check_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_selection *sel)
{
if (sel->which != V4L2_SUBDEV_FORMAT_TRY &&
sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
if (sel->pad >= sd->entity.num_pads)
return -EINVAL;
return 0;
}
static int check_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid)
{
if (edid->pad >= sd->entity.num_pads)
return -EINVAL;
if (edid->blocks && edid->edid == NULL)
return -EINVAL;
return 0;
}
#endif
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);
int rval;
#endif
switch (cmd) {
case VIDIOC_QUERYCTRL:
return v4l2_queryctrl(vfh->ctrl_handler, arg);
case VIDIOC_QUERY_EXT_CTRL:
return v4l2_query_ext_ctrl(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
case VIDIOC_LOG_STATUS: {
int ret;
pr_info("%s: ================= START STATUS =================\n",
sd->name);
ret = v4l2_subdev_call(sd, core, log_status);
pr_info("%s: ================== END STATUS ==================\n",
sd->name);
return ret;
}
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
case VIDIOC_SUBDEV_G_FMT: {
struct v4l2_subdev_format *format = arg;
rval = check_format(sd, format);
if (rval)
return rval;
return v4l2_subdev_call(sd, pad, get_fmt, subdev_fh, format);
}
case VIDIOC_SUBDEV_S_FMT: {
struct v4l2_subdev_format *format = arg;
rval = check_format(sd, format);
if (rval)
return rval;
return v4l2_subdev_call(sd, pad, set_fmt, subdev_fh, format);
}
case VIDIOC_SUBDEV_G_CROP: {
struct v4l2_subdev_crop *crop = arg;
struct v4l2_subdev_selection sel;
rval = check_crop(sd, crop);
if (rval)
return rval;
rval = v4l2_subdev_call(sd, pad, get_crop, subdev_fh, crop);
if (rval != -ENOIOCTLCMD)
return rval;
memset(&sel, 0, sizeof(sel));
sel.which = crop->which;
sel.pad = crop->pad;
sel.target = V4L2_SEL_TGT_CROP;
rval = v4l2_subdev_call(
sd, pad, get_selection, subdev_fh, &sel);
crop->rect = sel.r;
return rval;
}
case VIDIOC_SUBDEV_S_CROP: {
struct v4l2_subdev_crop *crop = arg;
struct v4l2_subdev_selection sel;
rval = check_crop(sd, crop);
if (rval)
return rval;
rval = v4l2_subdev_call(sd, pad, set_crop, subdev_fh, crop);
if (rval != -ENOIOCTLCMD)
return rval;
memset(&sel, 0, sizeof(sel));
sel.which = crop->which;
sel.pad = crop->pad;
sel.target = V4L2_SEL_TGT_CROP;
sel.r = crop->rect;
rval = v4l2_subdev_call(
sd, pad, set_selection, subdev_fh, &sel);
crop->rect = sel.r;
return rval;
}
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: {
struct v4l2_subdev_frame_interval *fi = arg;
if (fi->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, video, g_frame_interval, arg);
}
case VIDIOC_SUBDEV_S_FRAME_INTERVAL: {
struct v4l2_subdev_frame_interval *fi = arg;
if (fi->pad >= sd->entity.num_pads)
return -EINVAL;
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);
}
case VIDIOC_SUBDEV_G_SELECTION: {
struct v4l2_subdev_selection *sel = arg;
rval = check_selection(sd, sel);
if (rval)
return rval;
return v4l2_subdev_call(
sd, pad, get_selection, subdev_fh, sel);
}
case VIDIOC_SUBDEV_S_SELECTION: {
struct v4l2_subdev_selection *sel = arg;
rval = check_selection(sd, sel);
if (rval)
return rval;
return v4l2_subdev_call(
sd, pad, set_selection, subdev_fh, sel);
}
case VIDIOC_G_EDID: {
struct v4l2_subdev_edid *edid = arg;
rval = check_edid(sd, edid);
if (rval)
return rval;
return v4l2_subdev_call(sd, pad, get_edid, edid);
}
case VIDIOC_S_EDID: {
struct v4l2_subdev_edid *edid = arg;
rval = check_edid(sd, edid);
if (rval)
return rval;
return v4l2_subdev_call(sd, pad, set_edid, edid);
}
case VIDIOC_SUBDEV_DV_TIMINGS_CAP: {
struct v4l2_dv_timings_cap *cap = arg;
if (cap->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
}
case VIDIOC_SUBDEV_ENUM_DV_TIMINGS: {
struct v4l2_enum_dv_timings *dvt = arg;
if (dvt->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, pad, enum_dv_timings, dvt);
}
case VIDIOC_SUBDEV_QUERY_DV_TIMINGS:
return v4l2_subdev_call(sd, video, query_dv_timings, arg);
case VIDIOC_SUBDEV_G_DV_TIMINGS:
return v4l2_subdev_call(sd, video, g_dv_timings, arg);
case VIDIOC_SUBDEV_S_DV_TIMINGS:
return v4l2_subdev_call(sd, video, s_dv_timings, arg);
#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);
}
#ifdef CONFIG_COMPAT
static long subdev_compat_ioctl32(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct video_device *vdev = video_devdata(file);
struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
return v4l2_subdev_call(sd, core, compat_ioctl32, cmd, arg);
}
#endif
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,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = subdev_compat_ioctl32,
#endif
.release = subdev_close,
.poll = subdev_poll,
};
#ifdef CONFIG_MEDIA_CONTROLLER
int v4l2_subdev_link_validate_default(struct v4l2_subdev *sd,
struct media_link *link,
struct v4l2_subdev_format *source_fmt,
struct v4l2_subdev_format *sink_fmt)
{
/* 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 -EINVAL;
/* 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 -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate_default);
static int
v4l2_subdev_link_validate_get_format(struct media_pad *pad,
struct v4l2_subdev_format *fmt)
{
if (media_entity_type(pad->entity) == MEDIA_ENT_T_V4L2_SUBDEV) {
struct v4l2_subdev *sd =
media_entity_to_v4l2_subdev(pad->entity);
fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt->pad = pad->index;
return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt);
}
WARN(pad->entity->type != MEDIA_ENT_T_DEVNODE_V4L,
"Driver bug! Wrong media entity type 0x%08x, entity %s\n",
pad->entity->type, pad->entity->name);
return -EINVAL;
}
int v4l2_subdev_link_validate(struct media_link *link)
{
struct v4l2_subdev *sink;
struct v4l2_subdev_format sink_fmt, source_fmt;
int rval;
rval = v4l2_subdev_link_validate_get_format(
link->source, &source_fmt);
if (rval < 0)
return 0;
rval = v4l2_subdev_link_validate_get_format(
link->sink, &sink_fmt);
if (rval < 0)
return 0;
sink = media_entity_to_v4l2_subdev(link->sink->entity);
rval = v4l2_subdev_call(sink, pad, link_validate, link,
&source_fmt, &sink_fmt);
if (rval != -ENOIOCTLCMD)
return rval;
return v4l2_subdev_link_validate_default(
sink, link, &source_fmt, &sink_fmt);
}
EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate);
#endif /* CONFIG_MEDIA_CONTROLLER */
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);