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);
136