From: Hans Verkuil Date: Thu, 2 May 2019 13:42:31 +0000 (-0400) Subject: media: move drivers/media/media-* to drivers/media/mc/mc-* X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=c612e54fca55d9380c1378eaa623d74ed89b62db;p=openwrt%2Fstaging%2Fblogic.git media: move drivers/media/media-* to drivers/media/mc/mc-* It is really weird that the media controller sources are all top-level in drivers/media. It is a bit of a left-over from long ago when most media sources were all at the top-level. At some point we reorganized the directory structure, but the media-*.c sources where never moved to their own directory. So create a new mc directory and move all sources there. Also rename the prefix from media- to mc-. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index ce6782bc53ef..21cd9c02960b 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -89,39 +89,7 @@ config MEDIA_CEC_SUPPORT source "drivers/media/cec/Kconfig" -# -# Media controller -# Selectable only for webcam/grabbers, as other drivers don't use it -# - -config MEDIA_CONTROLLER - bool "Media Controller API" - depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT - help - Enable the media controller API used to query media devices internal - topology and configure it dynamically. - - This API is mostly used by camera interfaces in embedded platforms. - -config MEDIA_CONTROLLER_DVB - bool "Enable Media controller for DVB (EXPERIMENTAL)" - depends on MEDIA_CONTROLLER && DVB_CORE - help - Enable the media controller API support for DVB. - - This is currently experimental. - -config MEDIA_CONTROLLER_REQUEST_API - bool "Enable Media controller Request API (EXPERIMENTAL)" - depends on MEDIA_CONTROLLER && STAGING_MEDIA - help - DO NOT ENABLE THIS OPTION UNLESS YOU KNOW WHAT YOU'RE DOING. - - This option enables the Request API for the Media controller and V4L2 - interfaces. It is currently needed by a few stateless codec drivers. - - There is currently no intention to provide API or ABI stability for - this new API as of yet. +source "drivers/media/mc/Kconfig" # # Video4Linux support diff --git a/drivers/media/Makefile b/drivers/media/Makefile index 4a330d0e5e40..f215f0a89f9e 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -3,15 +3,6 @@ # Makefile for the kernel multimedia device drivers. # -media-objs := media-device.o media-devnode.o media-entity.o \ - media-request.o - -ifeq ($(CONFIG_MEDIA_CONTROLLER),y) - ifeq ($(CONFIG_USB),y) - media-objs += media-dev-allocator.o - endif -endif - # # I2C drivers should come before other drivers, otherwise they'll fail # when compiled as builtin drivers @@ -20,10 +11,10 @@ obj-y += i2c/ tuners/ obj-$(CONFIG_DVB_CORE) += dvb-frontends/ # -# Now, let's link-in the media core +# Now, let's link-in the media controller core # ifeq ($(CONFIG_MEDIA_CONTROLLER),y) - obj-$(CONFIG_MEDIA_SUPPORT) += media.o + obj-$(CONFIG_MEDIA_SUPPORT) += mc/ endif obj-$(CONFIG_VIDEO_DEV) += v4l2-core/ diff --git a/drivers/media/mc/Kconfig b/drivers/media/mc/Kconfig new file mode 100644 index 000000000000..3b9795cfcb36 --- /dev/null +++ b/drivers/media/mc/Kconfig @@ -0,0 +1,33 @@ +# +# Media controller +# Selectable only for webcam/grabbers, as other drivers don't use it +# + +config MEDIA_CONTROLLER + bool "Media Controller API" + depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT + help + Enable the media controller API used to query media devices internal + topology and configure it dynamically. + + This API is mostly used by camera interfaces in embedded platforms. + +config MEDIA_CONTROLLER_DVB + bool "Enable Media controller for DVB (EXPERIMENTAL)" + depends on MEDIA_CONTROLLER && DVB_CORE + help + Enable the media controller API support for DVB. + + This is currently experimental. + +config MEDIA_CONTROLLER_REQUEST_API + bool "Enable Media controller Request API (EXPERIMENTAL)" + depends on MEDIA_CONTROLLER && STAGING_MEDIA + help + DO NOT ENABLE THIS OPTION UNLESS YOU KNOW WHAT YOU'RE DOING. + + This option enables the Request API for the Media controller and V4L2 + interfaces. It is currently needed by a few stateless codec drivers. + + There is currently no intention to provide API or ABI stability for + this new API as of yet. diff --git a/drivers/media/mc/Makefile b/drivers/media/mc/Makefile new file mode 100644 index 000000000000..119037f0e686 --- /dev/null +++ b/drivers/media/mc/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +mc-objs := mc-device.o mc-devnode.o mc-entity.o \ + mc-request.o + +ifeq ($(CONFIG_USB),y) + mc-objs += mc-dev-allocator.o +endif + +obj-$(CONFIG_MEDIA_SUPPORT) += mc.o diff --git a/drivers/media/mc/mc-dev-allocator.c b/drivers/media/mc/mc-dev-allocator.c new file mode 100644 index 000000000000..ae17887dec59 --- /dev/null +++ b/drivers/media/mc/mc-dev-allocator.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * media-dev-allocator.c - Media Controller Device Allocator API + * + * Copyright (c) 2019 Shuah Khan + * + * Credits: Suggested by Laurent Pinchart + */ + +/* + * This file adds a global refcounted Media Controller Device Instance API. + * A system wide global media device list is managed and each media device + * includes a kref count. The last put on the media device releases the media + * device instance. + * + */ + +#include +#include +#include +#include + +#include +#include + +static LIST_HEAD(media_device_list); +static DEFINE_MUTEX(media_device_lock); + +struct media_device_instance { + struct media_device mdev; + struct module *owner; + struct list_head list; + struct kref refcount; +}; + +static inline struct media_device_instance * +to_media_device_instance(struct media_device *mdev) +{ + return container_of(mdev, struct media_device_instance, mdev); +} + +static void media_device_instance_release(struct kref *kref) +{ + struct media_device_instance *mdi = + container_of(kref, struct media_device_instance, refcount); + + dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__); + + mutex_lock(&media_device_lock); + + media_device_unregister(&mdi->mdev); + media_device_cleanup(&mdi->mdev); + + list_del(&mdi->list); + mutex_unlock(&media_device_lock); + + kfree(mdi); +} + +/* Callers should hold media_device_lock when calling this function */ +static struct media_device *__media_device_get(struct device *dev, + const char *module_name, + struct module *owner) +{ + struct media_device_instance *mdi; + + list_for_each_entry(mdi, &media_device_list, list) { + if (mdi->mdev.dev != dev) + continue; + + kref_get(&mdi->refcount); + + /* get module reference for the media_device owner */ + if (owner != mdi->owner && !try_module_get(mdi->owner)) + dev_err(dev, + "%s: module %s get owner reference error\n", + __func__, module_name); + else + dev_dbg(dev, "%s: module %s got owner reference\n", + __func__, module_name); + return &mdi->mdev; + } + + mdi = kzalloc(sizeof(*mdi), GFP_KERNEL); + if (!mdi) + return NULL; + + mdi->owner = owner; + kref_init(&mdi->refcount); + list_add_tail(&mdi->list, &media_device_list); + + dev_dbg(dev, "%s: Allocated media device for owner %s\n", + __func__, module_name); + return &mdi->mdev; +} + +struct media_device *media_device_usb_allocate(struct usb_device *udev, + const char *module_name, + struct module *owner) +{ + struct media_device *mdev; + + mutex_lock(&media_device_lock); + mdev = __media_device_get(&udev->dev, module_name, owner); + if (!mdev) { + mutex_unlock(&media_device_lock); + return ERR_PTR(-ENOMEM); + } + + /* check if media device is already initialized */ + if (!mdev->dev) + __media_device_usb_init(mdev, udev, udev->product, + module_name); + mutex_unlock(&media_device_lock); + return mdev; +} +EXPORT_SYMBOL_GPL(media_device_usb_allocate); + +void media_device_delete(struct media_device *mdev, const char *module_name, + struct module *owner) +{ + struct media_device_instance *mdi = to_media_device_instance(mdev); + + mutex_lock(&media_device_lock); + /* put module reference for the media_device owner */ + if (mdi->owner != owner) { + module_put(mdi->owner); + dev_dbg(mdi->mdev.dev, + "%s: module %s put owner module reference\n", + __func__, module_name); + } + mutex_unlock(&media_device_lock); + kref_put(&mdi->refcount, media_device_instance_release); +} +EXPORT_SYMBOL_GPL(media_device_delete); diff --git a/drivers/media/mc/mc-device.c b/drivers/media/mc/mc-device.c new file mode 100644 index 000000000000..6893843edada --- /dev/null +++ b/drivers/media/mc/mc-device.c @@ -0,0 +1,909 @@ +/* + * Media device + * + * Copyright (C) 2010 Nokia Corporation + * + * Contacts: Laurent Pinchart + * Sakari Ailus + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_MEDIA_CONTROLLER + +/* + * Legacy defines from linux/media.h. This is the only place we need this + * so we just define it here. The media.h header doesn't expose it to the + * kernel to prevent it from being used by drivers, but here (and only here!) + * we need it to handle the legacy behavior. + */ +#define MEDIA_ENT_SUBTYPE_MASK 0x0000ffff +#define MEDIA_ENT_T_DEVNODE_UNKNOWN (MEDIA_ENT_F_OLD_BASE | \ + MEDIA_ENT_SUBTYPE_MASK) + +/* ----------------------------------------------------------------------------- + * Userspace API + */ + +static inline void __user *media_get_uptr(__u64 arg) +{ + return (void __user *)(uintptr_t)arg; +} + +static int media_device_open(struct file *filp) +{ + return 0; +} + +static int media_device_close(struct file *filp) +{ + return 0; +} + +static long media_device_get_info(struct media_device *dev, void *arg) +{ + struct media_device_info *info = arg; + + memset(info, 0, sizeof(*info)); + + if (dev->driver_name[0]) + strscpy(info->driver, dev->driver_name, sizeof(info->driver)); + else + strscpy(info->driver, dev->dev->driver->name, + sizeof(info->driver)); + + strscpy(info->model, dev->model, sizeof(info->model)); + strscpy(info->serial, dev->serial, sizeof(info->serial)); + strscpy(info->bus_info, dev->bus_info, sizeof(info->bus_info)); + + info->media_version = LINUX_VERSION_CODE; + info->driver_version = info->media_version; + info->hw_revision = dev->hw_revision; + + return 0; +} + +static struct media_entity *find_entity(struct media_device *mdev, u32 id) +{ + struct media_entity *entity; + int next = id & MEDIA_ENT_ID_FLAG_NEXT; + + id &= ~MEDIA_ENT_ID_FLAG_NEXT; + + media_device_for_each_entity(entity, mdev) { + if (((media_entity_id(entity) == id) && !next) || + ((media_entity_id(entity) > id) && next)) { + return entity; + } + } + + return NULL; +} + +static long media_device_enum_entities(struct media_device *mdev, void *arg) +{ + struct media_entity_desc *entd = arg; + struct media_entity *ent; + + ent = find_entity(mdev, entd->id); + if (ent == NULL) + return -EINVAL; + + memset(entd, 0, sizeof(*entd)); + + entd->id = media_entity_id(ent); + if (ent->name) + strscpy(entd->name, ent->name, sizeof(entd->name)); + entd->type = ent->function; + entd->revision = 0; /* Unused */ + entd->flags = ent->flags; + entd->group_id = 0; /* Unused */ + entd->pads = ent->num_pads; + entd->links = ent->num_links - ent->num_backlinks; + + /* + * Workaround for a bug at media-ctl <= v1.10 that makes it to + * do the wrong thing if the entity function doesn't belong to + * either MEDIA_ENT_F_OLD_BASE or MEDIA_ENT_F_OLD_SUBDEV_BASE + * Ranges. + * + * Non-subdevices are expected to be at the MEDIA_ENT_F_OLD_BASE, + * or, otherwise, will be silently ignored by media-ctl when + * printing the graphviz diagram. So, map them into the devnode + * old range. + */ + if (ent->function < MEDIA_ENT_F_OLD_BASE || + ent->function > MEDIA_ENT_F_TUNER) { + if (is_media_entity_v4l2_subdev(ent)) + entd->type = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; + else if (ent->function != MEDIA_ENT_F_IO_V4L) + entd->type = MEDIA_ENT_T_DEVNODE_UNKNOWN; + } + + memcpy(&entd->raw, &ent->info, sizeof(ent->info)); + + return 0; +} + +static void media_device_kpad_to_upad(const struct media_pad *kpad, + struct media_pad_desc *upad) +{ + upad->entity = media_entity_id(kpad->entity); + upad->index = kpad->index; + upad->flags = kpad->flags; +} + +static long media_device_enum_links(struct media_device *mdev, void *arg) +{ + struct media_links_enum *links = arg; + struct media_entity *entity; + + entity = find_entity(mdev, links->entity); + if (entity == NULL) + return -EINVAL; + + if (links->pads) { + unsigned int p; + + for (p = 0; p < entity->num_pads; p++) { + struct media_pad_desc pad; + + memset(&pad, 0, sizeof(pad)); + media_device_kpad_to_upad(&entity->pads[p], &pad); + if (copy_to_user(&links->pads[p], &pad, sizeof(pad))) + return -EFAULT; + } + } + + if (links->links) { + struct media_link *link; + struct media_link_desc __user *ulink_desc = links->links; + + list_for_each_entry(link, &entity->links, list) { + struct media_link_desc klink_desc; + + /* Ignore backlinks. */ + if (link->source->entity != entity) + continue; + memset(&klink_desc, 0, sizeof(klink_desc)); + media_device_kpad_to_upad(link->source, + &klink_desc.source); + media_device_kpad_to_upad(link->sink, + &klink_desc.sink); + klink_desc.flags = link->flags; + if (copy_to_user(ulink_desc, &klink_desc, + sizeof(*ulink_desc))) + return -EFAULT; + ulink_desc++; + } + } + memset(links->reserved, 0, sizeof(links->reserved)); + + return 0; +} + +static long media_device_setup_link(struct media_device *mdev, void *arg) +{ + struct media_link_desc *linkd = arg; + struct media_link *link = NULL; + struct media_entity *source; + struct media_entity *sink; + + /* Find the source and sink entities and link. + */ + source = find_entity(mdev, linkd->source.entity); + sink = find_entity(mdev, linkd->sink.entity); + + if (source == NULL || sink == NULL) + return -EINVAL; + + if (linkd->source.index >= source->num_pads || + linkd->sink.index >= sink->num_pads) + return -EINVAL; + + link = media_entity_find_link(&source->pads[linkd->source.index], + &sink->pads[linkd->sink.index]); + if (link == NULL) + return -EINVAL; + + memset(linkd->reserved, 0, sizeof(linkd->reserved)); + + /* Setup the link on both entities. */ + return __media_entity_setup_link(link, linkd->flags); +} + +static long media_device_get_topology(struct media_device *mdev, void *arg) +{ + struct media_v2_topology *topo = arg; + struct media_entity *entity; + struct media_interface *intf; + struct media_pad *pad; + struct media_link *link; + struct media_v2_entity kentity, __user *uentity; + struct media_v2_interface kintf, __user *uintf; + struct media_v2_pad kpad, __user *upad; + struct media_v2_link klink, __user *ulink; + unsigned int i; + int ret = 0; + + topo->topology_version = mdev->topology_version; + + /* Get entities and number of entities */ + i = 0; + uentity = media_get_uptr(topo->ptr_entities); + media_device_for_each_entity(entity, mdev) { + i++; + if (ret || !uentity) + continue; + + if (i > topo->num_entities) { + ret = -ENOSPC; + continue; + } + + /* Copy fields to userspace struct if not error */ + memset(&kentity, 0, sizeof(kentity)); + kentity.id = entity->graph_obj.id; + kentity.function = entity->function; + kentity.flags = entity->flags; + strscpy(kentity.name, entity->name, + sizeof(kentity.name)); + + if (copy_to_user(uentity, &kentity, sizeof(kentity))) + ret = -EFAULT; + uentity++; + } + topo->num_entities = i; + topo->reserved1 = 0; + + /* Get interfaces and number of interfaces */ + i = 0; + uintf = media_get_uptr(topo->ptr_interfaces); + media_device_for_each_intf(intf, mdev) { + i++; + if (ret || !uintf) + continue; + + if (i > topo->num_interfaces) { + ret = -ENOSPC; + continue; + } + + memset(&kintf, 0, sizeof(kintf)); + + /* Copy intf fields to userspace struct */ + kintf.id = intf->graph_obj.id; + kintf.intf_type = intf->type; + kintf.flags = intf->flags; + + if (media_type(&intf->graph_obj) == MEDIA_GRAPH_INTF_DEVNODE) { + struct media_intf_devnode *devnode; + + devnode = intf_to_devnode(intf); + + kintf.devnode.major = devnode->major; + kintf.devnode.minor = devnode->minor; + } + + if (copy_to_user(uintf, &kintf, sizeof(kintf))) + ret = -EFAULT; + uintf++; + } + topo->num_interfaces = i; + topo->reserved2 = 0; + + /* Get pads and number of pads */ + i = 0; + upad = media_get_uptr(topo->ptr_pads); + media_device_for_each_pad(pad, mdev) { + i++; + if (ret || !upad) + continue; + + if (i > topo->num_pads) { + ret = -ENOSPC; + continue; + } + + memset(&kpad, 0, sizeof(kpad)); + + /* Copy pad fields to userspace struct */ + kpad.id = pad->graph_obj.id; + kpad.entity_id = pad->entity->graph_obj.id; + kpad.flags = pad->flags; + kpad.index = pad->index; + + if (copy_to_user(upad, &kpad, sizeof(kpad))) + ret = -EFAULT; + upad++; + } + topo->num_pads = i; + topo->reserved3 = 0; + + /* Get links and number of links */ + i = 0; + ulink = media_get_uptr(topo->ptr_links); + media_device_for_each_link(link, mdev) { + if (link->is_backlink) + continue; + + i++; + + if (ret || !ulink) + continue; + + if (i > topo->num_links) { + ret = -ENOSPC; + continue; + } + + memset(&klink, 0, sizeof(klink)); + + /* Copy link fields to userspace struct */ + klink.id = link->graph_obj.id; + klink.source_id = link->gobj0->id; + klink.sink_id = link->gobj1->id; + klink.flags = link->flags; + + if (copy_to_user(ulink, &klink, sizeof(klink))) + ret = -EFAULT; + ulink++; + } + topo->num_links = i; + topo->reserved4 = 0; + + return ret; +} + +static long media_device_request_alloc(struct media_device *mdev, + int *alloc_fd) +{ +#ifdef CONFIG_MEDIA_CONTROLLER_REQUEST_API + if (!mdev->ops || !mdev->ops->req_validate || !mdev->ops->req_queue) + return -ENOTTY; + + return media_request_alloc(mdev, alloc_fd); +#else + return -ENOTTY; +#endif +} + +static long copy_arg_from_user(void *karg, void __user *uarg, unsigned int cmd) +{ + if ((_IOC_DIR(cmd) & _IOC_WRITE) && + copy_from_user(karg, uarg, _IOC_SIZE(cmd))) + return -EFAULT; + + return 0; +} + +static long copy_arg_to_user(void __user *uarg, void *karg, unsigned int cmd) +{ + if ((_IOC_DIR(cmd) & _IOC_READ) && + copy_to_user(uarg, karg, _IOC_SIZE(cmd))) + return -EFAULT; + + return 0; +} + +/* Do acquire the graph mutex */ +#define MEDIA_IOC_FL_GRAPH_MUTEX BIT(0) + +#define MEDIA_IOC_ARG(__cmd, func, fl, from_user, to_user) \ + [_IOC_NR(MEDIA_IOC_##__cmd)] = { \ + .cmd = MEDIA_IOC_##__cmd, \ + .fn = (long (*)(struct media_device *, void *))func, \ + .flags = fl, \ + .arg_from_user = from_user, \ + .arg_to_user = to_user, \ + } + +#define MEDIA_IOC(__cmd, func, fl) \ + MEDIA_IOC_ARG(__cmd, func, fl, copy_arg_from_user, copy_arg_to_user) + +/* the table is indexed by _IOC_NR(cmd) */ +struct media_ioctl_info { + unsigned int cmd; + unsigned short flags; + long (*fn)(struct media_device *dev, void *arg); + long (*arg_from_user)(void *karg, void __user *uarg, unsigned int cmd); + long (*arg_to_user)(void __user *uarg, void *karg, unsigned int cmd); +}; + +static const struct media_ioctl_info ioctl_info[] = { + MEDIA_IOC(DEVICE_INFO, media_device_get_info, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(ENUM_ENTITIES, media_device_enum_entities, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(ENUM_LINKS, media_device_enum_links, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(SETUP_LINK, media_device_setup_link, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(G_TOPOLOGY, media_device_get_topology, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(REQUEST_ALLOC, media_device_request_alloc, 0), +}; + +static long media_device_ioctl(struct file *filp, unsigned int cmd, + unsigned long __arg) +{ + struct media_devnode *devnode = media_devnode_data(filp); + struct media_device *dev = devnode->media_dev; + const struct media_ioctl_info *info; + void __user *arg = (void __user *)__arg; + char __karg[256], *karg = __karg; + long ret; + + if (_IOC_NR(cmd) >= ARRAY_SIZE(ioctl_info) + || ioctl_info[_IOC_NR(cmd)].cmd != cmd) + return -ENOIOCTLCMD; + + info = &ioctl_info[_IOC_NR(cmd)]; + + if (_IOC_SIZE(info->cmd) > sizeof(__karg)) { + karg = kmalloc(_IOC_SIZE(info->cmd), GFP_KERNEL); + if (!karg) + return -ENOMEM; + } + + if (info->arg_from_user) { + ret = info->arg_from_user(karg, arg, cmd); + if (ret) + goto out_free; + } + + if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) + mutex_lock(&dev->graph_mutex); + + ret = info->fn(dev, karg); + + if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) + mutex_unlock(&dev->graph_mutex); + + if (!ret && info->arg_to_user) + ret = info->arg_to_user(arg, karg, cmd); + +out_free: + if (karg != __karg) + kfree(karg); + + return ret; +} + +#ifdef CONFIG_COMPAT + +struct media_links_enum32 { + __u32 entity; + compat_uptr_t pads; /* struct media_pad_desc * */ + compat_uptr_t links; /* struct media_link_desc * */ + __u32 reserved[4]; +}; + +static long media_device_enum_links32(struct media_device *mdev, + struct media_links_enum32 __user *ulinks) +{ + struct media_links_enum links; + compat_uptr_t pads_ptr, links_ptr; + int ret; + + memset(&links, 0, sizeof(links)); + + if (get_user(links.entity, &ulinks->entity) + || get_user(pads_ptr, &ulinks->pads) + || get_user(links_ptr, &ulinks->links)) + return -EFAULT; + + links.pads = compat_ptr(pads_ptr); + links.links = compat_ptr(links_ptr); + + ret = media_device_enum_links(mdev, &links); + if (ret) + return ret; + + memset(ulinks->reserved, 0, sizeof(ulinks->reserved)); + + return 0; +} + +#define MEDIA_IOC_ENUM_LINKS32 _IOWR('|', 0x02, struct media_links_enum32) + +static long media_device_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct media_devnode *devnode = media_devnode_data(filp); + struct media_device *dev = devnode->media_dev; + long ret; + + switch (cmd) { + case MEDIA_IOC_ENUM_LINKS32: + mutex_lock(&dev->graph_mutex); + ret = media_device_enum_links32(dev, + (struct media_links_enum32 __user *)arg); + mutex_unlock(&dev->graph_mutex); + break; + + default: + return media_device_ioctl(filp, cmd, arg); + } + + return ret; +} +#endif /* CONFIG_COMPAT */ + +static const struct media_file_operations media_device_fops = { + .owner = THIS_MODULE, + .open = media_device_open, + .ioctl = media_device_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = media_device_compat_ioctl, +#endif /* CONFIG_COMPAT */ + .release = media_device_close, +}; + +/* ----------------------------------------------------------------------------- + * sysfs + */ + +static ssize_t show_model(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct media_devnode *devnode = to_media_devnode(cd); + struct media_device *mdev = devnode->media_dev; + + return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model); +} + +static DEVICE_ATTR(model, S_IRUGO, show_model, NULL); + +/* ----------------------------------------------------------------------------- + * Registration/unregistration + */ + +static void media_device_release(struct media_devnode *devnode) +{ + dev_dbg(devnode->parent, "Media device released\n"); +} + +/** + * media_device_register_entity - Register an entity with a media device + * @mdev: The media device + * @entity: The entity + */ +int __must_check media_device_register_entity(struct media_device *mdev, + struct media_entity *entity) +{ + struct media_entity_notify *notify, *next; + unsigned int i; + int ret; + + if (entity->function == MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN || + entity->function == MEDIA_ENT_F_UNKNOWN) + dev_warn(mdev->dev, + "Entity type for entity %s was not initialized!\n", + entity->name); + + /* Warn if we apparently re-register an entity */ + WARN_ON(entity->graph_obj.mdev != NULL); + entity->graph_obj.mdev = mdev; + INIT_LIST_HEAD(&entity->links); + entity->num_links = 0; + entity->num_backlinks = 0; + + ret = ida_alloc_min(&mdev->entity_internal_idx, 1, GFP_KERNEL); + if (ret < 0) + return ret; + entity->internal_idx = ret; + + mutex_lock(&mdev->graph_mutex); + mdev->entity_internal_idx_max = + max(mdev->entity_internal_idx_max, entity->internal_idx); + + /* Initialize media_gobj embedded at the entity */ + media_gobj_create(mdev, MEDIA_GRAPH_ENTITY, &entity->graph_obj); + + /* Initialize objects at the pads */ + for (i = 0; i < entity->num_pads; i++) + media_gobj_create(mdev, MEDIA_GRAPH_PAD, + &entity->pads[i].graph_obj); + + /* invoke entity_notify callbacks */ + list_for_each_entry_safe(notify, next, &mdev->entity_notify, list) + notify->notify(entity, notify->notify_data); + + if (mdev->entity_internal_idx_max + >= mdev->pm_count_walk.ent_enum.idx_max) { + struct media_graph new = { .top = 0 }; + + /* + * Initialise the new graph walk before cleaning up + * the old one in order not to spoil the graph walk + * object of the media device if graph walk init fails. + */ + ret = media_graph_walk_init(&new, mdev); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + return ret; + } + media_graph_walk_cleanup(&mdev->pm_count_walk); + mdev->pm_count_walk = new; + } + mutex_unlock(&mdev->graph_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(media_device_register_entity); + +static void __media_device_unregister_entity(struct media_entity *entity) +{ + struct media_device *mdev = entity->graph_obj.mdev; + struct media_link *link, *tmp; + struct media_interface *intf; + unsigned int i; + + ida_free(&mdev->entity_internal_idx, entity->internal_idx); + + /* Remove all interface links pointing to this entity */ + list_for_each_entry(intf, &mdev->interfaces, graph_obj.list) { + list_for_each_entry_safe(link, tmp, &intf->links, list) { + if (link->entity == entity) + __media_remove_intf_link(link); + } + } + + /* Remove all data links that belong to this entity */ + __media_entity_remove_links(entity); + + /* Remove all pads that belong to this entity */ + for (i = 0; i < entity->num_pads; i++) + media_gobj_destroy(&entity->pads[i].graph_obj); + + /* Remove the entity */ + media_gobj_destroy(&entity->graph_obj); + + /* invoke entity_notify callbacks to handle entity removal?? */ + + entity->graph_obj.mdev = NULL; +} + +void media_device_unregister_entity(struct media_entity *entity) +{ + struct media_device *mdev = entity->graph_obj.mdev; + + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_device_unregister_entity(entity); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_device_unregister_entity); + +/** + * media_device_init() - initialize a media device + * @mdev: The media device + * + * The caller is responsible for initializing the media device before + * registration. The following fields must be set: + * + * - dev must point to the parent device + * - model must be filled with the device model name + */ +void media_device_init(struct media_device *mdev) +{ + INIT_LIST_HEAD(&mdev->entities); + INIT_LIST_HEAD(&mdev->interfaces); + INIT_LIST_HEAD(&mdev->pads); + INIT_LIST_HEAD(&mdev->links); + INIT_LIST_HEAD(&mdev->entity_notify); + + mutex_init(&mdev->req_queue_mutex); + mutex_init(&mdev->graph_mutex); + ida_init(&mdev->entity_internal_idx); + + atomic_set(&mdev->request_id, 0); + + dev_dbg(mdev->dev, "Media device initialized\n"); +} +EXPORT_SYMBOL_GPL(media_device_init); + +void media_device_cleanup(struct media_device *mdev) +{ + ida_destroy(&mdev->entity_internal_idx); + mdev->entity_internal_idx_max = 0; + media_graph_walk_cleanup(&mdev->pm_count_walk); + mutex_destroy(&mdev->graph_mutex); + mutex_destroy(&mdev->req_queue_mutex); +} +EXPORT_SYMBOL_GPL(media_device_cleanup); + +int __must_check __media_device_register(struct media_device *mdev, + struct module *owner) +{ + struct media_devnode *devnode; + int ret; + + devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); + if (!devnode) + return -ENOMEM; + + /* Register the device node. */ + mdev->devnode = devnode; + devnode->fops = &media_device_fops; + devnode->parent = mdev->dev; + devnode->release = media_device_release; + + /* Set version 0 to indicate user-space that the graph is static */ + mdev->topology_version = 0; + + ret = media_devnode_register(mdev, devnode, owner); + if (ret < 0) { + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; + return ret; + } + + ret = device_create_file(&devnode->dev, &dev_attr_model); + if (ret < 0) { + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; + media_devnode_unregister_prepare(devnode); + media_devnode_unregister(devnode); + return ret; + } + + dev_dbg(mdev->dev, "Media device registered\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(__media_device_register); + +int __must_check media_device_register_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr) +{ + mutex_lock(&mdev->graph_mutex); + list_add_tail(&nptr->list, &mdev->entity_notify); + mutex_unlock(&mdev->graph_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(media_device_register_entity_notify); + +/* + * Note: Should be called with mdev->lock held. + */ +static void __media_device_unregister_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr) +{ + list_del(&nptr->list); +} + +void media_device_unregister_entity_notify(struct media_device *mdev, + struct media_entity_notify *nptr) +{ + mutex_lock(&mdev->graph_mutex); + __media_device_unregister_entity_notify(mdev, nptr); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_device_unregister_entity_notify); + +void media_device_unregister(struct media_device *mdev) +{ + struct media_entity *entity; + struct media_entity *next; + struct media_interface *intf, *tmp_intf; + struct media_entity_notify *notify, *nextp; + + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + + /* Check if mdev was ever registered at all */ + if (!media_devnode_is_registered(mdev->devnode)) { + mutex_unlock(&mdev->graph_mutex); + return; + } + + /* Clear the devnode register bit to avoid races with media dev open */ + media_devnode_unregister_prepare(mdev->devnode); + + /* Remove all entities from the media device */ + list_for_each_entry_safe(entity, next, &mdev->entities, graph_obj.list) + __media_device_unregister_entity(entity); + + /* Remove all entity_notify callbacks from the media device */ + list_for_each_entry_safe(notify, nextp, &mdev->entity_notify, list) + __media_device_unregister_entity_notify(mdev, notify); + + /* Remove all interfaces from the media device */ + list_for_each_entry_safe(intf, tmp_intf, &mdev->interfaces, + graph_obj.list) { + /* + * Unlink the interface, but don't free it here; the + * module which created it is responsible for freeing + * it + */ + __media_remove_intf_links(intf); + media_gobj_destroy(&intf->graph_obj); + } + + mutex_unlock(&mdev->graph_mutex); + + dev_dbg(mdev->dev, "Media device unregistered\n"); + + device_remove_file(&mdev->devnode->dev, &dev_attr_model); + media_devnode_unregister(mdev->devnode); + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; +} +EXPORT_SYMBOL_GPL(media_device_unregister); + +#if IS_ENABLED(CONFIG_PCI) +void media_device_pci_init(struct media_device *mdev, + struct pci_dev *pci_dev, + const char *name) +{ + mdev->dev = &pci_dev->dev; + + if (name) + strscpy(mdev->model, name, sizeof(mdev->model)); + else + strscpy(mdev->model, pci_name(pci_dev), sizeof(mdev->model)); + + sprintf(mdev->bus_info, "PCI:%s", pci_name(pci_dev)); + + mdev->hw_revision = (pci_dev->subsystem_vendor << 16) + | pci_dev->subsystem_device; + + media_device_init(mdev); +} +EXPORT_SYMBOL_GPL(media_device_pci_init); +#endif + +#if IS_ENABLED(CONFIG_USB) +void __media_device_usb_init(struct media_device *mdev, + struct usb_device *udev, + const char *board_name, + const char *driver_name) +{ + mdev->dev = &udev->dev; + + if (driver_name) + strscpy(mdev->driver_name, driver_name, + sizeof(mdev->driver_name)); + + if (board_name) + strscpy(mdev->model, board_name, sizeof(mdev->model)); + else if (udev->product) + strscpy(mdev->model, udev->product, sizeof(mdev->model)); + else + strscpy(mdev->model, "unknown model", sizeof(mdev->model)); + if (udev->serial) + strscpy(mdev->serial, udev->serial, sizeof(mdev->serial)); + usb_make_path(udev, mdev->bus_info, sizeof(mdev->bus_info)); + mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); + + media_device_init(mdev); +} +EXPORT_SYMBOL_GPL(__media_device_usb_init); +#endif + + +#endif /* CONFIG_MEDIA_CONTROLLER */ diff --git a/drivers/media/mc/mc-devnode.c b/drivers/media/mc/mc-devnode.c new file mode 100644 index 000000000000..d5aa30eeff4a --- /dev/null +++ b/drivers/media/mc/mc-devnode.c @@ -0,0 +1,336 @@ +/* + * Media device node + * + * Copyright (C) 2010 Nokia Corporation + * + * Based on drivers/media/video/v4l2_dev.c code authored by + * Mauro Carvalho Chehab (version 2) + * Alan Cox, (version 1) + * + * Contacts: Laurent Pinchart + * Sakari Ailus + * + * 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. + * + * -- + * + * Generic media device node infrastructure to register and unregister + * character devices using a dynamic major number and proper reference + * counting. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MEDIA_NUM_DEVICES 256 +#define MEDIA_NAME "media" + +static dev_t media_dev_t; + +/* + * Active devices + */ +static DEFINE_MUTEX(media_devnode_lock); +static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES); + +/* Called when the last user of the media device exits. */ +static void media_devnode_release(struct device *cd) +{ + struct media_devnode *devnode = to_media_devnode(cd); + + mutex_lock(&media_devnode_lock); + /* Mark device node number as free */ + clear_bit(devnode->minor, media_devnode_nums); + mutex_unlock(&media_devnode_lock); + + /* Release media_devnode and perform other cleanups as needed. */ + if (devnode->release) + devnode->release(devnode); + + kfree(devnode); + pr_debug("%s: Media Devnode Deallocated\n", __func__); +} + +static struct bus_type media_bus_type = { + .name = MEDIA_NAME, +}; + +static ssize_t media_read(struct file *filp, char __user *buf, + size_t sz, loff_t *off) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + if (!devnode->fops->read) + return -EINVAL; + if (!media_devnode_is_registered(devnode)) + return -EIO; + return devnode->fops->read(filp, buf, sz, off); +} + +static ssize_t media_write(struct file *filp, const char __user *buf, + size_t sz, loff_t *off) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + if (!devnode->fops->write) + return -EINVAL; + if (!media_devnode_is_registered(devnode)) + return -EIO; + return devnode->fops->write(filp, buf, sz, off); +} + +static __poll_t media_poll(struct file *filp, + struct poll_table_struct *poll) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + if (!media_devnode_is_registered(devnode)) + return EPOLLERR | EPOLLHUP; + if (!devnode->fops->poll) + return DEFAULT_POLLMASK; + return devnode->fops->poll(filp, poll); +} + +static long +__media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg, + long (*ioctl_func)(struct file *filp, unsigned int cmd, + unsigned long arg)) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + if (!ioctl_func) + return -ENOTTY; + + if (!media_devnode_is_registered(devnode)) + return -EIO; + + return ioctl_func(filp, cmd, arg); +} + +static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl); +} + +#ifdef CONFIG_COMPAT + +static long media_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl); +} + +#endif /* CONFIG_COMPAT */ + +/* Override for the open function */ +static int media_open(struct inode *inode, struct file *filp) +{ + struct media_devnode *devnode; + int ret; + + /* Check if the media device is available. This needs to be done with + * the media_devnode_lock held to prevent an open/unregister race: + * without the lock, the device could be unregistered and freed between + * the media_devnode_is_registered() and get_device() calls, leading to + * a crash. + */ + mutex_lock(&media_devnode_lock); + devnode = container_of(inode->i_cdev, struct media_devnode, cdev); + /* return ENXIO if the media device has been removed + already or if it is not registered anymore. */ + if (!media_devnode_is_registered(devnode)) { + mutex_unlock(&media_devnode_lock); + return -ENXIO; + } + /* and increase the device refcount */ + get_device(&devnode->dev); + mutex_unlock(&media_devnode_lock); + + filp->private_data = devnode; + + if (devnode->fops->open) { + ret = devnode->fops->open(filp); + if (ret) { + put_device(&devnode->dev); + filp->private_data = NULL; + return ret; + } + } + + return 0; +} + +/* Override for the release function */ +static int media_release(struct inode *inode, struct file *filp) +{ + struct media_devnode *devnode = media_devnode_data(filp); + + if (devnode->fops->release) + devnode->fops->release(filp); + + filp->private_data = NULL; + + /* decrease the refcount unconditionally since the release() + return value is ignored. */ + put_device(&devnode->dev); + + pr_debug("%s: Media Release\n", __func__); + return 0; +} + +static const struct file_operations media_devnode_fops = { + .owner = THIS_MODULE, + .read = media_read, + .write = media_write, + .open = media_open, + .unlocked_ioctl = media_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = media_compat_ioctl, +#endif /* CONFIG_COMPAT */ + .release = media_release, + .poll = media_poll, + .llseek = no_llseek, +}; + +int __must_check media_devnode_register(struct media_device *mdev, + struct media_devnode *devnode, + struct module *owner) +{ + int minor; + int ret; + + /* Part 1: Find a free minor number */ + mutex_lock(&media_devnode_lock); + minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0); + if (minor == MEDIA_NUM_DEVICES) { + mutex_unlock(&media_devnode_lock); + pr_err("could not get a free minor\n"); + kfree(devnode); + return -ENFILE; + } + + set_bit(minor, media_devnode_nums); + mutex_unlock(&media_devnode_lock); + + devnode->minor = minor; + devnode->media_dev = mdev; + + /* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */ + devnode->dev.bus = &media_bus_type; + devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor); + devnode->dev.release = media_devnode_release; + if (devnode->parent) + devnode->dev.parent = devnode->parent; + dev_set_name(&devnode->dev, "media%d", devnode->minor); + device_initialize(&devnode->dev); + + /* Part 2: Initialize the character device */ + cdev_init(&devnode->cdev, &media_devnode_fops); + devnode->cdev.owner = owner; + kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor); + + /* Part 3: Add the media and char device */ + ret = cdev_device_add(&devnode->cdev, &devnode->dev); + if (ret < 0) { + pr_err("%s: cdev_device_add failed\n", __func__); + goto cdev_add_error; + } + + /* Part 4: Activate this minor. The char device can now be used. */ + set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); + + return 0; + +cdev_add_error: + mutex_lock(&media_devnode_lock); + clear_bit(devnode->minor, media_devnode_nums); + devnode->media_dev = NULL; + mutex_unlock(&media_devnode_lock); + + put_device(&devnode->dev); + return ret; +} + +void media_devnode_unregister_prepare(struct media_devnode *devnode) +{ + /* Check if devnode was ever registered at all */ + if (!media_devnode_is_registered(devnode)) + return; + + mutex_lock(&media_devnode_lock); + clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); + mutex_unlock(&media_devnode_lock); +} + +void media_devnode_unregister(struct media_devnode *devnode) +{ + mutex_lock(&media_devnode_lock); + /* Delete the cdev on this minor as well */ + cdev_device_del(&devnode->cdev, &devnode->dev); + devnode->media_dev = NULL; + mutex_unlock(&media_devnode_lock); + + put_device(&devnode->dev); +} + +/* + * Initialise media for linux + */ +static int __init media_devnode_init(void) +{ + int ret; + + pr_info("Linux media interface: v0.10\n"); + ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES, + MEDIA_NAME); + if (ret < 0) { + pr_warn("unable to allocate major\n"); + return ret; + } + + ret = bus_register(&media_bus_type); + if (ret < 0) { + unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); + pr_warn("bus_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit media_devnode_exit(void) +{ + bus_unregister(&media_bus_type); + unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); +} + +subsys_initcall(media_devnode_init); +module_exit(media_devnode_exit) + +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Device node registration for media drivers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c new file mode 100644 index 000000000000..a998a2e0ea1d --- /dev/null +++ b/drivers/media/mc/mc-entity.c @@ -0,0 +1,1036 @@ +/* + * Media entity + * + * Copyright (C) 2010 Nokia Corporation + * + * Contacts: Laurent Pinchart + * Sakari Ailus + * + * 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 +#include +#include +#include +#include + +static inline const char *gobj_type(enum media_gobj_type type) +{ + switch (type) { + case MEDIA_GRAPH_ENTITY: + return "entity"; + case MEDIA_GRAPH_PAD: + return "pad"; + case MEDIA_GRAPH_LINK: + return "link"; + case MEDIA_GRAPH_INTF_DEVNODE: + return "intf-devnode"; + default: + return "unknown"; + } +} + +static inline const char *intf_type(struct media_interface *intf) +{ + switch (intf->type) { + case MEDIA_INTF_T_DVB_FE: + return "dvb-frontend"; + case MEDIA_INTF_T_DVB_DEMUX: + return "dvb-demux"; + case MEDIA_INTF_T_DVB_DVR: + return "dvb-dvr"; + case MEDIA_INTF_T_DVB_CA: + return "dvb-ca"; + case MEDIA_INTF_T_DVB_NET: + return "dvb-net"; + case MEDIA_INTF_T_V4L_VIDEO: + return "v4l-video"; + case MEDIA_INTF_T_V4L_VBI: + return "v4l-vbi"; + case MEDIA_INTF_T_V4L_RADIO: + return "v4l-radio"; + case MEDIA_INTF_T_V4L_SUBDEV: + return "v4l-subdev"; + case MEDIA_INTF_T_V4L_SWRADIO: + return "v4l-swradio"; + case MEDIA_INTF_T_V4L_TOUCH: + return "v4l-touch"; + default: + return "unknown-intf"; + } +}; + +__must_check int __media_entity_enum_init(struct media_entity_enum *ent_enum, + int idx_max) +{ + idx_max = ALIGN(idx_max, BITS_PER_LONG); + ent_enum->bmap = kcalloc(idx_max / BITS_PER_LONG, sizeof(long), + GFP_KERNEL); + if (!ent_enum->bmap) + return -ENOMEM; + + bitmap_zero(ent_enum->bmap, idx_max); + ent_enum->idx_max = idx_max; + + return 0; +} +EXPORT_SYMBOL_GPL(__media_entity_enum_init); + +void media_entity_enum_cleanup(struct media_entity_enum *ent_enum) +{ + kfree(ent_enum->bmap); +} +EXPORT_SYMBOL_GPL(media_entity_enum_cleanup); + +/** + * dev_dbg_obj - Prints in debug mode a change on some object + * + * @event_name: Name of the event to report. Could be __func__ + * @gobj: Pointer to the object + * + * Enabled only if DEBUG or CONFIG_DYNAMIC_DEBUG. Otherwise, it + * won't produce any code. + */ +static void dev_dbg_obj(const char *event_name, struct media_gobj *gobj) +{ +#if defined(DEBUG) || defined (CONFIG_DYNAMIC_DEBUG) + switch (media_type(gobj)) { + case MEDIA_GRAPH_ENTITY: + dev_dbg(gobj->mdev->dev, + "%s id %u: entity '%s'\n", + event_name, media_id(gobj), + gobj_to_entity(gobj)->name); + break; + case MEDIA_GRAPH_LINK: + { + struct media_link *link = gobj_to_link(gobj); + + dev_dbg(gobj->mdev->dev, + "%s id %u: %s link id %u ==> id %u\n", + event_name, media_id(gobj), + media_type(link->gobj0) == MEDIA_GRAPH_PAD ? + "data" : "interface", + media_id(link->gobj0), + media_id(link->gobj1)); + break; + } + case MEDIA_GRAPH_PAD: + { + struct media_pad *pad = gobj_to_pad(gobj); + + dev_dbg(gobj->mdev->dev, + "%s id %u: %s%spad '%s':%d\n", + event_name, media_id(gobj), + pad->flags & MEDIA_PAD_FL_SINK ? "sink " : "", + pad->flags & MEDIA_PAD_FL_SOURCE ? "source " : "", + pad->entity->name, pad->index); + break; + } + case MEDIA_GRAPH_INTF_DEVNODE: + { + struct media_interface *intf = gobj_to_intf(gobj); + struct media_intf_devnode *devnode = intf_to_devnode(intf); + + dev_dbg(gobj->mdev->dev, + "%s id %u: intf_devnode %s - major: %d, minor: %d\n", + event_name, media_id(gobj), + intf_type(intf), + devnode->major, devnode->minor); + break; + } + } +#endif +} + +void media_gobj_create(struct media_device *mdev, + enum media_gobj_type type, + struct media_gobj *gobj) +{ + BUG_ON(!mdev); + + gobj->mdev = mdev; + + /* Create a per-type unique object ID */ + gobj->id = media_gobj_gen_id(type, ++mdev->id); + + switch (type) { + case MEDIA_GRAPH_ENTITY: + list_add_tail(&gobj->list, &mdev->entities); + break; + case MEDIA_GRAPH_PAD: + list_add_tail(&gobj->list, &mdev->pads); + break; + case MEDIA_GRAPH_LINK: + list_add_tail(&gobj->list, &mdev->links); + break; + case MEDIA_GRAPH_INTF_DEVNODE: + list_add_tail(&gobj->list, &mdev->interfaces); + break; + } + + mdev->topology_version++; + + dev_dbg_obj(__func__, gobj); +} + +void media_gobj_destroy(struct media_gobj *gobj) +{ + /* Do nothing if the object is not linked. */ + if (gobj->mdev == NULL) + return; + + dev_dbg_obj(__func__, gobj); + + gobj->mdev->topology_version++; + + /* Remove the object from mdev list */ + list_del(&gobj->list); + + gobj->mdev = NULL; +} + +/* + * TODO: Get rid of this. + */ +#define MEDIA_ENTITY_MAX_PADS 512 + +int media_entity_pads_init(struct media_entity *entity, u16 num_pads, + struct media_pad *pads) +{ + struct media_device *mdev = entity->graph_obj.mdev; + unsigned int i; + + if (num_pads >= MEDIA_ENTITY_MAX_PADS) + return -E2BIG; + + entity->num_pads = num_pads; + entity->pads = pads; + + if (mdev) + mutex_lock(&mdev->graph_mutex); + + for (i = 0; i < num_pads; i++) { + pads[i].entity = entity; + pads[i].index = i; + if (mdev) + media_gobj_create(mdev, MEDIA_GRAPH_PAD, + &entity->pads[i].graph_obj); + } + + if (mdev) + mutex_unlock(&mdev->graph_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(media_entity_pads_init); + +/* ----------------------------------------------------------------------------- + * Graph traversal + */ + +static struct media_entity * +media_entity_other(struct media_entity *entity, struct media_link *link) +{ + if (link->source->entity == entity) + return link->sink->entity; + else + return link->source->entity; +} + +/* push an entity to traversal stack */ +static void stack_push(struct media_graph *graph, + struct media_entity *entity) +{ + if (graph->top == MEDIA_ENTITY_ENUM_MAX_DEPTH - 1) { + WARN_ON(1); + return; + } + graph->top++; + graph->stack[graph->top].link = entity->links.next; + graph->stack[graph->top].entity = entity; +} + +static struct media_entity *stack_pop(struct media_graph *graph) +{ + struct media_entity *entity; + + entity = graph->stack[graph->top].entity; + graph->top--; + + return entity; +} + +#define link_top(en) ((en)->stack[(en)->top].link) +#define stack_top(en) ((en)->stack[(en)->top].entity) + +/** + * media_graph_walk_init - Allocate resources for graph walk + * @graph: Media graph structure that will be used to walk the graph + * @mdev: Media device + * + * Reserve resources for graph walk in media device's current + * state. The memory must be released using + * media_graph_walk_free(). + * + * Returns error on failure, zero on success. + */ +__must_check int media_graph_walk_init( + struct media_graph *graph, struct media_device *mdev) +{ + return media_entity_enum_init(&graph->ent_enum, mdev); +} +EXPORT_SYMBOL_GPL(media_graph_walk_init); + +/** + * media_graph_walk_cleanup - Release resources related to graph walking + * @graph: Media graph structure that was used to walk the graph + */ +void media_graph_walk_cleanup(struct media_graph *graph) +{ + media_entity_enum_cleanup(&graph->ent_enum); +} +EXPORT_SYMBOL_GPL(media_graph_walk_cleanup); + +void media_graph_walk_start(struct media_graph *graph, + struct media_entity *entity) +{ + media_entity_enum_zero(&graph->ent_enum); + media_entity_enum_set(&graph->ent_enum, entity); + + graph->top = 0; + graph->stack[graph->top].entity = NULL; + stack_push(graph, entity); + dev_dbg(entity->graph_obj.mdev->dev, + "begin graph walk at '%s'\n", entity->name); +} +EXPORT_SYMBOL_GPL(media_graph_walk_start); + +static void media_graph_walk_iter(struct media_graph *graph) +{ + struct media_entity *entity = stack_top(graph); + struct media_link *link; + struct media_entity *next; + + link = list_entry(link_top(graph), typeof(*link), list); + + /* The link is not enabled so we do not follow. */ + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { + link_top(graph) = link_top(graph)->next; + dev_dbg(entity->graph_obj.mdev->dev, + "walk: skipping disabled link '%s':%u -> '%s':%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + return; + } + + /* Get the entity in the other end of the link . */ + next = media_entity_other(entity, link); + + /* Has the entity already been visited? */ + if (media_entity_enum_test_and_set(&graph->ent_enum, next)) { + link_top(graph) = link_top(graph)->next; + dev_dbg(entity->graph_obj.mdev->dev, + "walk: skipping entity '%s' (already seen)\n", + next->name); + return; + } + + /* Push the new entity to stack and start over. */ + link_top(graph) = link_top(graph)->next; + stack_push(graph, next); + dev_dbg(entity->graph_obj.mdev->dev, "walk: pushing '%s' on stack\n", + next->name); +} + +struct media_entity *media_graph_walk_next(struct media_graph *graph) +{ + struct media_entity *entity; + + if (stack_top(graph) == NULL) + return NULL; + + /* + * Depth first search. Push entity to stack and continue from + * top of the stack until no more entities on the level can be + * found. + */ + while (link_top(graph) != &stack_top(graph)->links) + media_graph_walk_iter(graph); + + entity = stack_pop(graph); + dev_dbg(entity->graph_obj.mdev->dev, + "walk: returning entity '%s'\n", entity->name); + + return entity; +} +EXPORT_SYMBOL_GPL(media_graph_walk_next); + +int media_entity_get_fwnode_pad(struct media_entity *entity, + struct fwnode_handle *fwnode, + unsigned long direction_flags) +{ + struct fwnode_endpoint endpoint; + unsigned int i; + int ret; + + if (!entity->ops || !entity->ops->get_fwnode_pad) { + for (i = 0; i < entity->num_pads; i++) { + if (entity->pads[i].flags & direction_flags) + return i; + } + + return -ENXIO; + } + + ret = fwnode_graph_parse_endpoint(fwnode, &endpoint); + if (ret) + return ret; + + ret = entity->ops->get_fwnode_pad(&endpoint); + if (ret < 0) + return ret; + + if (ret >= entity->num_pads) + return -ENXIO; + + if (!(entity->pads[ret].flags & direction_flags)) + return -ENXIO; + + return ret; +} +EXPORT_SYMBOL_GPL(media_entity_get_fwnode_pad); + +/* ----------------------------------------------------------------------------- + * Pipeline management + */ + +__must_check int __media_pipeline_start(struct media_entity *entity, + struct media_pipeline *pipe) +{ + struct media_device *mdev = entity->graph_obj.mdev; + struct media_graph *graph = &pipe->graph; + struct media_entity *entity_err = entity; + struct media_link *link; + int ret; + + if (!pipe->streaming_count++) { + ret = media_graph_walk_init(&pipe->graph, mdev); + if (ret) + goto error_graph_walk_start; + } + + media_graph_walk_start(&pipe->graph, entity); + + while ((entity = media_graph_walk_next(graph))) { + DECLARE_BITMAP(active, MEDIA_ENTITY_MAX_PADS); + DECLARE_BITMAP(has_no_links, MEDIA_ENTITY_MAX_PADS); + + entity->stream_count++; + + if (entity->pipe && entity->pipe != pipe) { + pr_err("Pipe active for %s. Can't start for %s\n", + entity->name, + entity_err->name); + ret = -EBUSY; + goto error; + } + + entity->pipe = pipe; + + /* Already streaming --- no need to check. */ + if (entity->stream_count > 1) + continue; + + if (!entity->ops || !entity->ops->link_validate) + continue; + + bitmap_zero(active, entity->num_pads); + bitmap_fill(has_no_links, entity->num_pads); + + list_for_each_entry(link, &entity->links, list) { + struct media_pad *pad = link->sink->entity == entity + ? link->sink : link->source; + + /* Mark that a pad is connected by a link. */ + bitmap_clear(has_no_links, pad->index, 1); + + /* + * Pads that either do not need to connect or + * are connected through an enabled link are + * fine. + */ + if (!(pad->flags & MEDIA_PAD_FL_MUST_CONNECT) || + link->flags & MEDIA_LNK_FL_ENABLED) + bitmap_set(active, pad->index, 1); + + /* + * Link validation will only take place for + * sink ends of the link that are enabled. + */ + if (link->sink != pad || + !(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + ret = entity->ops->link_validate(link); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(entity->graph_obj.mdev->dev, + "link validation failed for '%s':%u -> '%s':%u, error %d\n", + link->source->entity->name, + link->source->index, + entity->name, link->sink->index, ret); + goto error; + } + } + + /* Either no links or validated links are fine. */ + bitmap_or(active, active, has_no_links, entity->num_pads); + + if (!bitmap_full(active, entity->num_pads)) { + ret = -ENOLINK; + dev_dbg(entity->graph_obj.mdev->dev, + "'%s':%u must be connected by an enabled link\n", + entity->name, + (unsigned)find_first_zero_bit( + active, entity->num_pads)); + goto error; + } + } + + return 0; + +error: + /* + * Link validation on graph failed. We revert what we did and + * return the error. + */ + media_graph_walk_start(graph, entity_err); + + while ((entity_err = media_graph_walk_next(graph))) { + /* Sanity check for negative stream_count */ + if (!WARN_ON_ONCE(entity_err->stream_count <= 0)) { + entity_err->stream_count--; + if (entity_err->stream_count == 0) + entity_err->pipe = NULL; + } + + /* + * We haven't increased stream_count further than this + * so we quit here. + */ + if (entity_err == entity) + break; + } + +error_graph_walk_start: + if (!--pipe->streaming_count) + media_graph_walk_cleanup(graph); + + return ret; +} +EXPORT_SYMBOL_GPL(__media_pipeline_start); + +__must_check int media_pipeline_start(struct media_entity *entity, + struct media_pipeline *pipe) +{ + struct media_device *mdev = entity->graph_obj.mdev; + int ret; + + mutex_lock(&mdev->graph_mutex); + ret = __media_pipeline_start(entity, pipe); + mutex_unlock(&mdev->graph_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(media_pipeline_start); + +void __media_pipeline_stop(struct media_entity *entity) +{ + struct media_graph *graph = &entity->pipe->graph; + struct media_pipeline *pipe = entity->pipe; + + /* + * If the following check fails, the driver has performed an + * unbalanced call to media_pipeline_stop() + */ + if (WARN_ON(!pipe)) + return; + + media_graph_walk_start(graph, entity); + + while ((entity = media_graph_walk_next(graph))) { + /* Sanity check for negative stream_count */ + if (!WARN_ON_ONCE(entity->stream_count <= 0)) { + entity->stream_count--; + if (entity->stream_count == 0) + entity->pipe = NULL; + } + } + + if (!--pipe->streaming_count) + media_graph_walk_cleanup(graph); + +} +EXPORT_SYMBOL_GPL(__media_pipeline_stop); + +void media_pipeline_stop(struct media_entity *entity) +{ + struct media_device *mdev = entity->graph_obj.mdev; + + mutex_lock(&mdev->graph_mutex); + __media_pipeline_stop(entity); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_pipeline_stop); + +/* ----------------------------------------------------------------------------- + * Links management + */ + +static struct media_link *media_add_link(struct list_head *head) +{ + struct media_link *link; + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (link == NULL) + return NULL; + + list_add_tail(&link->list, head); + + return link; +} + +static void __media_entity_remove_link(struct media_entity *entity, + struct media_link *link) +{ + struct media_link *rlink, *tmp; + struct media_entity *remote; + + if (link->source->entity == entity) + remote = link->sink->entity; + else + remote = link->source->entity; + + list_for_each_entry_safe(rlink, tmp, &remote->links, list) { + if (rlink != link->reverse) + continue; + + if (link->source->entity == entity) + remote->num_backlinks--; + + /* Remove the remote link */ + list_del(&rlink->list); + media_gobj_destroy(&rlink->graph_obj); + kfree(rlink); + + if (--remote->num_links == 0) + break; + } + list_del(&link->list); + media_gobj_destroy(&link->graph_obj); + kfree(link); +} + +int media_get_pad_index(struct media_entity *entity, bool is_sink, + enum media_pad_signal_type sig_type) +{ + int i; + bool pad_is_sink; + + if (!entity) + return -EINVAL; + + for (i = 0; i < entity->num_pads; i++) { + if (entity->pads[i].flags == MEDIA_PAD_FL_SINK) + pad_is_sink = true; + else if (entity->pads[i].flags == MEDIA_PAD_FL_SOURCE) + pad_is_sink = false; + else + continue; /* This is an error! */ + + if (pad_is_sink != is_sink) + continue; + if (entity->pads[i].sig_type == sig_type) + return i; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(media_get_pad_index); + +int +media_create_pad_link(struct media_entity *source, u16 source_pad, + struct media_entity *sink, u16 sink_pad, u32 flags) +{ + struct media_link *link; + struct media_link *backlink; + + BUG_ON(source == NULL || sink == NULL); + BUG_ON(source_pad >= source->num_pads); + BUG_ON(sink_pad >= sink->num_pads); + + link = media_add_link(&source->links); + if (link == NULL) + return -ENOMEM; + + link->source = &source->pads[source_pad]; + link->sink = &sink->pads[sink_pad]; + link->flags = flags & ~MEDIA_LNK_FL_INTERFACE_LINK; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(source->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + /* Create the backlink. Backlinks are used to help graph traversal and + * are not reported to userspace. + */ + backlink = media_add_link(&sink->links); + if (backlink == NULL) { + __media_entity_remove_link(source, link); + return -ENOMEM; + } + + backlink->source = &source->pads[source_pad]; + backlink->sink = &sink->pads[sink_pad]; + backlink->flags = flags; + backlink->is_backlink = true; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(sink->graph_obj.mdev, MEDIA_GRAPH_LINK, + &backlink->graph_obj); + + link->reverse = backlink; + backlink->reverse = link; + + sink->num_backlinks++; + sink->num_links++; + source->num_links++; + + return 0; +} +EXPORT_SYMBOL_GPL(media_create_pad_link); + +int media_create_pad_links(const struct media_device *mdev, + const u32 source_function, + struct media_entity *source, + const u16 source_pad, + const u32 sink_function, + struct media_entity *sink, + const u16 sink_pad, + u32 flags, + const bool allow_both_undefined) +{ + struct media_entity *entity; + unsigned function; + int ret; + + /* Trivial case: 1:1 relation */ + if (source && sink) + return media_create_pad_link(source, source_pad, + sink, sink_pad, flags); + + /* Worse case scenario: n:n relation */ + if (!source && !sink) { + if (!allow_both_undefined) + return 0; + media_device_for_each_entity(source, mdev) { + if (source->function != source_function) + continue; + media_device_for_each_entity(sink, mdev) { + if (sink->function != sink_function) + continue; + ret = media_create_pad_link(source, source_pad, + sink, sink_pad, + flags); + if (ret) + return ret; + flags &= ~(MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + } + } + return 0; + } + + /* Handle 1:n and n:1 cases */ + if (source) + function = sink_function; + else + function = source_function; + + media_device_for_each_entity(entity, mdev) { + if (entity->function != function) + continue; + + if (source) + ret = media_create_pad_link(source, source_pad, + entity, sink_pad, flags); + else + ret = media_create_pad_link(entity, source_pad, + sink, sink_pad, flags); + if (ret) + return ret; + flags &= ~(MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + } + return 0; +} +EXPORT_SYMBOL_GPL(media_create_pad_links); + +void __media_entity_remove_links(struct media_entity *entity) +{ + struct media_link *link, *tmp; + + list_for_each_entry_safe(link, tmp, &entity->links, list) + __media_entity_remove_link(entity, link); + + entity->num_links = 0; + entity->num_backlinks = 0; +} +EXPORT_SYMBOL_GPL(__media_entity_remove_links); + +void media_entity_remove_links(struct media_entity *entity) +{ + struct media_device *mdev = entity->graph_obj.mdev; + + /* Do nothing if the entity is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_entity_remove_links(entity); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_entity_remove_links); + +static int __media_entity_setup_link_notify(struct media_link *link, u32 flags) +{ + int ret; + + /* Notify both entities. */ + ret = media_entity_call(link->source->entity, link_setup, + link->source, link->sink, flags); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + ret = media_entity_call(link->sink->entity, link_setup, + link->sink, link->source, flags); + if (ret < 0 && ret != -ENOIOCTLCMD) { + media_entity_call(link->source->entity, link_setup, + link->source, link->sink, link->flags); + return ret; + } + + link->flags = flags; + link->reverse->flags = link->flags; + + return 0; +} + +int __media_entity_setup_link(struct media_link *link, u32 flags) +{ + const u32 mask = MEDIA_LNK_FL_ENABLED; + struct media_device *mdev; + struct media_entity *source, *sink; + int ret = -EBUSY; + + if (link == NULL) + return -EINVAL; + + /* The non-modifiable link flags must not be modified. */ + if ((link->flags & ~mask) != (flags & ~mask)) + return -EINVAL; + + if (link->flags & MEDIA_LNK_FL_IMMUTABLE) + return link->flags == flags ? 0 : -EINVAL; + + if (link->flags == flags) + return 0; + + source = link->source->entity; + sink = link->sink->entity; + + if (!(link->flags & MEDIA_LNK_FL_DYNAMIC) && + (source->stream_count || sink->stream_count)) + return -EBUSY; + + mdev = source->graph_obj.mdev; + + if (mdev->ops && mdev->ops->link_notify) { + ret = mdev->ops->link_notify(link, flags, + MEDIA_DEV_NOTIFY_PRE_LINK_CH); + if (ret < 0) + return ret; + } + + ret = __media_entity_setup_link_notify(link, flags); + + if (mdev->ops && mdev->ops->link_notify) + mdev->ops->link_notify(link, flags, + MEDIA_DEV_NOTIFY_POST_LINK_CH); + + return ret; +} +EXPORT_SYMBOL_GPL(__media_entity_setup_link); + +int media_entity_setup_link(struct media_link *link, u32 flags) +{ + int ret; + + mutex_lock(&link->graph_obj.mdev->graph_mutex); + ret = __media_entity_setup_link(link, flags); + mutex_unlock(&link->graph_obj.mdev->graph_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(media_entity_setup_link); + +struct media_link * +media_entity_find_link(struct media_pad *source, struct media_pad *sink) +{ + struct media_link *link; + + list_for_each_entry(link, &source->entity->links, list) { + if (link->source->entity == source->entity && + link->source->index == source->index && + link->sink->entity == sink->entity && + link->sink->index == sink->index) + return link; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(media_entity_find_link); + +struct media_pad *media_entity_remote_pad(const struct media_pad *pad) +{ + struct media_link *link; + + list_for_each_entry(link, &pad->entity->links, list) { + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (link->source == pad) + return link->sink; + + if (link->sink == pad) + return link->source; + } + + return NULL; + +} +EXPORT_SYMBOL_GPL(media_entity_remote_pad); + +static void media_interface_init(struct media_device *mdev, + struct media_interface *intf, + u32 gobj_type, + u32 intf_type, u32 flags) +{ + intf->type = intf_type; + intf->flags = flags; + INIT_LIST_HEAD(&intf->links); + + media_gobj_create(mdev, gobj_type, &intf->graph_obj); +} + +/* Functions related to the media interface via device nodes */ + +struct media_intf_devnode *media_devnode_create(struct media_device *mdev, + u32 type, u32 flags, + u32 major, u32 minor) +{ + struct media_intf_devnode *devnode; + + devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); + if (!devnode) + return NULL; + + devnode->major = major; + devnode->minor = minor; + + media_interface_init(mdev, &devnode->intf, MEDIA_GRAPH_INTF_DEVNODE, + type, flags); + + return devnode; +} +EXPORT_SYMBOL_GPL(media_devnode_create); + +void media_devnode_remove(struct media_intf_devnode *devnode) +{ + media_remove_intf_links(&devnode->intf); + media_gobj_destroy(&devnode->intf.graph_obj); + kfree(devnode); +} +EXPORT_SYMBOL_GPL(media_devnode_remove); + +struct media_link *media_create_intf_link(struct media_entity *entity, + struct media_interface *intf, + u32 flags) +{ + struct media_link *link; + + link = media_add_link(&intf->links); + if (link == NULL) + return NULL; + + link->intf = intf; + link->entity = entity; + link->flags = flags | MEDIA_LNK_FL_INTERFACE_LINK; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(intf->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + return link; +} +EXPORT_SYMBOL_GPL(media_create_intf_link); + +void __media_remove_intf_link(struct media_link *link) +{ + list_del(&link->list); + media_gobj_destroy(&link->graph_obj); + kfree(link); +} +EXPORT_SYMBOL_GPL(__media_remove_intf_link); + +void media_remove_intf_link(struct media_link *link) +{ + struct media_device *mdev = link->graph_obj.mdev; + + /* Do nothing if the intf is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_remove_intf_link(link); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_remove_intf_link); + +void __media_remove_intf_links(struct media_interface *intf) +{ + struct media_link *link, *tmp; + + list_for_each_entry_safe(link, tmp, &intf->links, list) + __media_remove_intf_link(link); + +} +EXPORT_SYMBOL_GPL(__media_remove_intf_links); + +void media_remove_intf_links(struct media_interface *intf) +{ + struct media_device *mdev = intf->graph_obj.mdev; + + /* Do nothing if the intf is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_remove_intf_links(intf); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_remove_intf_links); diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c new file mode 100644 index 000000000000..e3fca436c75b --- /dev/null +++ b/drivers/media/mc/mc-request.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Media device request objects + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018 Google, Inc. + * + * Author: Hans Verkuil + * Author: Sakari Ailus + */ + +#include +#include +#include + +#include +#include + +static const char * const request_state[] = { + [MEDIA_REQUEST_STATE_IDLE] = "idle", + [MEDIA_REQUEST_STATE_VALIDATING] = "validating", + [MEDIA_REQUEST_STATE_QUEUED] = "queued", + [MEDIA_REQUEST_STATE_COMPLETE] = "complete", + [MEDIA_REQUEST_STATE_CLEANING] = "cleaning", + [MEDIA_REQUEST_STATE_UPDATING] = "updating", +}; + +static const char * +media_request_state_str(enum media_request_state state) +{ + BUILD_BUG_ON(ARRAY_SIZE(request_state) != NR_OF_MEDIA_REQUEST_STATE); + + if (WARN_ON(state >= ARRAY_SIZE(request_state))) + return "invalid"; + return request_state[state]; +} + +static void media_request_clean(struct media_request *req) +{ + struct media_request_object *obj, *obj_safe; + + /* Just a sanity check. No other code path is allowed to change this. */ + WARN_ON(req->state != MEDIA_REQUEST_STATE_CLEANING); + WARN_ON(req->updating_count); + WARN_ON(req->access_count); + + list_for_each_entry_safe(obj, obj_safe, &req->objects, list) { + media_request_object_unbind(obj); + media_request_object_put(obj); + } + + req->updating_count = 0; + req->access_count = 0; + WARN_ON(req->num_incomplete_objects); + req->num_incomplete_objects = 0; + wake_up_interruptible_all(&req->poll_wait); +} + +static void media_request_release(struct kref *kref) +{ + struct media_request *req = + container_of(kref, struct media_request, kref); + struct media_device *mdev = req->mdev; + + dev_dbg(mdev->dev, "request: release %s\n", req->debug_str); + + /* No other users, no need for a spinlock */ + req->state = MEDIA_REQUEST_STATE_CLEANING; + + media_request_clean(req); + + if (mdev->ops->req_free) + mdev->ops->req_free(req); + else + kfree(req); +} + +void media_request_put(struct media_request *req) +{ + kref_put(&req->kref, media_request_release); +} +EXPORT_SYMBOL_GPL(media_request_put); + +static int media_request_close(struct inode *inode, struct file *filp) +{ + struct media_request *req = filp->private_data; + + media_request_put(req); + return 0; +} + +static __poll_t media_request_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct media_request *req = filp->private_data; + unsigned long flags; + __poll_t ret = 0; + + if (!(poll_requested_events(wait) & EPOLLPRI)) + return 0; + + poll_wait(filp, &req->poll_wait, wait); + spin_lock_irqsave(&req->lock, flags); + if (req->state == MEDIA_REQUEST_STATE_COMPLETE) { + ret = EPOLLPRI; + goto unlock; + } + if (req->state != MEDIA_REQUEST_STATE_QUEUED) { + ret = EPOLLERR; + goto unlock; + } + +unlock: + spin_unlock_irqrestore(&req->lock, flags); + return ret; +} + +static long media_request_ioctl_queue(struct media_request *req) +{ + struct media_device *mdev = req->mdev; + enum media_request_state state; + unsigned long flags; + int ret; + + dev_dbg(mdev->dev, "request: queue %s\n", req->debug_str); + + /* + * Ensure the request that is validated will be the one that gets queued + * next by serialising the queueing process. This mutex is also used + * to serialize with canceling a vb2 queue and with setting values such + * as controls in a request. + */ + mutex_lock(&mdev->req_queue_mutex); + + media_request_get(req); + + spin_lock_irqsave(&req->lock, flags); + if (req->state == MEDIA_REQUEST_STATE_IDLE) + req->state = MEDIA_REQUEST_STATE_VALIDATING; + state = req->state; + spin_unlock_irqrestore(&req->lock, flags); + if (state != MEDIA_REQUEST_STATE_VALIDATING) { + dev_dbg(mdev->dev, + "request: unable to queue %s, request in state %s\n", + req->debug_str, media_request_state_str(state)); + media_request_put(req); + mutex_unlock(&mdev->req_queue_mutex); + return -EBUSY; + } + + ret = mdev->ops->req_validate(req); + + /* + * If the req_validate was successful, then we mark the state as QUEUED + * and call req_queue. The reason we set the state first is that this + * allows req_queue to unbind or complete the queued objects in case + * they are immediately 'consumed'. State changes from QUEUED to another + * state can only happen if either the driver changes the state or if + * the user cancels the vb2 queue. The driver can only change the state + * after each object is queued through the req_queue op (and note that + * that op cannot fail), so setting the state to QUEUED up front is + * safe. + * + * The other reason for changing the state is if the vb2 queue is + * canceled, and that uses the req_queue_mutex which is still locked + * while req_queue is called, so that's safe as well. + */ + spin_lock_irqsave(&req->lock, flags); + req->state = ret ? MEDIA_REQUEST_STATE_IDLE + : MEDIA_REQUEST_STATE_QUEUED; + spin_unlock_irqrestore(&req->lock, flags); + + if (!ret) + mdev->ops->req_queue(req); + + mutex_unlock(&mdev->req_queue_mutex); + + if (ret) { + dev_dbg(mdev->dev, "request: can't queue %s (%d)\n", + req->debug_str, ret); + media_request_put(req); + } + + return ret; +} + +static long media_request_ioctl_reinit(struct media_request *req) +{ + struct media_device *mdev = req->mdev; + unsigned long flags; + + spin_lock_irqsave(&req->lock, flags); + if (req->state != MEDIA_REQUEST_STATE_IDLE && + req->state != MEDIA_REQUEST_STATE_COMPLETE) { + dev_dbg(mdev->dev, + "request: %s not in idle or complete state, cannot reinit\n", + req->debug_str); + spin_unlock_irqrestore(&req->lock, flags); + return -EBUSY; + } + if (req->access_count) { + dev_dbg(mdev->dev, + "request: %s is being accessed, cannot reinit\n", + req->debug_str); + spin_unlock_irqrestore(&req->lock, flags); + return -EBUSY; + } + req->state = MEDIA_REQUEST_STATE_CLEANING; + spin_unlock_irqrestore(&req->lock, flags); + + media_request_clean(req); + + spin_lock_irqsave(&req->lock, flags); + req->state = MEDIA_REQUEST_STATE_IDLE; + spin_unlock_irqrestore(&req->lock, flags); + + return 0; +} + +static long media_request_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct media_request *req = filp->private_data; + + switch (cmd) { + case MEDIA_REQUEST_IOC_QUEUE: + return media_request_ioctl_queue(req); + case MEDIA_REQUEST_IOC_REINIT: + return media_request_ioctl_reinit(req); + default: + return -ENOIOCTLCMD; + } +} + +static const struct file_operations request_fops = { + .owner = THIS_MODULE, + .poll = media_request_poll, + .unlocked_ioctl = media_request_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = media_request_ioctl, +#endif /* CONFIG_COMPAT */ + .release = media_request_close, +}; + +struct media_request * +media_request_get_by_fd(struct media_device *mdev, int request_fd) +{ + struct fd f; + struct media_request *req; + + if (!mdev || !mdev->ops || + !mdev->ops->req_validate || !mdev->ops->req_queue) + return ERR_PTR(-EBADR); + + f = fdget(request_fd); + if (!f.file) + goto err_no_req_fd; + + if (f.file->f_op != &request_fops) + goto err_fput; + req = f.file->private_data; + if (req->mdev != mdev) + goto err_fput; + + /* + * Note: as long as someone has an open filehandle of the request, + * the request can never be released. The fdget() above ensures that + * even if userspace closes the request filehandle, the release() + * fop won't be called, so the media_request_get() always succeeds + * and there is no race condition where the request was released + * before media_request_get() is called. + */ + media_request_get(req); + fdput(f); + + return req; + +err_fput: + fdput(f); + +err_no_req_fd: + dev_dbg(mdev->dev, "cannot find request_fd %d\n", request_fd); + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(media_request_get_by_fd); + +int media_request_alloc(struct media_device *mdev, int *alloc_fd) +{ + struct media_request *req; + struct file *filp; + int fd; + int ret; + + /* Either both are NULL or both are non-NULL */ + if (WARN_ON(!mdev->ops->req_alloc ^ !mdev->ops->req_free)) + return -ENOMEM; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + + filp = anon_inode_getfile("request", &request_fops, NULL, O_CLOEXEC); + if (IS_ERR(filp)) { + ret = PTR_ERR(filp); + goto err_put_fd; + } + + if (mdev->ops->req_alloc) + req = mdev->ops->req_alloc(mdev); + else + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto err_fput; + } + + filp->private_data = req; + req->mdev = mdev; + req->state = MEDIA_REQUEST_STATE_IDLE; + req->num_incomplete_objects = 0; + kref_init(&req->kref); + INIT_LIST_HEAD(&req->objects); + spin_lock_init(&req->lock); + init_waitqueue_head(&req->poll_wait); + req->updating_count = 0; + req->access_count = 0; + + *alloc_fd = fd; + + snprintf(req->debug_str, sizeof(req->debug_str), "%u:%d", + atomic_inc_return(&mdev->request_id), fd); + dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str); + + fd_install(fd, filp); + + return 0; + +err_fput: + fput(filp); + +err_put_fd: + put_unused_fd(fd); + + return ret; +} + +static void media_request_object_release(struct kref *kref) +{ + struct media_request_object *obj = + container_of(kref, struct media_request_object, kref); + struct media_request *req = obj->req; + + if (WARN_ON(req)) + media_request_object_unbind(obj); + obj->ops->release(obj); +} + +struct media_request_object * +media_request_object_find(struct media_request *req, + const struct media_request_object_ops *ops, + void *priv) +{ + struct media_request_object *obj; + struct media_request_object *found = NULL; + unsigned long flags; + + if (WARN_ON(!ops || !priv)) + return NULL; + + spin_lock_irqsave(&req->lock, flags); + list_for_each_entry(obj, &req->objects, list) { + if (obj->ops == ops && obj->priv == priv) { + media_request_object_get(obj); + found = obj; + break; + } + } + spin_unlock_irqrestore(&req->lock, flags); + return found; +} +EXPORT_SYMBOL_GPL(media_request_object_find); + +void media_request_object_put(struct media_request_object *obj) +{ + kref_put(&obj->kref, media_request_object_release); +} +EXPORT_SYMBOL_GPL(media_request_object_put); + +void media_request_object_init(struct media_request_object *obj) +{ + obj->ops = NULL; + obj->req = NULL; + obj->priv = NULL; + obj->completed = false; + INIT_LIST_HEAD(&obj->list); + kref_init(&obj->kref); +} +EXPORT_SYMBOL_GPL(media_request_object_init); + +int media_request_object_bind(struct media_request *req, + const struct media_request_object_ops *ops, + void *priv, bool is_buffer, + struct media_request_object *obj) +{ + unsigned long flags; + int ret = -EBUSY; + + if (WARN_ON(!ops->release)) + return -EBADR; + + spin_lock_irqsave(&req->lock, flags); + + if (WARN_ON(req->state != MEDIA_REQUEST_STATE_UPDATING)) + goto unlock; + + obj->req = req; + obj->ops = ops; + obj->priv = priv; + + if (is_buffer) + list_add_tail(&obj->list, &req->objects); + else + list_add(&obj->list, &req->objects); + req->num_incomplete_objects++; + ret = 0; + +unlock: + spin_unlock_irqrestore(&req->lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(media_request_object_bind); + +void media_request_object_unbind(struct media_request_object *obj) +{ + struct media_request *req = obj->req; + unsigned long flags; + bool completed = false; + + if (WARN_ON(!req)) + return; + + spin_lock_irqsave(&req->lock, flags); + list_del(&obj->list); + obj->req = NULL; + + if (req->state == MEDIA_REQUEST_STATE_COMPLETE) + goto unlock; + + if (WARN_ON(req->state == MEDIA_REQUEST_STATE_VALIDATING)) + goto unlock; + + if (req->state == MEDIA_REQUEST_STATE_CLEANING) { + if (!obj->completed) + req->num_incomplete_objects--; + goto unlock; + } + + if (WARN_ON(!req->num_incomplete_objects)) + goto unlock; + + req->num_incomplete_objects--; + if (req->state == MEDIA_REQUEST_STATE_QUEUED && + !req->num_incomplete_objects) { + req->state = MEDIA_REQUEST_STATE_COMPLETE; + completed = true; + wake_up_interruptible_all(&req->poll_wait); + } + +unlock: + spin_unlock_irqrestore(&req->lock, flags); + if (obj->ops->unbind) + obj->ops->unbind(obj); + if (completed) + media_request_put(req); +} +EXPORT_SYMBOL_GPL(media_request_object_unbind); + +void media_request_object_complete(struct media_request_object *obj) +{ + struct media_request *req = obj->req; + unsigned long flags; + bool completed = false; + + spin_lock_irqsave(&req->lock, flags); + if (obj->completed) + goto unlock; + obj->completed = true; + if (WARN_ON(!req->num_incomplete_objects) || + WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED)) + goto unlock; + + if (!--req->num_incomplete_objects) { + req->state = MEDIA_REQUEST_STATE_COMPLETE; + wake_up_interruptible_all(&req->poll_wait); + completed = true; + } +unlock: + spin_unlock_irqrestore(&req->lock, flags); + if (completed) + media_request_put(req); +} +EXPORT_SYMBOL_GPL(media_request_object_complete); diff --git a/drivers/media/media-dev-allocator.c b/drivers/media/media-dev-allocator.c deleted file mode 100644 index ae17887dec59..000000000000 --- a/drivers/media/media-dev-allocator.c +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * media-dev-allocator.c - Media Controller Device Allocator API - * - * Copyright (c) 2019 Shuah Khan - * - * Credits: Suggested by Laurent Pinchart - */ - -/* - * This file adds a global refcounted Media Controller Device Instance API. - * A system wide global media device list is managed and each media device - * includes a kref count. The last put on the media device releases the media - * device instance. - * - */ - -#include -#include -#include -#include - -#include -#include - -static LIST_HEAD(media_device_list); -static DEFINE_MUTEX(media_device_lock); - -struct media_device_instance { - struct media_device mdev; - struct module *owner; - struct list_head list; - struct kref refcount; -}; - -static inline struct media_device_instance * -to_media_device_instance(struct media_device *mdev) -{ - return container_of(mdev, struct media_device_instance, mdev); -} - -static void media_device_instance_release(struct kref *kref) -{ - struct media_device_instance *mdi = - container_of(kref, struct media_device_instance, refcount); - - dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__); - - mutex_lock(&media_device_lock); - - media_device_unregister(&mdi->mdev); - media_device_cleanup(&mdi->mdev); - - list_del(&mdi->list); - mutex_unlock(&media_device_lock); - - kfree(mdi); -} - -/* Callers should hold media_device_lock when calling this function */ -static struct media_device *__media_device_get(struct device *dev, - const char *module_name, - struct module *owner) -{ - struct media_device_instance *mdi; - - list_for_each_entry(mdi, &media_device_list, list) { - if (mdi->mdev.dev != dev) - continue; - - kref_get(&mdi->refcount); - - /* get module reference for the media_device owner */ - if (owner != mdi->owner && !try_module_get(mdi->owner)) - dev_err(dev, - "%s: module %s get owner reference error\n", - __func__, module_name); - else - dev_dbg(dev, "%s: module %s got owner reference\n", - __func__, module_name); - return &mdi->mdev; - } - - mdi = kzalloc(sizeof(*mdi), GFP_KERNEL); - if (!mdi) - return NULL; - - mdi->owner = owner; - kref_init(&mdi->refcount); - list_add_tail(&mdi->list, &media_device_list); - - dev_dbg(dev, "%s: Allocated media device for owner %s\n", - __func__, module_name); - return &mdi->mdev; -} - -struct media_device *media_device_usb_allocate(struct usb_device *udev, - const char *module_name, - struct module *owner) -{ - struct media_device *mdev; - - mutex_lock(&media_device_lock); - mdev = __media_device_get(&udev->dev, module_name, owner); - if (!mdev) { - mutex_unlock(&media_device_lock); - return ERR_PTR(-ENOMEM); - } - - /* check if media device is already initialized */ - if (!mdev->dev) - __media_device_usb_init(mdev, udev, udev->product, - module_name); - mutex_unlock(&media_device_lock); - return mdev; -} -EXPORT_SYMBOL_GPL(media_device_usb_allocate); - -void media_device_delete(struct media_device *mdev, const char *module_name, - struct module *owner) -{ - struct media_device_instance *mdi = to_media_device_instance(mdev); - - mutex_lock(&media_device_lock); - /* put module reference for the media_device owner */ - if (mdi->owner != owner) { - module_put(mdi->owner); - dev_dbg(mdi->mdev.dev, - "%s: module %s put owner module reference\n", - __func__, module_name); - } - mutex_unlock(&media_device_lock); - kref_put(&mdi->refcount, media_device_instance_release); -} -EXPORT_SYMBOL_GPL(media_device_delete); diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c deleted file mode 100644 index 6893843edada..000000000000 --- a/drivers/media/media-device.c +++ /dev/null @@ -1,909 +0,0 @@ -/* - * Media device - * - * Copyright (C) 2010 Nokia Corporation - * - * Contacts: Laurent Pinchart - * Sakari Ailus - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef CONFIG_MEDIA_CONTROLLER - -/* - * Legacy defines from linux/media.h. This is the only place we need this - * so we just define it here. The media.h header doesn't expose it to the - * kernel to prevent it from being used by drivers, but here (and only here!) - * we need it to handle the legacy behavior. - */ -#define MEDIA_ENT_SUBTYPE_MASK 0x0000ffff -#define MEDIA_ENT_T_DEVNODE_UNKNOWN (MEDIA_ENT_F_OLD_BASE | \ - MEDIA_ENT_SUBTYPE_MASK) - -/* ----------------------------------------------------------------------------- - * Userspace API - */ - -static inline void __user *media_get_uptr(__u64 arg) -{ - return (void __user *)(uintptr_t)arg; -} - -static int media_device_open(struct file *filp) -{ - return 0; -} - -static int media_device_close(struct file *filp) -{ - return 0; -} - -static long media_device_get_info(struct media_device *dev, void *arg) -{ - struct media_device_info *info = arg; - - memset(info, 0, sizeof(*info)); - - if (dev->driver_name[0]) - strscpy(info->driver, dev->driver_name, sizeof(info->driver)); - else - strscpy(info->driver, dev->dev->driver->name, - sizeof(info->driver)); - - strscpy(info->model, dev->model, sizeof(info->model)); - strscpy(info->serial, dev->serial, sizeof(info->serial)); - strscpy(info->bus_info, dev->bus_info, sizeof(info->bus_info)); - - info->media_version = LINUX_VERSION_CODE; - info->driver_version = info->media_version; - info->hw_revision = dev->hw_revision; - - return 0; -} - -static struct media_entity *find_entity(struct media_device *mdev, u32 id) -{ - struct media_entity *entity; - int next = id & MEDIA_ENT_ID_FLAG_NEXT; - - id &= ~MEDIA_ENT_ID_FLAG_NEXT; - - media_device_for_each_entity(entity, mdev) { - if (((media_entity_id(entity) == id) && !next) || - ((media_entity_id(entity) > id) && next)) { - return entity; - } - } - - return NULL; -} - -static long media_device_enum_entities(struct media_device *mdev, void *arg) -{ - struct media_entity_desc *entd = arg; - struct media_entity *ent; - - ent = find_entity(mdev, entd->id); - if (ent == NULL) - return -EINVAL; - - memset(entd, 0, sizeof(*entd)); - - entd->id = media_entity_id(ent); - if (ent->name) - strscpy(entd->name, ent->name, sizeof(entd->name)); - entd->type = ent->function; - entd->revision = 0; /* Unused */ - entd->flags = ent->flags; - entd->group_id = 0; /* Unused */ - entd->pads = ent->num_pads; - entd->links = ent->num_links - ent->num_backlinks; - - /* - * Workaround for a bug at media-ctl <= v1.10 that makes it to - * do the wrong thing if the entity function doesn't belong to - * either MEDIA_ENT_F_OLD_BASE or MEDIA_ENT_F_OLD_SUBDEV_BASE - * Ranges. - * - * Non-subdevices are expected to be at the MEDIA_ENT_F_OLD_BASE, - * or, otherwise, will be silently ignored by media-ctl when - * printing the graphviz diagram. So, map them into the devnode - * old range. - */ - if (ent->function < MEDIA_ENT_F_OLD_BASE || - ent->function > MEDIA_ENT_F_TUNER) { - if (is_media_entity_v4l2_subdev(ent)) - entd->type = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; - else if (ent->function != MEDIA_ENT_F_IO_V4L) - entd->type = MEDIA_ENT_T_DEVNODE_UNKNOWN; - } - - memcpy(&entd->raw, &ent->info, sizeof(ent->info)); - - return 0; -} - -static void media_device_kpad_to_upad(const struct media_pad *kpad, - struct media_pad_desc *upad) -{ - upad->entity = media_entity_id(kpad->entity); - upad->index = kpad->index; - upad->flags = kpad->flags; -} - -static long media_device_enum_links(struct media_device *mdev, void *arg) -{ - struct media_links_enum *links = arg; - struct media_entity *entity; - - entity = find_entity(mdev, links->entity); - if (entity == NULL) - return -EINVAL; - - if (links->pads) { - unsigned int p; - - for (p = 0; p < entity->num_pads; p++) { - struct media_pad_desc pad; - - memset(&pad, 0, sizeof(pad)); - media_device_kpad_to_upad(&entity->pads[p], &pad); - if (copy_to_user(&links->pads[p], &pad, sizeof(pad))) - return -EFAULT; - } - } - - if (links->links) { - struct media_link *link; - struct media_link_desc __user *ulink_desc = links->links; - - list_for_each_entry(link, &entity->links, list) { - struct media_link_desc klink_desc; - - /* Ignore backlinks. */ - if (link->source->entity != entity) - continue; - memset(&klink_desc, 0, sizeof(klink_desc)); - media_device_kpad_to_upad(link->source, - &klink_desc.source); - media_device_kpad_to_upad(link->sink, - &klink_desc.sink); - klink_desc.flags = link->flags; - if (copy_to_user(ulink_desc, &klink_desc, - sizeof(*ulink_desc))) - return -EFAULT; - ulink_desc++; - } - } - memset(links->reserved, 0, sizeof(links->reserved)); - - return 0; -} - -static long media_device_setup_link(struct media_device *mdev, void *arg) -{ - struct media_link_desc *linkd = arg; - struct media_link *link = NULL; - struct media_entity *source; - struct media_entity *sink; - - /* Find the source and sink entities and link. - */ - source = find_entity(mdev, linkd->source.entity); - sink = find_entity(mdev, linkd->sink.entity); - - if (source == NULL || sink == NULL) - return -EINVAL; - - if (linkd->source.index >= source->num_pads || - linkd->sink.index >= sink->num_pads) - return -EINVAL; - - link = media_entity_find_link(&source->pads[linkd->source.index], - &sink->pads[linkd->sink.index]); - if (link == NULL) - return -EINVAL; - - memset(linkd->reserved, 0, sizeof(linkd->reserved)); - - /* Setup the link on both entities. */ - return __media_entity_setup_link(link, linkd->flags); -} - -static long media_device_get_topology(struct media_device *mdev, void *arg) -{ - struct media_v2_topology *topo = arg; - struct media_entity *entity; - struct media_interface *intf; - struct media_pad *pad; - struct media_link *link; - struct media_v2_entity kentity, __user *uentity; - struct media_v2_interface kintf, __user *uintf; - struct media_v2_pad kpad, __user *upad; - struct media_v2_link klink, __user *ulink; - unsigned int i; - int ret = 0; - - topo->topology_version = mdev->topology_version; - - /* Get entities and number of entities */ - i = 0; - uentity = media_get_uptr(topo->ptr_entities); - media_device_for_each_entity(entity, mdev) { - i++; - if (ret || !uentity) - continue; - - if (i > topo->num_entities) { - ret = -ENOSPC; - continue; - } - - /* Copy fields to userspace struct if not error */ - memset(&kentity, 0, sizeof(kentity)); - kentity.id = entity->graph_obj.id; - kentity.function = entity->function; - kentity.flags = entity->flags; - strscpy(kentity.name, entity->name, - sizeof(kentity.name)); - - if (copy_to_user(uentity, &kentity, sizeof(kentity))) - ret = -EFAULT; - uentity++; - } - topo->num_entities = i; - topo->reserved1 = 0; - - /* Get interfaces and number of interfaces */ - i = 0; - uintf = media_get_uptr(topo->ptr_interfaces); - media_device_for_each_intf(intf, mdev) { - i++; - if (ret || !uintf) - continue; - - if (i > topo->num_interfaces) { - ret = -ENOSPC; - continue; - } - - memset(&kintf, 0, sizeof(kintf)); - - /* Copy intf fields to userspace struct */ - kintf.id = intf->graph_obj.id; - kintf.intf_type = intf->type; - kintf.flags = intf->flags; - - if (media_type(&intf->graph_obj) == MEDIA_GRAPH_INTF_DEVNODE) { - struct media_intf_devnode *devnode; - - devnode = intf_to_devnode(intf); - - kintf.devnode.major = devnode->major; - kintf.devnode.minor = devnode->minor; - } - - if (copy_to_user(uintf, &kintf, sizeof(kintf))) - ret = -EFAULT; - uintf++; - } - topo->num_interfaces = i; - topo->reserved2 = 0; - - /* Get pads and number of pads */ - i = 0; - upad = media_get_uptr(topo->ptr_pads); - media_device_for_each_pad(pad, mdev) { - i++; - if (ret || !upad) - continue; - - if (i > topo->num_pads) { - ret = -ENOSPC; - continue; - } - - memset(&kpad, 0, sizeof(kpad)); - - /* Copy pad fields to userspace struct */ - kpad.id = pad->graph_obj.id; - kpad.entity_id = pad->entity->graph_obj.id; - kpad.flags = pad->flags; - kpad.index = pad->index; - - if (copy_to_user(upad, &kpad, sizeof(kpad))) - ret = -EFAULT; - upad++; - } - topo->num_pads = i; - topo->reserved3 = 0; - - /* Get links and number of links */ - i = 0; - ulink = media_get_uptr(topo->ptr_links); - media_device_for_each_link(link, mdev) { - if (link->is_backlink) - continue; - - i++; - - if (ret || !ulink) - continue; - - if (i > topo->num_links) { - ret = -ENOSPC; - continue; - } - - memset(&klink, 0, sizeof(klink)); - - /* Copy link fields to userspace struct */ - klink.id = link->graph_obj.id; - klink.source_id = link->gobj0->id; - klink.sink_id = link->gobj1->id; - klink.flags = link->flags; - - if (copy_to_user(ulink, &klink, sizeof(klink))) - ret = -EFAULT; - ulink++; - } - topo->num_links = i; - topo->reserved4 = 0; - - return ret; -} - -static long media_device_request_alloc(struct media_device *mdev, - int *alloc_fd) -{ -#ifdef CONFIG_MEDIA_CONTROLLER_REQUEST_API - if (!mdev->ops || !mdev->ops->req_validate || !mdev->ops->req_queue) - return -ENOTTY; - - return media_request_alloc(mdev, alloc_fd); -#else - return -ENOTTY; -#endif -} - -static long copy_arg_from_user(void *karg, void __user *uarg, unsigned int cmd) -{ - if ((_IOC_DIR(cmd) & _IOC_WRITE) && - copy_from_user(karg, uarg, _IOC_SIZE(cmd))) - return -EFAULT; - - return 0; -} - -static long copy_arg_to_user(void __user *uarg, void *karg, unsigned int cmd) -{ - if ((_IOC_DIR(cmd) & _IOC_READ) && - copy_to_user(uarg, karg, _IOC_SIZE(cmd))) - return -EFAULT; - - return 0; -} - -/* Do acquire the graph mutex */ -#define MEDIA_IOC_FL_GRAPH_MUTEX BIT(0) - -#define MEDIA_IOC_ARG(__cmd, func, fl, from_user, to_user) \ - [_IOC_NR(MEDIA_IOC_##__cmd)] = { \ - .cmd = MEDIA_IOC_##__cmd, \ - .fn = (long (*)(struct media_device *, void *))func, \ - .flags = fl, \ - .arg_from_user = from_user, \ - .arg_to_user = to_user, \ - } - -#define MEDIA_IOC(__cmd, func, fl) \ - MEDIA_IOC_ARG(__cmd, func, fl, copy_arg_from_user, copy_arg_to_user) - -/* the table is indexed by _IOC_NR(cmd) */ -struct media_ioctl_info { - unsigned int cmd; - unsigned short flags; - long (*fn)(struct media_device *dev, void *arg); - long (*arg_from_user)(void *karg, void __user *uarg, unsigned int cmd); - long (*arg_to_user)(void __user *uarg, void *karg, unsigned int cmd); -}; - -static const struct media_ioctl_info ioctl_info[] = { - MEDIA_IOC(DEVICE_INFO, media_device_get_info, MEDIA_IOC_FL_GRAPH_MUTEX), - MEDIA_IOC(ENUM_ENTITIES, media_device_enum_entities, MEDIA_IOC_FL_GRAPH_MUTEX), - MEDIA_IOC(ENUM_LINKS, media_device_enum_links, MEDIA_IOC_FL_GRAPH_MUTEX), - MEDIA_IOC(SETUP_LINK, media_device_setup_link, MEDIA_IOC_FL_GRAPH_MUTEX), - MEDIA_IOC(G_TOPOLOGY, media_device_get_topology, MEDIA_IOC_FL_GRAPH_MUTEX), - MEDIA_IOC(REQUEST_ALLOC, media_device_request_alloc, 0), -}; - -static long media_device_ioctl(struct file *filp, unsigned int cmd, - unsigned long __arg) -{ - struct media_devnode *devnode = media_devnode_data(filp); - struct media_device *dev = devnode->media_dev; - const struct media_ioctl_info *info; - void __user *arg = (void __user *)__arg; - char __karg[256], *karg = __karg; - long ret; - - if (_IOC_NR(cmd) >= ARRAY_SIZE(ioctl_info) - || ioctl_info[_IOC_NR(cmd)].cmd != cmd) - return -ENOIOCTLCMD; - - info = &ioctl_info[_IOC_NR(cmd)]; - - if (_IOC_SIZE(info->cmd) > sizeof(__karg)) { - karg = kmalloc(_IOC_SIZE(info->cmd), GFP_KERNEL); - if (!karg) - return -ENOMEM; - } - - if (info->arg_from_user) { - ret = info->arg_from_user(karg, arg, cmd); - if (ret) - goto out_free; - } - - if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) - mutex_lock(&dev->graph_mutex); - - ret = info->fn(dev, karg); - - if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) - mutex_unlock(&dev->graph_mutex); - - if (!ret && info->arg_to_user) - ret = info->arg_to_user(arg, karg, cmd); - -out_free: - if (karg != __karg) - kfree(karg); - - return ret; -} - -#ifdef CONFIG_COMPAT - -struct media_links_enum32 { - __u32 entity; - compat_uptr_t pads; /* struct media_pad_desc * */ - compat_uptr_t links; /* struct media_link_desc * */ - __u32 reserved[4]; -}; - -static long media_device_enum_links32(struct media_device *mdev, - struct media_links_enum32 __user *ulinks) -{ - struct media_links_enum links; - compat_uptr_t pads_ptr, links_ptr; - int ret; - - memset(&links, 0, sizeof(links)); - - if (get_user(links.entity, &ulinks->entity) - || get_user(pads_ptr, &ulinks->pads) - || get_user(links_ptr, &ulinks->links)) - return -EFAULT; - - links.pads = compat_ptr(pads_ptr); - links.links = compat_ptr(links_ptr); - - ret = media_device_enum_links(mdev, &links); - if (ret) - return ret; - - memset(ulinks->reserved, 0, sizeof(ulinks->reserved)); - - return 0; -} - -#define MEDIA_IOC_ENUM_LINKS32 _IOWR('|', 0x02, struct media_links_enum32) - -static long media_device_compat_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - struct media_devnode *devnode = media_devnode_data(filp); - struct media_device *dev = devnode->media_dev; - long ret; - - switch (cmd) { - case MEDIA_IOC_ENUM_LINKS32: - mutex_lock(&dev->graph_mutex); - ret = media_device_enum_links32(dev, - (struct media_links_enum32 __user *)arg); - mutex_unlock(&dev->graph_mutex); - break; - - default: - return media_device_ioctl(filp, cmd, arg); - } - - return ret; -} -#endif /* CONFIG_COMPAT */ - -static const struct media_file_operations media_device_fops = { - .owner = THIS_MODULE, - .open = media_device_open, - .ioctl = media_device_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = media_device_compat_ioctl, -#endif /* CONFIG_COMPAT */ - .release = media_device_close, -}; - -/* ----------------------------------------------------------------------------- - * sysfs - */ - -static ssize_t show_model(struct device *cd, - struct device_attribute *attr, char *buf) -{ - struct media_devnode *devnode = to_media_devnode(cd); - struct media_device *mdev = devnode->media_dev; - - return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model); -} - -static DEVICE_ATTR(model, S_IRUGO, show_model, NULL); - -/* ----------------------------------------------------------------------------- - * Registration/unregistration - */ - -static void media_device_release(struct media_devnode *devnode) -{ - dev_dbg(devnode->parent, "Media device released\n"); -} - -/** - * media_device_register_entity - Register an entity with a media device - * @mdev: The media device - * @entity: The entity - */ -int __must_check media_device_register_entity(struct media_device *mdev, - struct media_entity *entity) -{ - struct media_entity_notify *notify, *next; - unsigned int i; - int ret; - - if (entity->function == MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN || - entity->function == MEDIA_ENT_F_UNKNOWN) - dev_warn(mdev->dev, - "Entity type for entity %s was not initialized!\n", - entity->name); - - /* Warn if we apparently re-register an entity */ - WARN_ON(entity->graph_obj.mdev != NULL); - entity->graph_obj.mdev = mdev; - INIT_LIST_HEAD(&entity->links); - entity->num_links = 0; - entity->num_backlinks = 0; - - ret = ida_alloc_min(&mdev->entity_internal_idx, 1, GFP_KERNEL); - if (ret < 0) - return ret; - entity->internal_idx = ret; - - mutex_lock(&mdev->graph_mutex); - mdev->entity_internal_idx_max = - max(mdev->entity_internal_idx_max, entity->internal_idx); - - /* Initialize media_gobj embedded at the entity */ - media_gobj_create(mdev, MEDIA_GRAPH_ENTITY, &entity->graph_obj); - - /* Initialize objects at the pads */ - for (i = 0; i < entity->num_pads; i++) - media_gobj_create(mdev, MEDIA_GRAPH_PAD, - &entity->pads[i].graph_obj); - - /* invoke entity_notify callbacks */ - list_for_each_entry_safe(notify, next, &mdev->entity_notify, list) - notify->notify(entity, notify->notify_data); - - if (mdev->entity_internal_idx_max - >= mdev->pm_count_walk.ent_enum.idx_max) { - struct media_graph new = { .top = 0 }; - - /* - * Initialise the new graph walk before cleaning up - * the old one in order not to spoil the graph walk - * object of the media device if graph walk init fails. - */ - ret = media_graph_walk_init(&new, mdev); - if (ret) { - mutex_unlock(&mdev->graph_mutex); - return ret; - } - media_graph_walk_cleanup(&mdev->pm_count_walk); - mdev->pm_count_walk = new; - } - mutex_unlock(&mdev->graph_mutex); - - return 0; -} -EXPORT_SYMBOL_GPL(media_device_register_entity); - -static void __media_device_unregister_entity(struct media_entity *entity) -{ - struct media_device *mdev = entity->graph_obj.mdev; - struct media_link *link, *tmp; - struct media_interface *intf; - unsigned int i; - - ida_free(&mdev->entity_internal_idx, entity->internal_idx); - - /* Remove all interface links pointing to this entity */ - list_for_each_entry(intf, &mdev->interfaces, graph_obj.list) { - list_for_each_entry_safe(link, tmp, &intf->links, list) { - if (link->entity == entity) - __media_remove_intf_link(link); - } - } - - /* Remove all data links that belong to this entity */ - __media_entity_remove_links(entity); - - /* Remove all pads that belong to this entity */ - for (i = 0; i < entity->num_pads; i++) - media_gobj_destroy(&entity->pads[i].graph_obj); - - /* Remove the entity */ - media_gobj_destroy(&entity->graph_obj); - - /* invoke entity_notify callbacks to handle entity removal?? */ - - entity->graph_obj.mdev = NULL; -} - -void media_device_unregister_entity(struct media_entity *entity) -{ - struct media_device *mdev = entity->graph_obj.mdev; - - if (mdev == NULL) - return; - - mutex_lock(&mdev->graph_mutex); - __media_device_unregister_entity(entity); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_device_unregister_entity); - -/** - * media_device_init() - initialize a media device - * @mdev: The media device - * - * The caller is responsible for initializing the media device before - * registration. The following fields must be set: - * - * - dev must point to the parent device - * - model must be filled with the device model name - */ -void media_device_init(struct media_device *mdev) -{ - INIT_LIST_HEAD(&mdev->entities); - INIT_LIST_HEAD(&mdev->interfaces); - INIT_LIST_HEAD(&mdev->pads); - INIT_LIST_HEAD(&mdev->links); - INIT_LIST_HEAD(&mdev->entity_notify); - - mutex_init(&mdev->req_queue_mutex); - mutex_init(&mdev->graph_mutex); - ida_init(&mdev->entity_internal_idx); - - atomic_set(&mdev->request_id, 0); - - dev_dbg(mdev->dev, "Media device initialized\n"); -} -EXPORT_SYMBOL_GPL(media_device_init); - -void media_device_cleanup(struct media_device *mdev) -{ - ida_destroy(&mdev->entity_internal_idx); - mdev->entity_internal_idx_max = 0; - media_graph_walk_cleanup(&mdev->pm_count_walk); - mutex_destroy(&mdev->graph_mutex); - mutex_destroy(&mdev->req_queue_mutex); -} -EXPORT_SYMBOL_GPL(media_device_cleanup); - -int __must_check __media_device_register(struct media_device *mdev, - struct module *owner) -{ - struct media_devnode *devnode; - int ret; - - devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); - if (!devnode) - return -ENOMEM; - - /* Register the device node. */ - mdev->devnode = devnode; - devnode->fops = &media_device_fops; - devnode->parent = mdev->dev; - devnode->release = media_device_release; - - /* Set version 0 to indicate user-space that the graph is static */ - mdev->topology_version = 0; - - ret = media_devnode_register(mdev, devnode, owner); - if (ret < 0) { - /* devnode free is handled in media_devnode_*() */ - mdev->devnode = NULL; - return ret; - } - - ret = device_create_file(&devnode->dev, &dev_attr_model); - if (ret < 0) { - /* devnode free is handled in media_devnode_*() */ - mdev->devnode = NULL; - media_devnode_unregister_prepare(devnode); - media_devnode_unregister(devnode); - return ret; - } - - dev_dbg(mdev->dev, "Media device registered\n"); - - return 0; -} -EXPORT_SYMBOL_GPL(__media_device_register); - -int __must_check media_device_register_entity_notify(struct media_device *mdev, - struct media_entity_notify *nptr) -{ - mutex_lock(&mdev->graph_mutex); - list_add_tail(&nptr->list, &mdev->entity_notify); - mutex_unlock(&mdev->graph_mutex); - return 0; -} -EXPORT_SYMBOL_GPL(media_device_register_entity_notify); - -/* - * Note: Should be called with mdev->lock held. - */ -static void __media_device_unregister_entity_notify(struct media_device *mdev, - struct media_entity_notify *nptr) -{ - list_del(&nptr->list); -} - -void media_device_unregister_entity_notify(struct media_device *mdev, - struct media_entity_notify *nptr) -{ - mutex_lock(&mdev->graph_mutex); - __media_device_unregister_entity_notify(mdev, nptr); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_device_unregister_entity_notify); - -void media_device_unregister(struct media_device *mdev) -{ - struct media_entity *entity; - struct media_entity *next; - struct media_interface *intf, *tmp_intf; - struct media_entity_notify *notify, *nextp; - - if (mdev == NULL) - return; - - mutex_lock(&mdev->graph_mutex); - - /* Check if mdev was ever registered at all */ - if (!media_devnode_is_registered(mdev->devnode)) { - mutex_unlock(&mdev->graph_mutex); - return; - } - - /* Clear the devnode register bit to avoid races with media dev open */ - media_devnode_unregister_prepare(mdev->devnode); - - /* Remove all entities from the media device */ - list_for_each_entry_safe(entity, next, &mdev->entities, graph_obj.list) - __media_device_unregister_entity(entity); - - /* Remove all entity_notify callbacks from the media device */ - list_for_each_entry_safe(notify, nextp, &mdev->entity_notify, list) - __media_device_unregister_entity_notify(mdev, notify); - - /* Remove all interfaces from the media device */ - list_for_each_entry_safe(intf, tmp_intf, &mdev->interfaces, - graph_obj.list) { - /* - * Unlink the interface, but don't free it here; the - * module which created it is responsible for freeing - * it - */ - __media_remove_intf_links(intf); - media_gobj_destroy(&intf->graph_obj); - } - - mutex_unlock(&mdev->graph_mutex); - - dev_dbg(mdev->dev, "Media device unregistered\n"); - - device_remove_file(&mdev->devnode->dev, &dev_attr_model); - media_devnode_unregister(mdev->devnode); - /* devnode free is handled in media_devnode_*() */ - mdev->devnode = NULL; -} -EXPORT_SYMBOL_GPL(media_device_unregister); - -#if IS_ENABLED(CONFIG_PCI) -void media_device_pci_init(struct media_device *mdev, - struct pci_dev *pci_dev, - const char *name) -{ - mdev->dev = &pci_dev->dev; - - if (name) - strscpy(mdev->model, name, sizeof(mdev->model)); - else - strscpy(mdev->model, pci_name(pci_dev), sizeof(mdev->model)); - - sprintf(mdev->bus_info, "PCI:%s", pci_name(pci_dev)); - - mdev->hw_revision = (pci_dev->subsystem_vendor << 16) - | pci_dev->subsystem_device; - - media_device_init(mdev); -} -EXPORT_SYMBOL_GPL(media_device_pci_init); -#endif - -#if IS_ENABLED(CONFIG_USB) -void __media_device_usb_init(struct media_device *mdev, - struct usb_device *udev, - const char *board_name, - const char *driver_name) -{ - mdev->dev = &udev->dev; - - if (driver_name) - strscpy(mdev->driver_name, driver_name, - sizeof(mdev->driver_name)); - - if (board_name) - strscpy(mdev->model, board_name, sizeof(mdev->model)); - else if (udev->product) - strscpy(mdev->model, udev->product, sizeof(mdev->model)); - else - strscpy(mdev->model, "unknown model", sizeof(mdev->model)); - if (udev->serial) - strscpy(mdev->serial, udev->serial, sizeof(mdev->serial)); - usb_make_path(udev, mdev->bus_info, sizeof(mdev->bus_info)); - mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); - - media_device_init(mdev); -} -EXPORT_SYMBOL_GPL(__media_device_usb_init); -#endif - - -#endif /* CONFIG_MEDIA_CONTROLLER */ diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c deleted file mode 100644 index d5aa30eeff4a..000000000000 --- a/drivers/media/media-devnode.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Media device node - * - * Copyright (C) 2010 Nokia Corporation - * - * Based on drivers/media/video/v4l2_dev.c code authored by - * Mauro Carvalho Chehab (version 2) - * Alan Cox, (version 1) - * - * Contacts: Laurent Pinchart - * Sakari Ailus - * - * 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. - * - * -- - * - * Generic media device node infrastructure to register and unregister - * character devices using a dynamic major number and proper reference - * counting. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define MEDIA_NUM_DEVICES 256 -#define MEDIA_NAME "media" - -static dev_t media_dev_t; - -/* - * Active devices - */ -static DEFINE_MUTEX(media_devnode_lock); -static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES); - -/* Called when the last user of the media device exits. */ -static void media_devnode_release(struct device *cd) -{ - struct media_devnode *devnode = to_media_devnode(cd); - - mutex_lock(&media_devnode_lock); - /* Mark device node number as free */ - clear_bit(devnode->minor, media_devnode_nums); - mutex_unlock(&media_devnode_lock); - - /* Release media_devnode and perform other cleanups as needed. */ - if (devnode->release) - devnode->release(devnode); - - kfree(devnode); - pr_debug("%s: Media Devnode Deallocated\n", __func__); -} - -static struct bus_type media_bus_type = { - .name = MEDIA_NAME, -}; - -static ssize_t media_read(struct file *filp, char __user *buf, - size_t sz, loff_t *off) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - if (!devnode->fops->read) - return -EINVAL; - if (!media_devnode_is_registered(devnode)) - return -EIO; - return devnode->fops->read(filp, buf, sz, off); -} - -static ssize_t media_write(struct file *filp, const char __user *buf, - size_t sz, loff_t *off) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - if (!devnode->fops->write) - return -EINVAL; - if (!media_devnode_is_registered(devnode)) - return -EIO; - return devnode->fops->write(filp, buf, sz, off); -} - -static __poll_t media_poll(struct file *filp, - struct poll_table_struct *poll) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - if (!media_devnode_is_registered(devnode)) - return EPOLLERR | EPOLLHUP; - if (!devnode->fops->poll) - return DEFAULT_POLLMASK; - return devnode->fops->poll(filp, poll); -} - -static long -__media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg, - long (*ioctl_func)(struct file *filp, unsigned int cmd, - unsigned long arg)) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - if (!ioctl_func) - return -ENOTTY; - - if (!media_devnode_is_registered(devnode)) - return -EIO; - - return ioctl_func(filp, cmd, arg); -} - -static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl); -} - -#ifdef CONFIG_COMPAT - -static long media_compat_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl); -} - -#endif /* CONFIG_COMPAT */ - -/* Override for the open function */ -static int media_open(struct inode *inode, struct file *filp) -{ - struct media_devnode *devnode; - int ret; - - /* Check if the media device is available. This needs to be done with - * the media_devnode_lock held to prevent an open/unregister race: - * without the lock, the device could be unregistered and freed between - * the media_devnode_is_registered() and get_device() calls, leading to - * a crash. - */ - mutex_lock(&media_devnode_lock); - devnode = container_of(inode->i_cdev, struct media_devnode, cdev); - /* return ENXIO if the media device has been removed - already or if it is not registered anymore. */ - if (!media_devnode_is_registered(devnode)) { - mutex_unlock(&media_devnode_lock); - return -ENXIO; - } - /* and increase the device refcount */ - get_device(&devnode->dev); - mutex_unlock(&media_devnode_lock); - - filp->private_data = devnode; - - if (devnode->fops->open) { - ret = devnode->fops->open(filp); - if (ret) { - put_device(&devnode->dev); - filp->private_data = NULL; - return ret; - } - } - - return 0; -} - -/* Override for the release function */ -static int media_release(struct inode *inode, struct file *filp) -{ - struct media_devnode *devnode = media_devnode_data(filp); - - if (devnode->fops->release) - devnode->fops->release(filp); - - filp->private_data = NULL; - - /* decrease the refcount unconditionally since the release() - return value is ignored. */ - put_device(&devnode->dev); - - pr_debug("%s: Media Release\n", __func__); - return 0; -} - -static const struct file_operations media_devnode_fops = { - .owner = THIS_MODULE, - .read = media_read, - .write = media_write, - .open = media_open, - .unlocked_ioctl = media_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = media_compat_ioctl, -#endif /* CONFIG_COMPAT */ - .release = media_release, - .poll = media_poll, - .llseek = no_llseek, -}; - -int __must_check media_devnode_register(struct media_device *mdev, - struct media_devnode *devnode, - struct module *owner) -{ - int minor; - int ret; - - /* Part 1: Find a free minor number */ - mutex_lock(&media_devnode_lock); - minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0); - if (minor == MEDIA_NUM_DEVICES) { - mutex_unlock(&media_devnode_lock); - pr_err("could not get a free minor\n"); - kfree(devnode); - return -ENFILE; - } - - set_bit(minor, media_devnode_nums); - mutex_unlock(&media_devnode_lock); - - devnode->minor = minor; - devnode->media_dev = mdev; - - /* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */ - devnode->dev.bus = &media_bus_type; - devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor); - devnode->dev.release = media_devnode_release; - if (devnode->parent) - devnode->dev.parent = devnode->parent; - dev_set_name(&devnode->dev, "media%d", devnode->minor); - device_initialize(&devnode->dev); - - /* Part 2: Initialize the character device */ - cdev_init(&devnode->cdev, &media_devnode_fops); - devnode->cdev.owner = owner; - kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor); - - /* Part 3: Add the media and char device */ - ret = cdev_device_add(&devnode->cdev, &devnode->dev); - if (ret < 0) { - pr_err("%s: cdev_device_add failed\n", __func__); - goto cdev_add_error; - } - - /* Part 4: Activate this minor. The char device can now be used. */ - set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); - - return 0; - -cdev_add_error: - mutex_lock(&media_devnode_lock); - clear_bit(devnode->minor, media_devnode_nums); - devnode->media_dev = NULL; - mutex_unlock(&media_devnode_lock); - - put_device(&devnode->dev); - return ret; -} - -void media_devnode_unregister_prepare(struct media_devnode *devnode) -{ - /* Check if devnode was ever registered at all */ - if (!media_devnode_is_registered(devnode)) - return; - - mutex_lock(&media_devnode_lock); - clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); - mutex_unlock(&media_devnode_lock); -} - -void media_devnode_unregister(struct media_devnode *devnode) -{ - mutex_lock(&media_devnode_lock); - /* Delete the cdev on this minor as well */ - cdev_device_del(&devnode->cdev, &devnode->dev); - devnode->media_dev = NULL; - mutex_unlock(&media_devnode_lock); - - put_device(&devnode->dev); -} - -/* - * Initialise media for linux - */ -static int __init media_devnode_init(void) -{ - int ret; - - pr_info("Linux media interface: v0.10\n"); - ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES, - MEDIA_NAME); - if (ret < 0) { - pr_warn("unable to allocate major\n"); - return ret; - } - - ret = bus_register(&media_bus_type); - if (ret < 0) { - unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); - pr_warn("bus_register failed\n"); - return -EIO; - } - - return 0; -} - -static void __exit media_devnode_exit(void) -{ - bus_unregister(&media_bus_type); - unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); -} - -subsys_initcall(media_devnode_init); -module_exit(media_devnode_exit) - -MODULE_AUTHOR("Laurent Pinchart "); -MODULE_DESCRIPTION("Device node registration for media drivers"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c deleted file mode 100644 index a998a2e0ea1d..000000000000 --- a/drivers/media/media-entity.c +++ /dev/null @@ -1,1036 +0,0 @@ -/* - * Media entity - * - * Copyright (C) 2010 Nokia Corporation - * - * Contacts: Laurent Pinchart - * Sakari Ailus - * - * 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 -#include -#include -#include -#include - -static inline const char *gobj_type(enum media_gobj_type type) -{ - switch (type) { - case MEDIA_GRAPH_ENTITY: - return "entity"; - case MEDIA_GRAPH_PAD: - return "pad"; - case MEDIA_GRAPH_LINK: - return "link"; - case MEDIA_GRAPH_INTF_DEVNODE: - return "intf-devnode"; - default: - return "unknown"; - } -} - -static inline const char *intf_type(struct media_interface *intf) -{ - switch (intf->type) { - case MEDIA_INTF_T_DVB_FE: - return "dvb-frontend"; - case MEDIA_INTF_T_DVB_DEMUX: - return "dvb-demux"; - case MEDIA_INTF_T_DVB_DVR: - return "dvb-dvr"; - case MEDIA_INTF_T_DVB_CA: - return "dvb-ca"; - case MEDIA_INTF_T_DVB_NET: - return "dvb-net"; - case MEDIA_INTF_T_V4L_VIDEO: - return "v4l-video"; - case MEDIA_INTF_T_V4L_VBI: - return "v4l-vbi"; - case MEDIA_INTF_T_V4L_RADIO: - return "v4l-radio"; - case MEDIA_INTF_T_V4L_SUBDEV: - return "v4l-subdev"; - case MEDIA_INTF_T_V4L_SWRADIO: - return "v4l-swradio"; - case MEDIA_INTF_T_V4L_TOUCH: - return "v4l-touch"; - default: - return "unknown-intf"; - } -}; - -__must_check int __media_entity_enum_init(struct media_entity_enum *ent_enum, - int idx_max) -{ - idx_max = ALIGN(idx_max, BITS_PER_LONG); - ent_enum->bmap = kcalloc(idx_max / BITS_PER_LONG, sizeof(long), - GFP_KERNEL); - if (!ent_enum->bmap) - return -ENOMEM; - - bitmap_zero(ent_enum->bmap, idx_max); - ent_enum->idx_max = idx_max; - - return 0; -} -EXPORT_SYMBOL_GPL(__media_entity_enum_init); - -void media_entity_enum_cleanup(struct media_entity_enum *ent_enum) -{ - kfree(ent_enum->bmap); -} -EXPORT_SYMBOL_GPL(media_entity_enum_cleanup); - -/** - * dev_dbg_obj - Prints in debug mode a change on some object - * - * @event_name: Name of the event to report. Could be __func__ - * @gobj: Pointer to the object - * - * Enabled only if DEBUG or CONFIG_DYNAMIC_DEBUG. Otherwise, it - * won't produce any code. - */ -static void dev_dbg_obj(const char *event_name, struct media_gobj *gobj) -{ -#if defined(DEBUG) || defined (CONFIG_DYNAMIC_DEBUG) - switch (media_type(gobj)) { - case MEDIA_GRAPH_ENTITY: - dev_dbg(gobj->mdev->dev, - "%s id %u: entity '%s'\n", - event_name, media_id(gobj), - gobj_to_entity(gobj)->name); - break; - case MEDIA_GRAPH_LINK: - { - struct media_link *link = gobj_to_link(gobj); - - dev_dbg(gobj->mdev->dev, - "%s id %u: %s link id %u ==> id %u\n", - event_name, media_id(gobj), - media_type(link->gobj0) == MEDIA_GRAPH_PAD ? - "data" : "interface", - media_id(link->gobj0), - media_id(link->gobj1)); - break; - } - case MEDIA_GRAPH_PAD: - { - struct media_pad *pad = gobj_to_pad(gobj); - - dev_dbg(gobj->mdev->dev, - "%s id %u: %s%spad '%s':%d\n", - event_name, media_id(gobj), - pad->flags & MEDIA_PAD_FL_SINK ? "sink " : "", - pad->flags & MEDIA_PAD_FL_SOURCE ? "source " : "", - pad->entity->name, pad->index); - break; - } - case MEDIA_GRAPH_INTF_DEVNODE: - { - struct media_interface *intf = gobj_to_intf(gobj); - struct media_intf_devnode *devnode = intf_to_devnode(intf); - - dev_dbg(gobj->mdev->dev, - "%s id %u: intf_devnode %s - major: %d, minor: %d\n", - event_name, media_id(gobj), - intf_type(intf), - devnode->major, devnode->minor); - break; - } - } -#endif -} - -void media_gobj_create(struct media_device *mdev, - enum media_gobj_type type, - struct media_gobj *gobj) -{ - BUG_ON(!mdev); - - gobj->mdev = mdev; - - /* Create a per-type unique object ID */ - gobj->id = media_gobj_gen_id(type, ++mdev->id); - - switch (type) { - case MEDIA_GRAPH_ENTITY: - list_add_tail(&gobj->list, &mdev->entities); - break; - case MEDIA_GRAPH_PAD: - list_add_tail(&gobj->list, &mdev->pads); - break; - case MEDIA_GRAPH_LINK: - list_add_tail(&gobj->list, &mdev->links); - break; - case MEDIA_GRAPH_INTF_DEVNODE: - list_add_tail(&gobj->list, &mdev->interfaces); - break; - } - - mdev->topology_version++; - - dev_dbg_obj(__func__, gobj); -} - -void media_gobj_destroy(struct media_gobj *gobj) -{ - /* Do nothing if the object is not linked. */ - if (gobj->mdev == NULL) - return; - - dev_dbg_obj(__func__, gobj); - - gobj->mdev->topology_version++; - - /* Remove the object from mdev list */ - list_del(&gobj->list); - - gobj->mdev = NULL; -} - -/* - * TODO: Get rid of this. - */ -#define MEDIA_ENTITY_MAX_PADS 512 - -int media_entity_pads_init(struct media_entity *entity, u16 num_pads, - struct media_pad *pads) -{ - struct media_device *mdev = entity->graph_obj.mdev; - unsigned int i; - - if (num_pads >= MEDIA_ENTITY_MAX_PADS) - return -E2BIG; - - entity->num_pads = num_pads; - entity->pads = pads; - - if (mdev) - mutex_lock(&mdev->graph_mutex); - - for (i = 0; i < num_pads; i++) { - pads[i].entity = entity; - pads[i].index = i; - if (mdev) - media_gobj_create(mdev, MEDIA_GRAPH_PAD, - &entity->pads[i].graph_obj); - } - - if (mdev) - mutex_unlock(&mdev->graph_mutex); - - return 0; -} -EXPORT_SYMBOL_GPL(media_entity_pads_init); - -/* ----------------------------------------------------------------------------- - * Graph traversal - */ - -static struct media_entity * -media_entity_other(struct media_entity *entity, struct media_link *link) -{ - if (link->source->entity == entity) - return link->sink->entity; - else - return link->source->entity; -} - -/* push an entity to traversal stack */ -static void stack_push(struct media_graph *graph, - struct media_entity *entity) -{ - if (graph->top == MEDIA_ENTITY_ENUM_MAX_DEPTH - 1) { - WARN_ON(1); - return; - } - graph->top++; - graph->stack[graph->top].link = entity->links.next; - graph->stack[graph->top].entity = entity; -} - -static struct media_entity *stack_pop(struct media_graph *graph) -{ - struct media_entity *entity; - - entity = graph->stack[graph->top].entity; - graph->top--; - - return entity; -} - -#define link_top(en) ((en)->stack[(en)->top].link) -#define stack_top(en) ((en)->stack[(en)->top].entity) - -/** - * media_graph_walk_init - Allocate resources for graph walk - * @graph: Media graph structure that will be used to walk the graph - * @mdev: Media device - * - * Reserve resources for graph walk in media device's current - * state. The memory must be released using - * media_graph_walk_free(). - * - * Returns error on failure, zero on success. - */ -__must_check int media_graph_walk_init( - struct media_graph *graph, struct media_device *mdev) -{ - return media_entity_enum_init(&graph->ent_enum, mdev); -} -EXPORT_SYMBOL_GPL(media_graph_walk_init); - -/** - * media_graph_walk_cleanup - Release resources related to graph walking - * @graph: Media graph structure that was used to walk the graph - */ -void media_graph_walk_cleanup(struct media_graph *graph) -{ - media_entity_enum_cleanup(&graph->ent_enum); -} -EXPORT_SYMBOL_GPL(media_graph_walk_cleanup); - -void media_graph_walk_start(struct media_graph *graph, - struct media_entity *entity) -{ - media_entity_enum_zero(&graph->ent_enum); - media_entity_enum_set(&graph->ent_enum, entity); - - graph->top = 0; - graph->stack[graph->top].entity = NULL; - stack_push(graph, entity); - dev_dbg(entity->graph_obj.mdev->dev, - "begin graph walk at '%s'\n", entity->name); -} -EXPORT_SYMBOL_GPL(media_graph_walk_start); - -static void media_graph_walk_iter(struct media_graph *graph) -{ - struct media_entity *entity = stack_top(graph); - struct media_link *link; - struct media_entity *next; - - link = list_entry(link_top(graph), typeof(*link), list); - - /* The link is not enabled so we do not follow. */ - if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { - link_top(graph) = link_top(graph)->next; - dev_dbg(entity->graph_obj.mdev->dev, - "walk: skipping disabled link '%s':%u -> '%s':%u\n", - link->source->entity->name, link->source->index, - link->sink->entity->name, link->sink->index); - return; - } - - /* Get the entity in the other end of the link . */ - next = media_entity_other(entity, link); - - /* Has the entity already been visited? */ - if (media_entity_enum_test_and_set(&graph->ent_enum, next)) { - link_top(graph) = link_top(graph)->next; - dev_dbg(entity->graph_obj.mdev->dev, - "walk: skipping entity '%s' (already seen)\n", - next->name); - return; - } - - /* Push the new entity to stack and start over. */ - link_top(graph) = link_top(graph)->next; - stack_push(graph, next); - dev_dbg(entity->graph_obj.mdev->dev, "walk: pushing '%s' on stack\n", - next->name); -} - -struct media_entity *media_graph_walk_next(struct media_graph *graph) -{ - struct media_entity *entity; - - if (stack_top(graph) == NULL) - return NULL; - - /* - * Depth first search. Push entity to stack and continue from - * top of the stack until no more entities on the level can be - * found. - */ - while (link_top(graph) != &stack_top(graph)->links) - media_graph_walk_iter(graph); - - entity = stack_pop(graph); - dev_dbg(entity->graph_obj.mdev->dev, - "walk: returning entity '%s'\n", entity->name); - - return entity; -} -EXPORT_SYMBOL_GPL(media_graph_walk_next); - -int media_entity_get_fwnode_pad(struct media_entity *entity, - struct fwnode_handle *fwnode, - unsigned long direction_flags) -{ - struct fwnode_endpoint endpoint; - unsigned int i; - int ret; - - if (!entity->ops || !entity->ops->get_fwnode_pad) { - for (i = 0; i < entity->num_pads; i++) { - if (entity->pads[i].flags & direction_flags) - return i; - } - - return -ENXIO; - } - - ret = fwnode_graph_parse_endpoint(fwnode, &endpoint); - if (ret) - return ret; - - ret = entity->ops->get_fwnode_pad(&endpoint); - if (ret < 0) - return ret; - - if (ret >= entity->num_pads) - return -ENXIO; - - if (!(entity->pads[ret].flags & direction_flags)) - return -ENXIO; - - return ret; -} -EXPORT_SYMBOL_GPL(media_entity_get_fwnode_pad); - -/* ----------------------------------------------------------------------------- - * Pipeline management - */ - -__must_check int __media_pipeline_start(struct media_entity *entity, - struct media_pipeline *pipe) -{ - struct media_device *mdev = entity->graph_obj.mdev; - struct media_graph *graph = &pipe->graph; - struct media_entity *entity_err = entity; - struct media_link *link; - int ret; - - if (!pipe->streaming_count++) { - ret = media_graph_walk_init(&pipe->graph, mdev); - if (ret) - goto error_graph_walk_start; - } - - media_graph_walk_start(&pipe->graph, entity); - - while ((entity = media_graph_walk_next(graph))) { - DECLARE_BITMAP(active, MEDIA_ENTITY_MAX_PADS); - DECLARE_BITMAP(has_no_links, MEDIA_ENTITY_MAX_PADS); - - entity->stream_count++; - - if (entity->pipe && entity->pipe != pipe) { - pr_err("Pipe active for %s. Can't start for %s\n", - entity->name, - entity_err->name); - ret = -EBUSY; - goto error; - } - - entity->pipe = pipe; - - /* Already streaming --- no need to check. */ - if (entity->stream_count > 1) - continue; - - if (!entity->ops || !entity->ops->link_validate) - continue; - - bitmap_zero(active, entity->num_pads); - bitmap_fill(has_no_links, entity->num_pads); - - list_for_each_entry(link, &entity->links, list) { - struct media_pad *pad = link->sink->entity == entity - ? link->sink : link->source; - - /* Mark that a pad is connected by a link. */ - bitmap_clear(has_no_links, pad->index, 1); - - /* - * Pads that either do not need to connect or - * are connected through an enabled link are - * fine. - */ - if (!(pad->flags & MEDIA_PAD_FL_MUST_CONNECT) || - link->flags & MEDIA_LNK_FL_ENABLED) - bitmap_set(active, pad->index, 1); - - /* - * Link validation will only take place for - * sink ends of the link that are enabled. - */ - if (link->sink != pad || - !(link->flags & MEDIA_LNK_FL_ENABLED)) - continue; - - ret = entity->ops->link_validate(link); - if (ret < 0 && ret != -ENOIOCTLCMD) { - dev_dbg(entity->graph_obj.mdev->dev, - "link validation failed for '%s':%u -> '%s':%u, error %d\n", - link->source->entity->name, - link->source->index, - entity->name, link->sink->index, ret); - goto error; - } - } - - /* Either no links or validated links are fine. */ - bitmap_or(active, active, has_no_links, entity->num_pads); - - if (!bitmap_full(active, entity->num_pads)) { - ret = -ENOLINK; - dev_dbg(entity->graph_obj.mdev->dev, - "'%s':%u must be connected by an enabled link\n", - entity->name, - (unsigned)find_first_zero_bit( - active, entity->num_pads)); - goto error; - } - } - - return 0; - -error: - /* - * Link validation on graph failed. We revert what we did and - * return the error. - */ - media_graph_walk_start(graph, entity_err); - - while ((entity_err = media_graph_walk_next(graph))) { - /* Sanity check for negative stream_count */ - if (!WARN_ON_ONCE(entity_err->stream_count <= 0)) { - entity_err->stream_count--; - if (entity_err->stream_count == 0) - entity_err->pipe = NULL; - } - - /* - * We haven't increased stream_count further than this - * so we quit here. - */ - if (entity_err == entity) - break; - } - -error_graph_walk_start: - if (!--pipe->streaming_count) - media_graph_walk_cleanup(graph); - - return ret; -} -EXPORT_SYMBOL_GPL(__media_pipeline_start); - -__must_check int media_pipeline_start(struct media_entity *entity, - struct media_pipeline *pipe) -{ - struct media_device *mdev = entity->graph_obj.mdev; - int ret; - - mutex_lock(&mdev->graph_mutex); - ret = __media_pipeline_start(entity, pipe); - mutex_unlock(&mdev->graph_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(media_pipeline_start); - -void __media_pipeline_stop(struct media_entity *entity) -{ - struct media_graph *graph = &entity->pipe->graph; - struct media_pipeline *pipe = entity->pipe; - - /* - * If the following check fails, the driver has performed an - * unbalanced call to media_pipeline_stop() - */ - if (WARN_ON(!pipe)) - return; - - media_graph_walk_start(graph, entity); - - while ((entity = media_graph_walk_next(graph))) { - /* Sanity check for negative stream_count */ - if (!WARN_ON_ONCE(entity->stream_count <= 0)) { - entity->stream_count--; - if (entity->stream_count == 0) - entity->pipe = NULL; - } - } - - if (!--pipe->streaming_count) - media_graph_walk_cleanup(graph); - -} -EXPORT_SYMBOL_GPL(__media_pipeline_stop); - -void media_pipeline_stop(struct media_entity *entity) -{ - struct media_device *mdev = entity->graph_obj.mdev; - - mutex_lock(&mdev->graph_mutex); - __media_pipeline_stop(entity); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_pipeline_stop); - -/* ----------------------------------------------------------------------------- - * Links management - */ - -static struct media_link *media_add_link(struct list_head *head) -{ - struct media_link *link; - - link = kzalloc(sizeof(*link), GFP_KERNEL); - if (link == NULL) - return NULL; - - list_add_tail(&link->list, head); - - return link; -} - -static void __media_entity_remove_link(struct media_entity *entity, - struct media_link *link) -{ - struct media_link *rlink, *tmp; - struct media_entity *remote; - - if (link->source->entity == entity) - remote = link->sink->entity; - else - remote = link->source->entity; - - list_for_each_entry_safe(rlink, tmp, &remote->links, list) { - if (rlink != link->reverse) - continue; - - if (link->source->entity == entity) - remote->num_backlinks--; - - /* Remove the remote link */ - list_del(&rlink->list); - media_gobj_destroy(&rlink->graph_obj); - kfree(rlink); - - if (--remote->num_links == 0) - break; - } - list_del(&link->list); - media_gobj_destroy(&link->graph_obj); - kfree(link); -} - -int media_get_pad_index(struct media_entity *entity, bool is_sink, - enum media_pad_signal_type sig_type) -{ - int i; - bool pad_is_sink; - - if (!entity) - return -EINVAL; - - for (i = 0; i < entity->num_pads; i++) { - if (entity->pads[i].flags == MEDIA_PAD_FL_SINK) - pad_is_sink = true; - else if (entity->pads[i].flags == MEDIA_PAD_FL_SOURCE) - pad_is_sink = false; - else - continue; /* This is an error! */ - - if (pad_is_sink != is_sink) - continue; - if (entity->pads[i].sig_type == sig_type) - return i; - } - return -EINVAL; -} -EXPORT_SYMBOL_GPL(media_get_pad_index); - -int -media_create_pad_link(struct media_entity *source, u16 source_pad, - struct media_entity *sink, u16 sink_pad, u32 flags) -{ - struct media_link *link; - struct media_link *backlink; - - BUG_ON(source == NULL || sink == NULL); - BUG_ON(source_pad >= source->num_pads); - BUG_ON(sink_pad >= sink->num_pads); - - link = media_add_link(&source->links); - if (link == NULL) - return -ENOMEM; - - link->source = &source->pads[source_pad]; - link->sink = &sink->pads[sink_pad]; - link->flags = flags & ~MEDIA_LNK_FL_INTERFACE_LINK; - - /* Initialize graph object embedded at the new link */ - media_gobj_create(source->graph_obj.mdev, MEDIA_GRAPH_LINK, - &link->graph_obj); - - /* Create the backlink. Backlinks are used to help graph traversal and - * are not reported to userspace. - */ - backlink = media_add_link(&sink->links); - if (backlink == NULL) { - __media_entity_remove_link(source, link); - return -ENOMEM; - } - - backlink->source = &source->pads[source_pad]; - backlink->sink = &sink->pads[sink_pad]; - backlink->flags = flags; - backlink->is_backlink = true; - - /* Initialize graph object embedded at the new link */ - media_gobj_create(sink->graph_obj.mdev, MEDIA_GRAPH_LINK, - &backlink->graph_obj); - - link->reverse = backlink; - backlink->reverse = link; - - sink->num_backlinks++; - sink->num_links++; - source->num_links++; - - return 0; -} -EXPORT_SYMBOL_GPL(media_create_pad_link); - -int media_create_pad_links(const struct media_device *mdev, - const u32 source_function, - struct media_entity *source, - const u16 source_pad, - const u32 sink_function, - struct media_entity *sink, - const u16 sink_pad, - u32 flags, - const bool allow_both_undefined) -{ - struct media_entity *entity; - unsigned function; - int ret; - - /* Trivial case: 1:1 relation */ - if (source && sink) - return media_create_pad_link(source, source_pad, - sink, sink_pad, flags); - - /* Worse case scenario: n:n relation */ - if (!source && !sink) { - if (!allow_both_undefined) - return 0; - media_device_for_each_entity(source, mdev) { - if (source->function != source_function) - continue; - media_device_for_each_entity(sink, mdev) { - if (sink->function != sink_function) - continue; - ret = media_create_pad_link(source, source_pad, - sink, sink_pad, - flags); - if (ret) - return ret; - flags &= ~(MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); - } - } - return 0; - } - - /* Handle 1:n and n:1 cases */ - if (source) - function = sink_function; - else - function = source_function; - - media_device_for_each_entity(entity, mdev) { - if (entity->function != function) - continue; - - if (source) - ret = media_create_pad_link(source, source_pad, - entity, sink_pad, flags); - else - ret = media_create_pad_link(entity, source_pad, - sink, sink_pad, flags); - if (ret) - return ret; - flags &= ~(MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); - } - return 0; -} -EXPORT_SYMBOL_GPL(media_create_pad_links); - -void __media_entity_remove_links(struct media_entity *entity) -{ - struct media_link *link, *tmp; - - list_for_each_entry_safe(link, tmp, &entity->links, list) - __media_entity_remove_link(entity, link); - - entity->num_links = 0; - entity->num_backlinks = 0; -} -EXPORT_SYMBOL_GPL(__media_entity_remove_links); - -void media_entity_remove_links(struct media_entity *entity) -{ - struct media_device *mdev = entity->graph_obj.mdev; - - /* Do nothing if the entity is not registered. */ - if (mdev == NULL) - return; - - mutex_lock(&mdev->graph_mutex); - __media_entity_remove_links(entity); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_entity_remove_links); - -static int __media_entity_setup_link_notify(struct media_link *link, u32 flags) -{ - int ret; - - /* Notify both entities. */ - ret = media_entity_call(link->source->entity, link_setup, - link->source, link->sink, flags); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - - ret = media_entity_call(link->sink->entity, link_setup, - link->sink, link->source, flags); - if (ret < 0 && ret != -ENOIOCTLCMD) { - media_entity_call(link->source->entity, link_setup, - link->source, link->sink, link->flags); - return ret; - } - - link->flags = flags; - link->reverse->flags = link->flags; - - return 0; -} - -int __media_entity_setup_link(struct media_link *link, u32 flags) -{ - const u32 mask = MEDIA_LNK_FL_ENABLED; - struct media_device *mdev; - struct media_entity *source, *sink; - int ret = -EBUSY; - - if (link == NULL) - return -EINVAL; - - /* The non-modifiable link flags must not be modified. */ - if ((link->flags & ~mask) != (flags & ~mask)) - return -EINVAL; - - if (link->flags & MEDIA_LNK_FL_IMMUTABLE) - return link->flags == flags ? 0 : -EINVAL; - - if (link->flags == flags) - return 0; - - source = link->source->entity; - sink = link->sink->entity; - - if (!(link->flags & MEDIA_LNK_FL_DYNAMIC) && - (source->stream_count || sink->stream_count)) - return -EBUSY; - - mdev = source->graph_obj.mdev; - - if (mdev->ops && mdev->ops->link_notify) { - ret = mdev->ops->link_notify(link, flags, - MEDIA_DEV_NOTIFY_PRE_LINK_CH); - if (ret < 0) - return ret; - } - - ret = __media_entity_setup_link_notify(link, flags); - - if (mdev->ops && mdev->ops->link_notify) - mdev->ops->link_notify(link, flags, - MEDIA_DEV_NOTIFY_POST_LINK_CH); - - return ret; -} -EXPORT_SYMBOL_GPL(__media_entity_setup_link); - -int media_entity_setup_link(struct media_link *link, u32 flags) -{ - int ret; - - mutex_lock(&link->graph_obj.mdev->graph_mutex); - ret = __media_entity_setup_link(link, flags); - mutex_unlock(&link->graph_obj.mdev->graph_mutex); - - return ret; -} -EXPORT_SYMBOL_GPL(media_entity_setup_link); - -struct media_link * -media_entity_find_link(struct media_pad *source, struct media_pad *sink) -{ - struct media_link *link; - - list_for_each_entry(link, &source->entity->links, list) { - if (link->source->entity == source->entity && - link->source->index == source->index && - link->sink->entity == sink->entity && - link->sink->index == sink->index) - return link; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(media_entity_find_link); - -struct media_pad *media_entity_remote_pad(const struct media_pad *pad) -{ - struct media_link *link; - - list_for_each_entry(link, &pad->entity->links, list) { - if (!(link->flags & MEDIA_LNK_FL_ENABLED)) - continue; - - if (link->source == pad) - return link->sink; - - if (link->sink == pad) - return link->source; - } - - return NULL; - -} -EXPORT_SYMBOL_GPL(media_entity_remote_pad); - -static void media_interface_init(struct media_device *mdev, - struct media_interface *intf, - u32 gobj_type, - u32 intf_type, u32 flags) -{ - intf->type = intf_type; - intf->flags = flags; - INIT_LIST_HEAD(&intf->links); - - media_gobj_create(mdev, gobj_type, &intf->graph_obj); -} - -/* Functions related to the media interface via device nodes */ - -struct media_intf_devnode *media_devnode_create(struct media_device *mdev, - u32 type, u32 flags, - u32 major, u32 minor) -{ - struct media_intf_devnode *devnode; - - devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); - if (!devnode) - return NULL; - - devnode->major = major; - devnode->minor = minor; - - media_interface_init(mdev, &devnode->intf, MEDIA_GRAPH_INTF_DEVNODE, - type, flags); - - return devnode; -} -EXPORT_SYMBOL_GPL(media_devnode_create); - -void media_devnode_remove(struct media_intf_devnode *devnode) -{ - media_remove_intf_links(&devnode->intf); - media_gobj_destroy(&devnode->intf.graph_obj); - kfree(devnode); -} -EXPORT_SYMBOL_GPL(media_devnode_remove); - -struct media_link *media_create_intf_link(struct media_entity *entity, - struct media_interface *intf, - u32 flags) -{ - struct media_link *link; - - link = media_add_link(&intf->links); - if (link == NULL) - return NULL; - - link->intf = intf; - link->entity = entity; - link->flags = flags | MEDIA_LNK_FL_INTERFACE_LINK; - - /* Initialize graph object embedded at the new link */ - media_gobj_create(intf->graph_obj.mdev, MEDIA_GRAPH_LINK, - &link->graph_obj); - - return link; -} -EXPORT_SYMBOL_GPL(media_create_intf_link); - -void __media_remove_intf_link(struct media_link *link) -{ - list_del(&link->list); - media_gobj_destroy(&link->graph_obj); - kfree(link); -} -EXPORT_SYMBOL_GPL(__media_remove_intf_link); - -void media_remove_intf_link(struct media_link *link) -{ - struct media_device *mdev = link->graph_obj.mdev; - - /* Do nothing if the intf is not registered. */ - if (mdev == NULL) - return; - - mutex_lock(&mdev->graph_mutex); - __media_remove_intf_link(link); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_remove_intf_link); - -void __media_remove_intf_links(struct media_interface *intf) -{ - struct media_link *link, *tmp; - - list_for_each_entry_safe(link, tmp, &intf->links, list) - __media_remove_intf_link(link); - -} -EXPORT_SYMBOL_GPL(__media_remove_intf_links); - -void media_remove_intf_links(struct media_interface *intf) -{ - struct media_device *mdev = intf->graph_obj.mdev; - - /* Do nothing if the intf is not registered. */ - if (mdev == NULL) - return; - - mutex_lock(&mdev->graph_mutex); - __media_remove_intf_links(intf); - mutex_unlock(&mdev->graph_mutex); -} -EXPORT_SYMBOL_GPL(media_remove_intf_links); diff --git a/drivers/media/media-request.c b/drivers/media/media-request.c deleted file mode 100644 index e3fca436c75b..000000000000 --- a/drivers/media/media-request.c +++ /dev/null @@ -1,503 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Media device request objects - * - * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * Copyright (C) 2018 Intel Corporation - * Copyright (C) 2018 Google, Inc. - * - * Author: Hans Verkuil - * Author: Sakari Ailus - */ - -#include -#include -#include - -#include -#include - -static const char * const request_state[] = { - [MEDIA_REQUEST_STATE_IDLE] = "idle", - [MEDIA_REQUEST_STATE_VALIDATING] = "validating", - [MEDIA_REQUEST_STATE_QUEUED] = "queued", - [MEDIA_REQUEST_STATE_COMPLETE] = "complete", - [MEDIA_REQUEST_STATE_CLEANING] = "cleaning", - [MEDIA_REQUEST_STATE_UPDATING] = "updating", -}; - -static const char * -media_request_state_str(enum media_request_state state) -{ - BUILD_BUG_ON(ARRAY_SIZE(request_state) != NR_OF_MEDIA_REQUEST_STATE); - - if (WARN_ON(state >= ARRAY_SIZE(request_state))) - return "invalid"; - return request_state[state]; -} - -static void media_request_clean(struct media_request *req) -{ - struct media_request_object *obj, *obj_safe; - - /* Just a sanity check. No other code path is allowed to change this. */ - WARN_ON(req->state != MEDIA_REQUEST_STATE_CLEANING); - WARN_ON(req->updating_count); - WARN_ON(req->access_count); - - list_for_each_entry_safe(obj, obj_safe, &req->objects, list) { - media_request_object_unbind(obj); - media_request_object_put(obj); - } - - req->updating_count = 0; - req->access_count = 0; - WARN_ON(req->num_incomplete_objects); - req->num_incomplete_objects = 0; - wake_up_interruptible_all(&req->poll_wait); -} - -static void media_request_release(struct kref *kref) -{ - struct media_request *req = - container_of(kref, struct media_request, kref); - struct media_device *mdev = req->mdev; - - dev_dbg(mdev->dev, "request: release %s\n", req->debug_str); - - /* No other users, no need for a spinlock */ - req->state = MEDIA_REQUEST_STATE_CLEANING; - - media_request_clean(req); - - if (mdev->ops->req_free) - mdev->ops->req_free(req); - else - kfree(req); -} - -void media_request_put(struct media_request *req) -{ - kref_put(&req->kref, media_request_release); -} -EXPORT_SYMBOL_GPL(media_request_put); - -static int media_request_close(struct inode *inode, struct file *filp) -{ - struct media_request *req = filp->private_data; - - media_request_put(req); - return 0; -} - -static __poll_t media_request_poll(struct file *filp, - struct poll_table_struct *wait) -{ - struct media_request *req = filp->private_data; - unsigned long flags; - __poll_t ret = 0; - - if (!(poll_requested_events(wait) & EPOLLPRI)) - return 0; - - poll_wait(filp, &req->poll_wait, wait); - spin_lock_irqsave(&req->lock, flags); - if (req->state == MEDIA_REQUEST_STATE_COMPLETE) { - ret = EPOLLPRI; - goto unlock; - } - if (req->state != MEDIA_REQUEST_STATE_QUEUED) { - ret = EPOLLERR; - goto unlock; - } - -unlock: - spin_unlock_irqrestore(&req->lock, flags); - return ret; -} - -static long media_request_ioctl_queue(struct media_request *req) -{ - struct media_device *mdev = req->mdev; - enum media_request_state state; - unsigned long flags; - int ret; - - dev_dbg(mdev->dev, "request: queue %s\n", req->debug_str); - - /* - * Ensure the request that is validated will be the one that gets queued - * next by serialising the queueing process. This mutex is also used - * to serialize with canceling a vb2 queue and with setting values such - * as controls in a request. - */ - mutex_lock(&mdev->req_queue_mutex); - - media_request_get(req); - - spin_lock_irqsave(&req->lock, flags); - if (req->state == MEDIA_REQUEST_STATE_IDLE) - req->state = MEDIA_REQUEST_STATE_VALIDATING; - state = req->state; - spin_unlock_irqrestore(&req->lock, flags); - if (state != MEDIA_REQUEST_STATE_VALIDATING) { - dev_dbg(mdev->dev, - "request: unable to queue %s, request in state %s\n", - req->debug_str, media_request_state_str(state)); - media_request_put(req); - mutex_unlock(&mdev->req_queue_mutex); - return -EBUSY; - } - - ret = mdev->ops->req_validate(req); - - /* - * If the req_validate was successful, then we mark the state as QUEUED - * and call req_queue. The reason we set the state first is that this - * allows req_queue to unbind or complete the queued objects in case - * they are immediately 'consumed'. State changes from QUEUED to another - * state can only happen if either the driver changes the state or if - * the user cancels the vb2 queue. The driver can only change the state - * after each object is queued through the req_queue op (and note that - * that op cannot fail), so setting the state to QUEUED up front is - * safe. - * - * The other reason for changing the state is if the vb2 queue is - * canceled, and that uses the req_queue_mutex which is still locked - * while req_queue is called, so that's safe as well. - */ - spin_lock_irqsave(&req->lock, flags); - req->state = ret ? MEDIA_REQUEST_STATE_IDLE - : MEDIA_REQUEST_STATE_QUEUED; - spin_unlock_irqrestore(&req->lock, flags); - - if (!ret) - mdev->ops->req_queue(req); - - mutex_unlock(&mdev->req_queue_mutex); - - if (ret) { - dev_dbg(mdev->dev, "request: can't queue %s (%d)\n", - req->debug_str, ret); - media_request_put(req); - } - - return ret; -} - -static long media_request_ioctl_reinit(struct media_request *req) -{ - struct media_device *mdev = req->mdev; - unsigned long flags; - - spin_lock_irqsave(&req->lock, flags); - if (req->state != MEDIA_REQUEST_STATE_IDLE && - req->state != MEDIA_REQUEST_STATE_COMPLETE) { - dev_dbg(mdev->dev, - "request: %s not in idle or complete state, cannot reinit\n", - req->debug_str); - spin_unlock_irqrestore(&req->lock, flags); - return -EBUSY; - } - if (req->access_count) { - dev_dbg(mdev->dev, - "request: %s is being accessed, cannot reinit\n", - req->debug_str); - spin_unlock_irqrestore(&req->lock, flags); - return -EBUSY; - } - req->state = MEDIA_REQUEST_STATE_CLEANING; - spin_unlock_irqrestore(&req->lock, flags); - - media_request_clean(req); - - spin_lock_irqsave(&req->lock, flags); - req->state = MEDIA_REQUEST_STATE_IDLE; - spin_unlock_irqrestore(&req->lock, flags); - - return 0; -} - -static long media_request_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - struct media_request *req = filp->private_data; - - switch (cmd) { - case MEDIA_REQUEST_IOC_QUEUE: - return media_request_ioctl_queue(req); - case MEDIA_REQUEST_IOC_REINIT: - return media_request_ioctl_reinit(req); - default: - return -ENOIOCTLCMD; - } -} - -static const struct file_operations request_fops = { - .owner = THIS_MODULE, - .poll = media_request_poll, - .unlocked_ioctl = media_request_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = media_request_ioctl, -#endif /* CONFIG_COMPAT */ - .release = media_request_close, -}; - -struct media_request * -media_request_get_by_fd(struct media_device *mdev, int request_fd) -{ - struct fd f; - struct media_request *req; - - if (!mdev || !mdev->ops || - !mdev->ops->req_validate || !mdev->ops->req_queue) - return ERR_PTR(-EBADR); - - f = fdget(request_fd); - if (!f.file) - goto err_no_req_fd; - - if (f.file->f_op != &request_fops) - goto err_fput; - req = f.file->private_data; - if (req->mdev != mdev) - goto err_fput; - - /* - * Note: as long as someone has an open filehandle of the request, - * the request can never be released. The fdget() above ensures that - * even if userspace closes the request filehandle, the release() - * fop won't be called, so the media_request_get() always succeeds - * and there is no race condition where the request was released - * before media_request_get() is called. - */ - media_request_get(req); - fdput(f); - - return req; - -err_fput: - fdput(f); - -err_no_req_fd: - dev_dbg(mdev->dev, "cannot find request_fd %d\n", request_fd); - return ERR_PTR(-EINVAL); -} -EXPORT_SYMBOL_GPL(media_request_get_by_fd); - -int media_request_alloc(struct media_device *mdev, int *alloc_fd) -{ - struct media_request *req; - struct file *filp; - int fd; - int ret; - - /* Either both are NULL or both are non-NULL */ - if (WARN_ON(!mdev->ops->req_alloc ^ !mdev->ops->req_free)) - return -ENOMEM; - - fd = get_unused_fd_flags(O_CLOEXEC); - if (fd < 0) - return fd; - - filp = anon_inode_getfile("request", &request_fops, NULL, O_CLOEXEC); - if (IS_ERR(filp)) { - ret = PTR_ERR(filp); - goto err_put_fd; - } - - if (mdev->ops->req_alloc) - req = mdev->ops->req_alloc(mdev); - else - req = kzalloc(sizeof(*req), GFP_KERNEL); - if (!req) { - ret = -ENOMEM; - goto err_fput; - } - - filp->private_data = req; - req->mdev = mdev; - req->state = MEDIA_REQUEST_STATE_IDLE; - req->num_incomplete_objects = 0; - kref_init(&req->kref); - INIT_LIST_HEAD(&req->objects); - spin_lock_init(&req->lock); - init_waitqueue_head(&req->poll_wait); - req->updating_count = 0; - req->access_count = 0; - - *alloc_fd = fd; - - snprintf(req->debug_str, sizeof(req->debug_str), "%u:%d", - atomic_inc_return(&mdev->request_id), fd); - dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str); - - fd_install(fd, filp); - - return 0; - -err_fput: - fput(filp); - -err_put_fd: - put_unused_fd(fd); - - return ret; -} - -static void media_request_object_release(struct kref *kref) -{ - struct media_request_object *obj = - container_of(kref, struct media_request_object, kref); - struct media_request *req = obj->req; - - if (WARN_ON(req)) - media_request_object_unbind(obj); - obj->ops->release(obj); -} - -struct media_request_object * -media_request_object_find(struct media_request *req, - const struct media_request_object_ops *ops, - void *priv) -{ - struct media_request_object *obj; - struct media_request_object *found = NULL; - unsigned long flags; - - if (WARN_ON(!ops || !priv)) - return NULL; - - spin_lock_irqsave(&req->lock, flags); - list_for_each_entry(obj, &req->objects, list) { - if (obj->ops == ops && obj->priv == priv) { - media_request_object_get(obj); - found = obj; - break; - } - } - spin_unlock_irqrestore(&req->lock, flags); - return found; -} -EXPORT_SYMBOL_GPL(media_request_object_find); - -void media_request_object_put(struct media_request_object *obj) -{ - kref_put(&obj->kref, media_request_object_release); -} -EXPORT_SYMBOL_GPL(media_request_object_put); - -void media_request_object_init(struct media_request_object *obj) -{ - obj->ops = NULL; - obj->req = NULL; - obj->priv = NULL; - obj->completed = false; - INIT_LIST_HEAD(&obj->list); - kref_init(&obj->kref); -} -EXPORT_SYMBOL_GPL(media_request_object_init); - -int media_request_object_bind(struct media_request *req, - const struct media_request_object_ops *ops, - void *priv, bool is_buffer, - struct media_request_object *obj) -{ - unsigned long flags; - int ret = -EBUSY; - - if (WARN_ON(!ops->release)) - return -EBADR; - - spin_lock_irqsave(&req->lock, flags); - - if (WARN_ON(req->state != MEDIA_REQUEST_STATE_UPDATING)) - goto unlock; - - obj->req = req; - obj->ops = ops; - obj->priv = priv; - - if (is_buffer) - list_add_tail(&obj->list, &req->objects); - else - list_add(&obj->list, &req->objects); - req->num_incomplete_objects++; - ret = 0; - -unlock: - spin_unlock_irqrestore(&req->lock, flags); - return ret; -} -EXPORT_SYMBOL_GPL(media_request_object_bind); - -void media_request_object_unbind(struct media_request_object *obj) -{ - struct media_request *req = obj->req; - unsigned long flags; - bool completed = false; - - if (WARN_ON(!req)) - return; - - spin_lock_irqsave(&req->lock, flags); - list_del(&obj->list); - obj->req = NULL; - - if (req->state == MEDIA_REQUEST_STATE_COMPLETE) - goto unlock; - - if (WARN_ON(req->state == MEDIA_REQUEST_STATE_VALIDATING)) - goto unlock; - - if (req->state == MEDIA_REQUEST_STATE_CLEANING) { - if (!obj->completed) - req->num_incomplete_objects--; - goto unlock; - } - - if (WARN_ON(!req->num_incomplete_objects)) - goto unlock; - - req->num_incomplete_objects--; - if (req->state == MEDIA_REQUEST_STATE_QUEUED && - !req->num_incomplete_objects) { - req->state = MEDIA_REQUEST_STATE_COMPLETE; - completed = true; - wake_up_interruptible_all(&req->poll_wait); - } - -unlock: - spin_unlock_irqrestore(&req->lock, flags); - if (obj->ops->unbind) - obj->ops->unbind(obj); - if (completed) - media_request_put(req); -} -EXPORT_SYMBOL_GPL(media_request_object_unbind); - -void media_request_object_complete(struct media_request_object *obj) -{ - struct media_request *req = obj->req; - unsigned long flags; - bool completed = false; - - spin_lock_irqsave(&req->lock, flags); - if (obj->completed) - goto unlock; - obj->completed = true; - if (WARN_ON(!req->num_incomplete_objects) || - WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED)) - goto unlock; - - if (!--req->num_incomplete_objects) { - req->state = MEDIA_REQUEST_STATE_COMPLETE; - wake_up_interruptible_all(&req->poll_wait); - completed = true; - } -unlock: - spin_unlock_irqrestore(&req->lock, flags); - if (completed) - media_request_put(req); -} -EXPORT_SYMBOL_GPL(media_request_object_complete);