summaryrefslogtreecommitdiffstats
path: root/drivers/media
diff options
context:
space:
mode:
authorShuah Khan <shuah@kernel.org>2019-04-01 20:40:19 -0400
committerMauro Carvalho Chehab <mchehab+samsung@kernel.org>2019-04-22 11:18:26 -0400
commit6e1d824e7a1d324f7fdd276fb7133013109d3764 (patch)
treedd135d8fd4f43d2375e1716eb0b6cb0dfc055ee7 /drivers/media
parent33dfeb62e23c31619d2197850f7e8b50e8cc5466 (diff)
media: Media Device Allocator API
Media Device Allocator API to allows multiple drivers share a media device. This API solves a very common use-case for media devices where one physical device (an USB stick) provides both audio and video. When such media device exposes a standard USB Audio class, a proprietary Video class, two or more independent drivers will share a single physical USB bridge. In such cases, it is necessary to coordinate access to the shared resource. Using this API, drivers can allocate a media device with the shared struct device as the key. Once the media device is allocated by a driver, other drivers can get a reference to it. The media device is released when all the references are released. Signed-off-by: Shuah Khan <shuah@kernel.org> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
Diffstat (limited to 'drivers/media')
-rw-r--r--drivers/media/Makefile6
-rw-r--r--drivers/media/media-dev-allocator.c135
2 files changed, 141 insertions, 0 deletions
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 985d35ec6b29..4a330d0e5e40 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -6,6 +6,12 @@
6media-objs := media-device.o media-devnode.o media-entity.o \ 6media-objs := media-device.o media-devnode.o media-entity.o \
7 media-request.o 7 media-request.o
8 8
9ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
10 ifeq ($(CONFIG_USB),y)
11 media-objs += media-dev-allocator.o
12 endif
13endif
14
9# 15#
10# I2C drivers should come before other drivers, otherwise they'll fail 16# I2C drivers should come before other drivers, otherwise they'll fail
11# when compiled as builtin drivers 17# when compiled as builtin drivers
diff --git a/drivers/media/media-dev-allocator.c b/drivers/media/media-dev-allocator.c
new file mode 100644
index 000000000000..ae17887dec59
--- /dev/null
+++ b/drivers/media/media-dev-allocator.c
@@ -0,0 +1,135 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * media-dev-allocator.c - Media Controller Device Allocator API
4 *
5 * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
6 *
7 * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
8 */
9
10/*
11 * This file adds a global refcounted Media Controller Device Instance API.
12 * A system wide global media device list is managed and each media device
13 * includes a kref count. The last put on the media device releases the media
14 * device instance.
15 *
16 */
17
18#include <linux/kref.h>
19#include <linux/module.h>
20#include <linux/slab.h>
21#include <linux/usb.h>
22
23#include <media/media-device.h>
24#include <media/media-dev-allocator.h>
25
26static LIST_HEAD(media_device_list);
27static DEFINE_MUTEX(media_device_lock);
28
29struct media_device_instance {
30 struct media_device mdev;
31 struct module *owner;
32 struct list_head list;
33 struct kref refcount;
34};
35
36static inline struct media_device_instance *
37to_media_device_instance(struct media_device *mdev)
38{
39 return container_of(mdev, struct media_device_instance, mdev);
40}
41
42static void media_device_instance_release(struct kref *kref)
43{
44 struct media_device_instance *mdi =
45 container_of(kref, struct media_device_instance, refcount);
46
47 dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__);
48
49 mutex_lock(&media_device_lock);
50
51 media_device_unregister(&mdi->mdev);
52 media_device_cleanup(&mdi->mdev);
53
54 list_del(&mdi->list);
55 mutex_unlock(&media_device_lock);
56
57 kfree(mdi);
58}
59
60/* Callers should hold media_device_lock when calling this function */
61static struct media_device *__media_device_get(struct device *dev,
62 const char *module_name,
63 struct module *owner)
64{
65 struct media_device_instance *mdi;
66
67 list_for_each_entry(mdi, &media_device_list, list) {
68 if (mdi->mdev.dev != dev)
69 continue;
70
71 kref_get(&mdi->refcount);
72
73 /* get module reference for the media_device owner */
74 if (owner != mdi->owner && !try_module_get(mdi->owner))
75 dev_err(dev,
76 "%s: module %s get owner reference error\n",
77 __func__, module_name);
78 else
79 dev_dbg(dev, "%s: module %s got owner reference\n",
80 __func__, module_name);
81 return &mdi->mdev;
82 }
83
84 mdi = kzalloc(sizeof(*mdi), GFP_KERNEL);
85 if (!mdi)
86 return NULL;
87
88 mdi->owner = owner;
89 kref_init(&mdi->refcount);
90 list_add_tail(&mdi->list, &media_device_list);
91
92 dev_dbg(dev, "%s: Allocated media device for owner %s\n",
93 __func__, module_name);
94 return &mdi->mdev;
95}
96
97struct media_device *media_device_usb_allocate(struct usb_device *udev,
98 const char *module_name,
99 struct module *owner)
100{
101 struct media_device *mdev;
102
103 mutex_lock(&media_device_lock);
104 mdev = __media_device_get(&udev->dev, module_name, owner);
105 if (!mdev) {
106 mutex_unlock(&media_device_lock);
107 return ERR_PTR(-ENOMEM);
108 }
109
110 /* check if media device is already initialized */
111 if (!mdev->dev)
112 __media_device_usb_init(mdev, udev, udev->product,
113 module_name);
114 mutex_unlock(&media_device_lock);
115 return mdev;
116}
117EXPORT_SYMBOL_GPL(media_device_usb_allocate);
118
119void media_device_delete(struct media_device *mdev, const char *module_name,
120 struct module *owner)
121{
122 struct media_device_instance *mdi = to_media_device_instance(mdev);
123
124 mutex_lock(&media_device_lock);
125 /* put module reference for the media_device owner */
126 if (mdi->owner != owner) {
127 module_put(mdi->owner);
128 dev_dbg(mdi->mdev.dev,
129 "%s: module %s put owner module reference\n",
130 __func__, module_name);
131 }
132 mutex_unlock(&media_device_lock);
133 kref_put(&mdi->refcount, media_device_instance_release);
134}
135EXPORT_SYMBOL_GPL(media_device_delete);