]>
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 | ||
278bca7f | 63 | const guid_t *mdev_uuid(struct mdev_device *mdev) |
99e3123e | 64 | { |
278bca7f | 65 | return &mdev->uuid; |
99e3123e AW |
66 | } |
67 | EXPORT_SYMBOL(mdev_uuid); | |
68 | ||
7b96953b | 69 | /* Should be called holding parent_list_lock */ |
42930553 | 70 | static struct mdev_parent *__find_parent_device(struct device *dev) |
7b96953b | 71 | { |
42930553 | 72 | struct mdev_parent *parent; |
7b96953b KW |
73 | |
74 | list_for_each_entry(parent, &parent_list, next) { | |
75 | if (parent->dev == dev) | |
76 | return parent; | |
77 | } | |
78 | return NULL; | |
79 | } | |
80 | ||
81 | static void mdev_release_parent(struct kref *kref) | |
82 | { | |
42930553 AW |
83 | struct mdev_parent *parent = container_of(kref, struct mdev_parent, |
84 | ref); | |
7b96953b KW |
85 | struct device *dev = parent->dev; |
86 | ||
87 | kfree(parent); | |
88 | put_device(dev); | |
89 | } | |
90 | ||
278bca7f | 91 | static inline struct mdev_parent *mdev_get_parent(struct mdev_parent *parent) |
7b96953b KW |
92 | { |
93 | if (parent) | |
94 | kref_get(&parent->ref); | |
95 | ||
96 | return parent; | |
97 | } | |
98 | ||
42930553 | 99 | static inline void mdev_put_parent(struct mdev_parent *parent) |
7b96953b KW |
100 | { |
101 | if (parent) | |
102 | kref_put(&parent->ref, mdev_release_parent); | |
103 | } | |
104 | ||
105 | static int mdev_device_create_ops(struct kobject *kobj, | |
106 | struct mdev_device *mdev) | |
107 | { | |
42930553 | 108 | struct mdev_parent *parent = mdev->parent; |
7b96953b KW |
109 | int ret; |
110 | ||
111 | ret = parent->ops->create(kobj, mdev); | |
112 | if (ret) | |
113 | return ret; | |
114 | ||
115 | ret = sysfs_create_groups(&mdev->dev.kobj, | |
116 | parent->ops->mdev_attr_groups); | |
117 | if (ret) | |
118 | parent->ops->remove(mdev); | |
119 | ||
120 | return ret; | |
121 | } | |
122 | ||
123 | /* | |
124 | * mdev_device_remove_ops gets called from sysfs's 'remove' and when parent | |
125 | * device is being unregistered from mdev device framework. | |
126 | * - 'force_remove' is set to 'false' when called from sysfs's 'remove' which | |
127 | * indicates that if the mdev device is active, used by VMM or userspace | |
128 | * application, vendor driver could return error then don't remove the device. | |
129 | * - 'force_remove' is set to 'true' when called from mdev_unregister_device() | |
130 | * which indicate that parent device is being removed from mdev device | |
131 | * framework so remove mdev device forcefully. | |
132 | */ | |
133 | static int mdev_device_remove_ops(struct mdev_device *mdev, bool force_remove) | |
134 | { | |
42930553 | 135 | struct mdev_parent *parent = mdev->parent; |
7b96953b KW |
136 | int ret; |
137 | ||
138 | /* | |
139 | * Vendor driver can return error if VMM or userspace application is | |
140 | * using this mdev device. | |
141 | */ | |
142 | ret = parent->ops->remove(mdev); | |
143 | if (ret && !force_remove) | |
144 | return -EBUSY; | |
145 | ||
146 | sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); | |
147 | return 0; | |
148 | } | |
149 | ||
150 | static int mdev_device_remove_cb(struct device *dev, void *data) | |
151 | { | |
152 | if (!dev_is_mdev(dev)) | |
153 | return 0; | |
154 | ||
155 | return mdev_device_remove(dev, data ? *(bool *)data : true); | |
156 | } | |
157 | ||
158 | /* | |
159 | * mdev_register_device : Register a device | |
160 | * @dev: device structure representing parent device. | |
161 | * @ops: Parent device operation structure to be registered. | |
162 | * | |
163 | * Add device to list of registered parent devices. | |
164 | * Returns a negative value on error, otherwise 0. | |
165 | */ | |
42930553 | 166 | int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) |
7b96953b KW |
167 | { |
168 | int ret; | |
42930553 | 169 | struct mdev_parent *parent; |
7b96953b KW |
170 | |
171 | /* check for mandatory ops */ | |
172 | if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) | |
173 | return -EINVAL; | |
174 | ||
175 | dev = get_device(dev); | |
176 | if (!dev) | |
177 | return -EINVAL; | |
178 | ||
179 | mutex_lock(&parent_list_lock); | |
180 | ||
181 | /* Check for duplicate */ | |
182 | parent = __find_parent_device(dev); | |
183 | if (parent) { | |
184 | ret = -EEXIST; | |
185 | goto add_dev_err; | |
186 | } | |
187 | ||
188 | parent = kzalloc(sizeof(*parent), GFP_KERNEL); | |
189 | if (!parent) { | |
190 | ret = -ENOMEM; | |
191 | goto add_dev_err; | |
192 | } | |
193 | ||
194 | kref_init(&parent->ref); | |
7b96953b KW |
195 | |
196 | parent->dev = dev; | |
197 | parent->ops = ops; | |
198 | ||
199 | if (!mdev_bus_compat_class) { | |
200 | mdev_bus_compat_class = class_compat_register("mdev_bus"); | |
201 | if (!mdev_bus_compat_class) { | |
202 | ret = -ENOMEM; | |
203 | goto add_dev_err; | |
204 | } | |
205 | } | |
206 | ||
207 | ret = parent_create_sysfs_files(parent); | |
208 | if (ret) | |
209 | goto add_dev_err; | |
210 | ||
211 | ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); | |
212 | if (ret) | |
213 | dev_warn(dev, "Failed to create compatibility class link\n"); | |
214 | ||
215 | list_add(&parent->next, &parent_list); | |
216 | mutex_unlock(&parent_list_lock); | |
217 | ||
218 | dev_info(dev, "MDEV: Registered\n"); | |
219 | return 0; | |
220 | ||
221 | add_dev_err: | |
222 | mutex_unlock(&parent_list_lock); | |
223 | if (parent) | |
224 | mdev_put_parent(parent); | |
225 | else | |
226 | put_device(dev); | |
227 | return ret; | |
228 | } | |
229 | EXPORT_SYMBOL(mdev_register_device); | |
230 | ||
231 | /* | |
232 | * mdev_unregister_device : Unregister a parent device | |
233 | * @dev: device structure representing parent device. | |
234 | * | |
235 | * Remove device from list of registered parent devices. Give a chance to free | |
236 | * existing mediated devices for given device. | |
237 | */ | |
238 | ||
239 | void mdev_unregister_device(struct device *dev) | |
240 | { | |
42930553 | 241 | struct mdev_parent *parent; |
7b96953b KW |
242 | bool force_remove = true; |
243 | ||
244 | mutex_lock(&parent_list_lock); | |
245 | parent = __find_parent_device(dev); | |
246 | ||
247 | if (!parent) { | |
248 | mutex_unlock(&parent_list_lock); | |
249 | return; | |
250 | } | |
251 | dev_info(dev, "MDEV: Unregistering\n"); | |
252 | ||
253 | list_del(&parent->next); | |
254 | class_compat_remove_link(mdev_bus_compat_class, dev, NULL); | |
255 | ||
256 | device_for_each_child(dev, (void *)&force_remove, | |
257 | mdev_device_remove_cb); | |
258 | ||
259 | parent_remove_sysfs_files(parent); | |
260 | ||
261 | mutex_unlock(&parent_list_lock); | |
262 | mdev_put_parent(parent); | |
263 | } | |
264 | EXPORT_SYMBOL(mdev_unregister_device); | |
265 | ||
266 | static void mdev_device_release(struct device *dev) | |
267 | { | |
268 | struct mdev_device *mdev = to_mdev_device(dev); | |
269 | ||
002fe996 AW |
270 | mutex_lock(&mdev_list_lock); |
271 | list_del(&mdev->next); | |
272 | mutex_unlock(&mdev_list_lock); | |
273 | ||
7b96953b KW |
274 | dev_dbg(&mdev->dev, "MDEV: destroying\n"); |
275 | kfree(mdev); | |
276 | } | |
277 | ||
278bca7f AS |
278 | int mdev_device_create(struct kobject *kobj, |
279 | struct device *dev, const guid_t *uuid) | |
7b96953b KW |
280 | { |
281 | int ret; | |
002fe996 | 282 | struct mdev_device *mdev, *tmp; |
42930553 | 283 | struct mdev_parent *parent; |
7b96953b KW |
284 | struct mdev_type *type = to_mdev_type(kobj); |
285 | ||
286 | parent = mdev_get_parent(type->parent); | |
287 | if (!parent) | |
288 | return -EINVAL; | |
289 | ||
002fe996 | 290 | mutex_lock(&mdev_list_lock); |
7b96953b KW |
291 | |
292 | /* Check for duplicate */ | |
002fe996 | 293 | list_for_each_entry(tmp, &mdev_list, next) { |
278bca7f | 294 | if (guid_equal(&tmp->uuid, uuid)) { |
002fe996 AW |
295 | mutex_unlock(&mdev_list_lock); |
296 | ret = -EEXIST; | |
297 | goto mdev_fail; | |
298 | } | |
7b96953b KW |
299 | } |
300 | ||
301 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); | |
302 | if (!mdev) { | |
002fe996 | 303 | mutex_unlock(&mdev_list_lock); |
7b96953b | 304 | ret = -ENOMEM; |
002fe996 | 305 | goto mdev_fail; |
7b96953b KW |
306 | } |
307 | ||
278bca7f | 308 | guid_copy(&mdev->uuid, uuid); |
002fe996 AW |
309 | list_add(&mdev->next, &mdev_list); |
310 | mutex_unlock(&mdev_list_lock); | |
311 | ||
7b96953b KW |
312 | mdev->parent = parent; |
313 | kref_init(&mdev->ref); | |
314 | ||
315 | mdev->dev.parent = dev; | |
316 | mdev->dev.bus = &mdev_bus_type; | |
317 | mdev->dev.release = mdev_device_release; | |
278bca7f | 318 | dev_set_name(&mdev->dev, "%pUl", uuid); |
7b96953b KW |
319 | |
320 | ret = device_register(&mdev->dev); | |
321 | if (ret) { | |
322 | put_device(&mdev->dev); | |
002fe996 | 323 | goto mdev_fail; |
7b96953b KW |
324 | } |
325 | ||
326 | ret = mdev_device_create_ops(kobj, mdev); | |
327 | if (ret) | |
002fe996 | 328 | goto create_fail; |
7b96953b KW |
329 | |
330 | ret = mdev_create_sysfs_files(&mdev->dev, type); | |
331 | if (ret) { | |
332 | mdev_device_remove_ops(mdev, true); | |
002fe996 | 333 | goto create_fail; |
7b96953b KW |
334 | } |
335 | ||
336 | mdev->type_kobj = kobj; | |
002fe996 | 337 | mdev->active = true; |
7b96953b KW |
338 | dev_dbg(&mdev->dev, "MDEV: created\n"); |
339 | ||
002fe996 | 340 | return 0; |
7b96953b | 341 | |
002fe996 | 342 | create_fail: |
7b96953b | 343 | device_unregister(&mdev->dev); |
002fe996 | 344 | mdev_fail: |
7b96953b KW |
345 | mdev_put_parent(parent); |
346 | return ret; | |
347 | } | |
348 | ||
349 | int mdev_device_remove(struct device *dev, bool force_remove) | |
350 | { | |
49550787 | 351 | struct mdev_device *mdev, *tmp; |
42930553 | 352 | struct mdev_parent *parent; |
7b96953b KW |
353 | struct mdev_type *type; |
354 | int ret; | |
355 | ||
356 | mdev = to_mdev_device(dev); | |
49550787 AW |
357 | |
358 | mutex_lock(&mdev_list_lock); | |
359 | list_for_each_entry(tmp, &mdev_list, next) { | |
002fe996 | 360 | if (tmp == mdev) |
49550787 | 361 | break; |
49550787 AW |
362 | } |
363 | ||
002fe996 AW |
364 | if (tmp != mdev) { |
365 | mutex_unlock(&mdev_list_lock); | |
366 | return -ENODEV; | |
367 | } | |
49550787 | 368 | |
002fe996 AW |
369 | if (!mdev->active) { |
370 | mutex_unlock(&mdev_list_lock); | |
371 | return -EAGAIN; | |
372 | } | |
49550787 | 373 | |
002fe996 AW |
374 | mdev->active = false; |
375 | mutex_unlock(&mdev_list_lock); | |
49550787 | 376 | |
7b96953b KW |
377 | type = to_mdev_type(mdev->type_kobj); |
378 | parent = mdev->parent; | |
7b96953b KW |
379 | |
380 | ret = mdev_device_remove_ops(mdev, force_remove); | |
381 | if (ret) { | |
002fe996 | 382 | mdev->active = true; |
7b96953b KW |
383 | return ret; |
384 | } | |
385 | ||
386 | mdev_remove_sysfs_files(dev, type); | |
387 | device_unregister(dev); | |
7b96953b | 388 | mdev_put_parent(parent); |
49550787 AW |
389 | |
390 | return 0; | |
7b96953b KW |
391 | } |
392 | ||
393 | static int __init mdev_init(void) | |
394 | { | |
f790eb57 | 395 | return mdev_bus_register(); |
7b96953b KW |
396 | } |
397 | ||
398 | static void __exit mdev_exit(void) | |
399 | { | |
400 | if (mdev_bus_compat_class) | |
401 | class_compat_unregister(mdev_bus_compat_class); | |
402 | ||
403 | mdev_bus_unregister(); | |
404 | } | |
405 | ||
406 | module_init(mdev_init) | |
407 | module_exit(mdev_exit) | |
408 | ||
409 | MODULE_VERSION(DRIVER_VERSION); | |
410 | MODULE_LICENSE("GPL v2"); | |
411 | MODULE_AUTHOR(DRIVER_AUTHOR); | |
412 | MODULE_DESCRIPTION(DRIVER_DESC); | |
f790eb57 | 413 | MODULE_SOFTDEP("post: vfio_mdev"); |