summaryrefslogtreecommitdiffstats
path: root/drivers/media/mc/mc-dev-allocator.c
blob: ae17887dec592d2df51b3adc6d565b6ca03e3d97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: GPL-2.0
/*
 * media-dev-allocator.c - Media Controller Device Allocator API
 *
 * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
 *
 * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 */

/*
 * 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 <linux/kref.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>

#include <media/media-device.h>
#include <media/media-dev-allocator.h>

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