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 
26 static LIST_HEAD(media_device_list);
27 static DEFINE_MUTEX(media_device_lock);
28 
29 struct media_device_instance {
30 	struct media_device mdev;
31 	struct module *owner;
32 	struct list_head list;
33 	struct kref refcount;
34 };
35 
36 static inline struct media_device_instance *
to_media_device_instance(struct media_device * mdev)37 to_media_device_instance(struct media_device *mdev)
38 {
39 	return container_of(mdev, struct media_device_instance, mdev);
40 }
41 
media_device_instance_release(struct kref * kref)42 static 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 */
__media_device_get(struct device * dev,const char * module_name,struct module * owner)61 static 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 
media_device_usb_allocate(struct usb_device * udev,const char * module_name,struct module * owner)97 struct 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 }
117 EXPORT_SYMBOL_GPL(media_device_usb_allocate);
118 
media_device_delete(struct media_device * mdev,const char * module_name,struct module * owner)119 void 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 }
135 EXPORT_SYMBOL_GPL(media_device_delete);
136