1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Mediated device Core Driver |
4 | * |
5 | * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. |
6 | * Author: Neo Jia <cjia@nvidia.com> |
7 | * Kirti Wankhede <kwankhede@nvidia.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/sysfs.h> |
13 | #include <linux/mdev.h> |
14 | |
15 | #include "mdev_private.h" |
16 | |
17 | #define DRIVER_VERSION "0.1" |
18 | #define DRIVER_AUTHOR "NVIDIA Corporation" |
19 | #define DRIVER_DESC "Mediated device Core Driver" |
20 | |
21 | static struct class_compat *mdev_bus_compat_class; |
22 | |
23 | static LIST_HEAD(mdev_list); |
24 | static DEFINE_MUTEX(mdev_list_lock); |
25 | |
26 | /* Caller must hold parent unreg_sem read or write lock */ |
27 | static void mdev_device_remove_common(struct mdev_device *mdev) |
28 | { |
29 | struct mdev_parent *parent = mdev->type->parent; |
30 | |
31 | mdev_remove_sysfs_files(mdev); |
32 | device_del(dev: &mdev->dev); |
33 | lockdep_assert_held(&parent->unreg_sem); |
34 | /* Balances with device_initialize() */ |
35 | put_device(dev: &mdev->dev); |
36 | } |
37 | |
38 | static int mdev_device_remove_cb(struct device *dev, void *data) |
39 | { |
40 | if (dev->bus == &mdev_bus_type) |
41 | mdev_device_remove_common(mdev: to_mdev_device(dev)); |
42 | return 0; |
43 | } |
44 | |
45 | /* |
46 | * mdev_register_parent: Register a device as parent for mdevs |
47 | * @parent: parent structure registered |
48 | * @dev: device structure representing parent device. |
49 | * @mdev_driver: Device driver to bind to the newly created mdev |
50 | * @types: Array of supported mdev types |
51 | * @nr_types: Number of entries in @types |
52 | * |
53 | * Registers the @parent stucture as a parent for mdev types and thus mdev |
54 | * devices. The caller needs to hold a reference on @dev that must not be |
55 | * released until after the call to mdev_unregister_parent(). |
56 | * |
57 | * Returns a negative value on error, otherwise 0. |
58 | */ |
59 | int mdev_register_parent(struct mdev_parent *parent, struct device *dev, |
60 | struct mdev_driver *mdev_driver, struct mdev_type **types, |
61 | unsigned int nr_types) |
62 | { |
63 | char *env_string = "MDEV_STATE=registered" ; |
64 | char *envp[] = { env_string, NULL }; |
65 | int ret; |
66 | |
67 | memset(parent, 0, sizeof(*parent)); |
68 | init_rwsem(&parent->unreg_sem); |
69 | parent->dev = dev; |
70 | parent->mdev_driver = mdev_driver; |
71 | parent->types = types; |
72 | parent->nr_types = nr_types; |
73 | atomic_set(v: &parent->available_instances, i: mdev_driver->max_instances); |
74 | |
75 | ret = parent_create_sysfs_files(parent); |
76 | if (ret) |
77 | return ret; |
78 | |
79 | ret = class_compat_create_link(cls: mdev_bus_compat_class, dev, NULL); |
80 | if (ret) |
81 | dev_warn(dev, "Failed to create compatibility class link\n" ); |
82 | |
83 | dev_info(dev, "MDEV: Registered\n" ); |
84 | kobject_uevent_env(kobj: &dev->kobj, action: KOBJ_CHANGE, envp); |
85 | return 0; |
86 | } |
87 | EXPORT_SYMBOL(mdev_register_parent); |
88 | |
89 | /* |
90 | * mdev_unregister_parent : Unregister a parent device |
91 | * @parent: parent structure to unregister |
92 | */ |
93 | void mdev_unregister_parent(struct mdev_parent *parent) |
94 | { |
95 | char *env_string = "MDEV_STATE=unregistered" ; |
96 | char *envp[] = { env_string, NULL }; |
97 | |
98 | dev_info(parent->dev, "MDEV: Unregistering\n" ); |
99 | |
100 | down_write(sem: &parent->unreg_sem); |
101 | class_compat_remove_link(cls: mdev_bus_compat_class, dev: parent->dev, NULL); |
102 | device_for_each_child(dev: parent->dev, NULL, fn: mdev_device_remove_cb); |
103 | parent_remove_sysfs_files(parent); |
104 | up_write(sem: &parent->unreg_sem); |
105 | |
106 | kobject_uevent_env(kobj: &parent->dev->kobj, action: KOBJ_CHANGE, envp); |
107 | } |
108 | EXPORT_SYMBOL(mdev_unregister_parent); |
109 | |
110 | static void mdev_device_release(struct device *dev) |
111 | { |
112 | struct mdev_device *mdev = to_mdev_device(dev); |
113 | struct mdev_parent *parent = mdev->type->parent; |
114 | |
115 | mutex_lock(&mdev_list_lock); |
116 | list_del(entry: &mdev->next); |
117 | if (!parent->mdev_driver->get_available) |
118 | atomic_inc(v: &parent->available_instances); |
119 | mutex_unlock(lock: &mdev_list_lock); |
120 | |
121 | /* Pairs with the get in mdev_device_create() */ |
122 | kobject_put(kobj: &mdev->type->kobj); |
123 | |
124 | dev_dbg(&mdev->dev, "MDEV: destroying\n" ); |
125 | kfree(objp: mdev); |
126 | } |
127 | |
128 | int mdev_device_create(struct mdev_type *type, const guid_t *uuid) |
129 | { |
130 | int ret; |
131 | struct mdev_device *mdev, *tmp; |
132 | struct mdev_parent *parent = type->parent; |
133 | struct mdev_driver *drv = parent->mdev_driver; |
134 | |
135 | mutex_lock(&mdev_list_lock); |
136 | |
137 | /* Check for duplicate */ |
138 | list_for_each_entry(tmp, &mdev_list, next) { |
139 | if (guid_equal(u1: &tmp->uuid, u2: uuid)) { |
140 | mutex_unlock(lock: &mdev_list_lock); |
141 | return -EEXIST; |
142 | } |
143 | } |
144 | |
145 | if (!drv->get_available) { |
146 | /* |
147 | * Note: that non-atomic read and dec is fine here because |
148 | * all modifications are under mdev_list_lock. |
149 | */ |
150 | if (!atomic_read(v: &parent->available_instances)) { |
151 | mutex_unlock(lock: &mdev_list_lock); |
152 | return -EUSERS; |
153 | } |
154 | atomic_dec(v: &parent->available_instances); |
155 | } |
156 | |
157 | mdev = kzalloc(size: sizeof(*mdev), GFP_KERNEL); |
158 | if (!mdev) { |
159 | mutex_unlock(lock: &mdev_list_lock); |
160 | return -ENOMEM; |
161 | } |
162 | |
163 | device_initialize(dev: &mdev->dev); |
164 | mdev->dev.parent = parent->dev; |
165 | mdev->dev.bus = &mdev_bus_type; |
166 | mdev->dev.release = mdev_device_release; |
167 | mdev->dev.groups = mdev_device_groups; |
168 | mdev->type = type; |
169 | /* Pairs with the put in mdev_device_release() */ |
170 | kobject_get(kobj: &type->kobj); |
171 | |
172 | guid_copy(dst: &mdev->uuid, src: uuid); |
173 | list_add(new: &mdev->next, head: &mdev_list); |
174 | mutex_unlock(lock: &mdev_list_lock); |
175 | |
176 | ret = dev_set_name(dev: &mdev->dev, name: "%pUl" , uuid); |
177 | if (ret) |
178 | goto out_put_device; |
179 | |
180 | /* Check if parent unregistration has started */ |
181 | if (!down_read_trylock(sem: &parent->unreg_sem)) { |
182 | ret = -ENODEV; |
183 | goto out_put_device; |
184 | } |
185 | |
186 | ret = device_add(dev: &mdev->dev); |
187 | if (ret) |
188 | goto out_unlock; |
189 | |
190 | ret = device_driver_attach(drv: &drv->driver, dev: &mdev->dev); |
191 | if (ret) |
192 | goto out_del; |
193 | |
194 | ret = mdev_create_sysfs_files(mdev); |
195 | if (ret) |
196 | goto out_del; |
197 | |
198 | mdev->active = true; |
199 | dev_dbg(&mdev->dev, "MDEV: created\n" ); |
200 | up_read(sem: &parent->unreg_sem); |
201 | |
202 | return 0; |
203 | |
204 | out_del: |
205 | device_del(dev: &mdev->dev); |
206 | out_unlock: |
207 | up_read(sem: &parent->unreg_sem); |
208 | out_put_device: |
209 | put_device(dev: &mdev->dev); |
210 | return ret; |
211 | } |
212 | |
213 | int mdev_device_remove(struct mdev_device *mdev) |
214 | { |
215 | struct mdev_device *tmp; |
216 | struct mdev_parent *parent = mdev->type->parent; |
217 | |
218 | mutex_lock(&mdev_list_lock); |
219 | list_for_each_entry(tmp, &mdev_list, next) { |
220 | if (tmp == mdev) |
221 | break; |
222 | } |
223 | |
224 | if (tmp != mdev) { |
225 | mutex_unlock(lock: &mdev_list_lock); |
226 | return -ENODEV; |
227 | } |
228 | |
229 | if (!mdev->active) { |
230 | mutex_unlock(lock: &mdev_list_lock); |
231 | return -EAGAIN; |
232 | } |
233 | |
234 | mdev->active = false; |
235 | mutex_unlock(lock: &mdev_list_lock); |
236 | |
237 | /* Check if parent unregistration has started */ |
238 | if (!down_read_trylock(sem: &parent->unreg_sem)) |
239 | return -ENODEV; |
240 | |
241 | mdev_device_remove_common(mdev); |
242 | up_read(sem: &parent->unreg_sem); |
243 | return 0; |
244 | } |
245 | |
246 | static int __init mdev_init(void) |
247 | { |
248 | int ret; |
249 | |
250 | ret = bus_register(bus: &mdev_bus_type); |
251 | if (ret) |
252 | return ret; |
253 | |
254 | mdev_bus_compat_class = class_compat_register(name: "mdev_bus" ); |
255 | if (!mdev_bus_compat_class) { |
256 | bus_unregister(bus: &mdev_bus_type); |
257 | return -ENOMEM; |
258 | } |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | static void __exit mdev_exit(void) |
264 | { |
265 | class_compat_unregister(cls: mdev_bus_compat_class); |
266 | bus_unregister(bus: &mdev_bus_type); |
267 | } |
268 | |
269 | subsys_initcall(mdev_init) |
270 | module_exit(mdev_exit) |
271 | |
272 | MODULE_VERSION(DRIVER_VERSION); |
273 | MODULE_LICENSE("GPL v2" ); |
274 | MODULE_AUTHOR(DRIVER_AUTHOR); |
275 | MODULE_DESCRIPTION(DRIVER_DESC); |
276 | |