2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-29 15:43:59 +08:00
linux-next/drivers/media/v4l2-core/v4l2-subdev.c
Tomasz Figa 758d90e161 [media] v4l2-core: Use kvmalloc() for potentially big allocations
There are multiple places where arrays or otherwise variable sized
buffer are allocated through V4L2 core code, including things like
controls, memory pages, staging buffers for ioctls and so on. Such
allocations can potentially require an order > 0 allocation from the
page allocator, which is not guaranteed to be fulfilled and is likely to
fail on a system with severe memory fragmentation (e.g. a system with
very long uptime).

Since the memory being allocated is intended to be used by the CPU
exclusively, we can consider using vmalloc() as a fallback and this is
exactly what the recently merged kvmalloc() helpers do. A kmalloc() call
is still attempted, even for order > 0 allocations, but it is done
with __GFP_NORETRY and __GFP_NOWARN, with expectation of failing if
requested memory is not available instantly. Only then the vmalloc()
fallback is used. This should give us fast and more reliable allocations
even on systems with higher memory pressure and/or more fragmentation,
while still retaining the same performance level on systems not
suffering from such conditions.

While at it, replace explicit array size calculations on changed
allocations with kvmalloc_array().

Purposedly not touching videobuf1, as it is deprecated, has only few
users remaining and would rather be seen removed instead.

Signed-off-by: Tomasz Figa <tfiga@chromium.org>
Acked-by: Marek Szyprowski <m.szyprowski@samsung.com>
Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-06-20 09:11:48 -03:00

629 lines
15 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.
*/
#include <linux/ioctl.h>
#include <linux/mm.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)
if (sd->entity.num_pads) {
fh->pad = v4l2_subdev_alloc_pad_config(sd);
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)
v4l2_subdev_free_pad_config(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)
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->pad, 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->pad, 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;
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->pad, &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;
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->pad, &sel);
crop->rect = sel.r;
return rval;
}
case VIDIOC_SUBDEV_ENUM_MBUS_CODE: {
struct v4l2_subdev_mbus_code_enum *code = arg;
if (code->which != V4L2_SUBDEV_FORMAT_TRY &&
code->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
if (code->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh->pad,
code);
}
case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: {
struct v4l2_subdev_frame_size_enum *fse = arg;
if (fse->which != V4L2_SUBDEV_FORMAT_TRY &&
fse->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
if (fse->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh->pad,
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->which != V4L2_SUBDEV_FORMAT_TRY &&
fie->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
if (fie->pad >= sd->entity.num_pads)
return -EINVAL;
return v4l2_subdev_call(sd, pad, enum_frame_interval, subdev_fh->pad,
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->pad, 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->pad, 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 -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;
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 (is_media_entity_v4l2_subdev(pad->entity)) {
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->function != MEDIA_ENT_F_IO_V4L,
"Driver bug! Wrong media entity type 0x%08x, entity %s\n",
pad->entity->function, 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);
struct v4l2_subdev_pad_config *
v4l2_subdev_alloc_pad_config(struct v4l2_subdev *sd)
{
struct v4l2_subdev_pad_config *cfg;
int ret;
if (!sd->entity.num_pads)
return NULL;
cfg = kvmalloc_array(sd->entity.num_pads, sizeof(*cfg),
GFP_KERNEL | __GFP_ZERO);
if (!cfg)
return NULL;
ret = v4l2_subdev_call(sd, pad, init_cfg, cfg);
if (ret < 0 && ret != -ENOIOCTLCMD) {
kvfree(cfg);
return NULL;
}
return cfg;
}
EXPORT_SYMBOL_GPL(v4l2_subdev_alloc_pad_config);
void v4l2_subdev_free_pad_config(struct v4l2_subdev_pad_config *cfg)
{
kvfree(cfg);
}
EXPORT_SYMBOL_GPL(v4l2_subdev_free_pad_config);
#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.obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV;
sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
#endif
}
EXPORT_SYMBOL(v4l2_subdev_init);
void v4l2_subdev_notify_event(struct v4l2_subdev *sd,
const struct v4l2_event *ev)
{
v4l2_event_queue(sd->devnode, ev);
v4l2_subdev_notify(sd, V4L2_DEVICE_NOTIFY_EVENT, (void *)ev);
}
EXPORT_SYMBOL_GPL(v4l2_subdev_notify_event);