]>
Commit | Line | Data |
---|---|---|
7b96953b KW |
1 | /* |
2 | * Mediated device Core Driver | |
3 | * | |
4 | * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. | |
5 | * Author: Neo Jia <cjia@nvidia.com> | |
6 | * Kirti Wankhede <kwankhede@nvidia.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/device.h> | |
15 | #include <linux/slab.h> | |
16 | #include <linux/uuid.h> | |
17 | #include <linux/sysfs.h> | |
18 | #include <linux/mdev.h> | |
19 | ||
20 | #include "mdev_private.h" | |
21 | ||
22 | #define DRIVER_VERSION "0.1" | |
23 | #define DRIVER_AUTHOR "NVIDIA Corporation" | |
24 | #define DRIVER_DESC "Mediated device Core Driver" | |
25 | ||
26 | static LIST_HEAD(parent_list); | |
27 | static DEFINE_MUTEX(parent_list_lock); | |
28 | static struct class_compat *mdev_bus_compat_class; | |
29 | ||
49550787 AW |
30 | static LIST_HEAD(mdev_list); |
31 | static DEFINE_MUTEX(mdev_list_lock); | |
32 | ||
9372e6fe AW |
33 | struct device *mdev_parent_dev(struct mdev_device *mdev) |
34 | { | |
35 | return mdev->parent->dev; | |
36 | } | |
37 | EXPORT_SYMBOL(mdev_parent_dev); | |
38 | ||
99e3123e AW |
39 | void *mdev_get_drvdata(struct mdev_device *mdev) |
40 | { | |
41 | return mdev->driver_data; | |
42 | } | |
43 | EXPORT_SYMBOL(mdev_get_drvdata); | |
44 | ||
45 | void mdev_set_drvdata(struct mdev_device *mdev, void *data) | |
46 | { | |
47 | mdev->driver_data = data; | |
48 | } | |
49 | EXPORT_SYMBOL(mdev_set_drvdata); | |
50 | ||
51 | struct device *mdev_dev(struct mdev_device *mdev) | |
52 | { | |
53 | return &mdev->dev; | |
54 | } | |
55 | EXPORT_SYMBOL(mdev_dev); | |
56 | ||
57 | struct mdev_device *mdev_from_dev(struct device *dev) | |
58 | { | |
59 | return dev_is_mdev(dev) ? to_mdev_device(dev) : NULL; | |
60 | } | |
61 | EXPORT_SYMBOL(mdev_from_dev); | |
62 | ||
63 | uuid_le mdev_uuid(struct mdev_device *mdev) | |
64 | { | |
65 | return mdev->uuid; | |
66 | } | |
67 | EXPORT_SYMBOL(mdev_uuid); | |
68 | ||
7b96953b KW |
69 | static int _find_mdev_device(struct device *dev, void *data) |
70 | { | |
71 | struct mdev_device *mdev; | |
72 | ||
73 | if (!dev_is_mdev(dev)) | |
74 | return 0; | |
75 | ||
76 | mdev = to_mdev_device(dev); | |
77 | ||
78 | if (uuid_le_cmp(mdev->uuid, *(uuid_le *)data) == 0) | |
79 | return 1; | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
42930553 | 84 | static bool mdev_device_exist(struct mdev_parent *parent, uuid_le uuid) |
7b96953b KW |
85 | { |
86 | struct device *dev; | |
87 | ||
88 | dev = device_find_child(parent->dev, &uuid, _find_mdev_device); | |
89 | if (dev) { | |
90 | put_device(dev); | |
91 | return true; | |
92 | } | |
93 | ||
94 | return false; | |
95 | } | |
96 | ||
97 | /* Should be called holding parent_list_lock */ | |
42930553 | 98 | static struct mdev_parent *__find_parent_device(struct device *dev) |
7b96953b | 99 | { |
42930553 | 100 | struct mdev_parent *parent; |
7b96953b KW |
101 | |
102 | list_for_each_entry(parent, &parent_list, next) { | |
103 | if (parent->dev == dev) | |
104 | return parent; | |
105 | } | |
106 | return NULL; | |
107 | } | |
108 | ||
109 | static void mdev_release_parent(struct kref *kref) | |
110 | { | |
42930553 AW |
111 | struct mdev_parent *parent = container_of(kref, struct mdev_parent, |
112 | ref); | |
7b96953b KW |
113 | struct device *dev = parent->dev; |
114 | ||
115 | kfree(parent); | |
116 | put_device(dev); | |
117 | } | |
118 | ||
119 | static | |
42930553 | 120 | inline struct mdev_parent *mdev_get_parent(struct mdev_parent *parent) |
7b96953b KW |
121 | { |
122 | if (parent) | |
123 | kref_get(&parent->ref); | |
124 | ||
125 | return parent; | |
126 | } | |
127 | ||
42930553 | 128 | static inline void mdev_put_parent(struct mdev_parent *parent) |
7b96953b KW |
129 | { |
130 | if (parent) | |
131 | kref_put(&parent->ref, mdev_release_parent); | |
132 | } | |
133 | ||
134 | static int mdev_device_create_ops(struct kobject *kobj, | |
135 | struct mdev_device *mdev) | |
136 | { | |
42930553 | 137 | struct mdev_parent *parent = mdev->parent; |
7b96953b KW |
138 | int ret; |
139 | ||
140 | ret = parent->ops->create(kobj, mdev); | |
141 | if (ret) | |
142 | return ret; | |
143 | ||
144 | ret = sysfs_create_groups(&mdev->dev.kobj, | |
145 | parent->ops->mdev_attr_groups); | |
146 | if (ret) | |
147 | parent->ops->remove(mdev); | |
148 | ||
149 | return ret; | |
150 | } | |
151 | ||
152 | /* | |
153 | * mdev_device_remove_ops gets called from sysfs's 'remove' and when parent | |
154 | * device is being unregistered from mdev device framework. | |
155 | * - 'force_remove' is set to 'false' when called from sysfs's 'remove' which | |
156 | * indicates that if the mdev device is active, used by VMM or userspace | |
157 | * application, vendor driver could return error then don't remove the device. | |
158 | * - 'force_remove' is set to 'true' when called from mdev_unregister_device() | |
159 | * which indicate that parent device is being removed from mdev device | |
160 | * framework so remove mdev device forcefully. | |
161 | */ | |
162 | static int mdev_device_remove_ops(struct mdev_device *mdev, bool force_remove) | |
163 | { | |
42930553 | 164 | struct mdev_parent *parent = mdev->parent; |
7b96953b KW |
165 | int ret; |
166 | ||
167 | /* | |
168 | * Vendor driver can return error if VMM or userspace application is | |
169 | * using this mdev device. | |
170 | */ | |
171 | ret = parent->ops->remove(mdev); | |
172 | if (ret && !force_remove) | |
173 | return -EBUSY; | |
174 | ||
175 | sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | static int mdev_device_remove_cb(struct device *dev, void *data) | |
180 | { | |
181 | if (!dev_is_mdev(dev)) | |
182 | return 0; | |
183 | ||
184 | return mdev_device_remove(dev, data ? *(bool *)data : true); | |
185 | } | |
186 | ||
187 | /* | |
188 | * mdev_register_device : Register a device | |
189 | * @dev: device structure representing parent device. | |
190 | * @ops: Parent device operation structure to be registered. | |
191 | * | |
192 | * Add device to list of registered parent devices. | |
193 | * Returns a negative value on error, otherwise 0. | |
194 | */ | |
42930553 | 195 | int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) |
7b96953b KW |
196 | { |
197 | int ret; | |
42930553 | 198 | struct mdev_parent *parent; |
7b96953b KW |
199 | |
200 | /* check for mandatory ops */ | |
201 | if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) | |
202 | return -EINVAL; | |
203 | ||
204 | dev = get_device(dev); | |
205 | if (!dev) | |
206 | return -EINVAL; | |
207 | ||
208 | mutex_lock(&parent_list_lock); | |
209 | ||
210 | /* Check for duplicate */ | |
211 | parent = __find_parent_device(dev); | |
212 | if (parent) { | |
213 | ret = -EEXIST; | |
214 | goto add_dev_err; | |
215 | } | |
216 | ||
217 | parent = kzalloc(sizeof(*parent), GFP_KERNEL); | |
218 | if (!parent) { | |
219 | ret = -ENOMEM; | |
220 | goto add_dev_err; | |
221 | } | |
222 | ||
223 | kref_init(&parent->ref); | |
224 | mutex_init(&parent->lock); | |
225 | ||
226 | parent->dev = dev; | |
227 | parent->ops = ops; | |
228 | ||
229 | if (!mdev_bus_compat_class) { | |
230 | mdev_bus_compat_class = class_compat_register("mdev_bus"); | |
231 | if (!mdev_bus_compat_class) { | |
232 | ret = -ENOMEM; | |
233 | goto add_dev_err; | |
234 | } | |
235 | } | |
236 | ||
237 | ret = parent_create_sysfs_files(parent); | |
238 | if (ret) | |
239 | goto add_dev_err; | |
240 | ||
241 | ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); | |
242 | if (ret) | |
243 | dev_warn(dev, "Failed to create compatibility class link\n"); | |
244 | ||
245 | list_add(&parent->next, &parent_list); | |
246 | mutex_unlock(&parent_list_lock); | |
247 | ||
248 | dev_info(dev, "MDEV: Registered\n"); | |
249 | return 0; | |
250 | ||
251 | add_dev_err: | |
252 | mutex_unlock(&parent_list_lock); | |
253 | if (parent) | |
254 | mdev_put_parent(parent); | |
255 | else | |
256 | put_device(dev); | |
257 | return ret; | |
258 | } | |
259 | EXPORT_SYMBOL(mdev_register_device); | |
260 | ||
261 | /* | |
262 | * mdev_unregister_device : Unregister a parent device | |
263 | * @dev: device structure representing parent device. | |
264 | * | |
265 | * Remove device from list of registered parent devices. Give a chance to free | |
266 | * existing mediated devices for given device. | |
267 | */ | |
268 | ||
269 | void mdev_unregister_device(struct device *dev) | |
270 | { | |
42930553 | 271 | struct mdev_parent *parent; |
7b96953b KW |
272 | bool force_remove = true; |
273 | ||
274 | mutex_lock(&parent_list_lock); | |
275 | parent = __find_parent_device(dev); | |
276 | ||
277 | if (!parent) { | |
278 | mutex_unlock(&parent_list_lock); | |
279 | return; | |
280 | } | |
281 | dev_info(dev, "MDEV: Unregistering\n"); | |
282 | ||
283 | list_del(&parent->next); | |
284 | class_compat_remove_link(mdev_bus_compat_class, dev, NULL); | |
285 | ||
286 | device_for_each_child(dev, (void *)&force_remove, | |
287 | mdev_device_remove_cb); | |
288 | ||
289 | parent_remove_sysfs_files(parent); | |
290 | ||
291 | mutex_unlock(&parent_list_lock); | |
292 | mdev_put_parent(parent); | |
293 | } | |
294 | EXPORT_SYMBOL(mdev_unregister_device); | |
295 | ||
296 | static void mdev_device_release(struct device *dev) | |
297 | { | |
298 | struct mdev_device *mdev = to_mdev_device(dev); | |
299 | ||
300 | dev_dbg(&mdev->dev, "MDEV: destroying\n"); | |
301 | kfree(mdev); | |
302 | } | |
303 | ||
304 | int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) | |
305 | { | |
306 | int ret; | |
307 | struct mdev_device *mdev; | |
42930553 | 308 | struct mdev_parent *parent; |
7b96953b KW |
309 | struct mdev_type *type = to_mdev_type(kobj); |
310 | ||
311 | parent = mdev_get_parent(type->parent); | |
312 | if (!parent) | |
313 | return -EINVAL; | |
314 | ||
315 | mutex_lock(&parent->lock); | |
316 | ||
317 | /* Check for duplicate */ | |
318 | if (mdev_device_exist(parent, uuid)) { | |
319 | ret = -EEXIST; | |
320 | goto create_err; | |
321 | } | |
322 | ||
323 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); | |
324 | if (!mdev) { | |
325 | ret = -ENOMEM; | |
326 | goto create_err; | |
327 | } | |
328 | ||
329 | memcpy(&mdev->uuid, &uuid, sizeof(uuid_le)); | |
330 | mdev->parent = parent; | |
331 | kref_init(&mdev->ref); | |
332 | ||
333 | mdev->dev.parent = dev; | |
334 | mdev->dev.bus = &mdev_bus_type; | |
335 | mdev->dev.release = mdev_device_release; | |
336 | dev_set_name(&mdev->dev, "%pUl", uuid.b); | |
337 | ||
338 | ret = device_register(&mdev->dev); | |
339 | if (ret) { | |
340 | put_device(&mdev->dev); | |
341 | goto create_err; | |
342 | } | |
343 | ||
344 | ret = mdev_device_create_ops(kobj, mdev); | |
345 | if (ret) | |
346 | goto create_failed; | |
347 | ||
348 | ret = mdev_create_sysfs_files(&mdev->dev, type); | |
349 | if (ret) { | |
350 | mdev_device_remove_ops(mdev, true); | |
351 | goto create_failed; | |
352 | } | |
353 | ||
354 | mdev->type_kobj = kobj; | |
355 | dev_dbg(&mdev->dev, "MDEV: created\n"); | |
356 | ||
357 | mutex_unlock(&parent->lock); | |
49550787 AW |
358 | |
359 | mutex_lock(&mdev_list_lock); | |
360 | list_add(&mdev->next, &mdev_list); | |
361 | mutex_unlock(&mdev_list_lock); | |
362 | ||
7b96953b KW |
363 | return ret; |
364 | ||
365 | create_failed: | |
366 | device_unregister(&mdev->dev); | |
367 | ||
368 | create_err: | |
369 | mutex_unlock(&parent->lock); | |
370 | mdev_put_parent(parent); | |
371 | return ret; | |
372 | } | |
373 | ||
374 | int mdev_device_remove(struct device *dev, bool force_remove) | |
375 | { | |
49550787 | 376 | struct mdev_device *mdev, *tmp; |
42930553 | 377 | struct mdev_parent *parent; |
7b96953b KW |
378 | struct mdev_type *type; |
379 | int ret; | |
49550787 | 380 | bool found = false; |
7b96953b KW |
381 | |
382 | mdev = to_mdev_device(dev); | |
49550787 AW |
383 | |
384 | mutex_lock(&mdev_list_lock); | |
385 | list_for_each_entry(tmp, &mdev_list, next) { | |
386 | if (tmp == mdev) { | |
387 | found = true; | |
388 | break; | |
389 | } | |
390 | } | |
391 | ||
392 | if (found) | |
393 | list_del(&mdev->next); | |
394 | ||
395 | mutex_unlock(&mdev_list_lock); | |
396 | ||
397 | if (!found) | |
398 | return -ENODEV; | |
399 | ||
7b96953b KW |
400 | type = to_mdev_type(mdev->type_kobj); |
401 | parent = mdev->parent; | |
402 | mutex_lock(&parent->lock); | |
403 | ||
404 | ret = mdev_device_remove_ops(mdev, force_remove); | |
405 | if (ret) { | |
406 | mutex_unlock(&parent->lock); | |
49550787 AW |
407 | |
408 | mutex_lock(&mdev_list_lock); | |
409 | list_add(&mdev->next, &mdev_list); | |
410 | mutex_unlock(&mdev_list_lock); | |
411 | ||
7b96953b KW |
412 | return ret; |
413 | } | |
414 | ||
415 | mdev_remove_sysfs_files(dev, type); | |
416 | device_unregister(dev); | |
417 | mutex_unlock(&parent->lock); | |
418 | mdev_put_parent(parent); | |
49550787 AW |
419 | |
420 | return 0; | |
7b96953b KW |
421 | } |
422 | ||
423 | static int __init mdev_init(void) | |
424 | { | |
f790eb57 | 425 | return mdev_bus_register(); |
7b96953b KW |
426 | } |
427 | ||
428 | static void __exit mdev_exit(void) | |
429 | { | |
430 | if (mdev_bus_compat_class) | |
431 | class_compat_unregister(mdev_bus_compat_class); | |
432 | ||
433 | mdev_bus_unregister(); | |
434 | } | |
435 | ||
436 | module_init(mdev_init) | |
437 | module_exit(mdev_exit) | |
438 | ||
439 | MODULE_VERSION(DRIVER_VERSION); | |
440 | MODULE_LICENSE("GPL v2"); | |
441 | MODULE_AUTHOR(DRIVER_AUTHOR); | |
442 | MODULE_DESCRIPTION(DRIVER_DESC); | |
f790eb57 | 443 | MODULE_SOFTDEP("post: vfio_mdev"); |