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