aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorParav Pandit <parav@mellanox.com>2019-06-06 12:52:33 -0400
committerAlex Williamson <alex.williamson@redhat.com>2019-06-06 14:32:37 -0400
commit5715c4dd66a315515eedef3fc4cbe1bf4620f009 (patch)
treeb7eee6e5db791b456a6d70635d424f7a07986910
parent26c9e3988eec6b858c08b0fc352d8eb13832d828 (diff)
vfio/mdev: Synchronize device create/remove with parent removal
In following sequences, child devices created while removing mdev parent device can be left out, or it may lead to race of removing half initialized child mdev devices. issue-1: -------- cpu-0 cpu-1 ----- ----- mdev_unregister_device() device_for_each_child() mdev_device_remove_cb() mdev_device_remove() create_store() mdev_device_create() [...] device_add() parent_remove_sysfs_files() /* BUG: device added by cpu-0 * whose parent is getting removed * and it won't process this mdev. */ issue-2: -------- Below crash is observed when user initiated remove is in progress and mdev_unregister_driver() completes parent unregistration. cpu-0 cpu-1 ----- ----- remove_store() mdev_device_remove() active = false; mdev_unregister_device() parent device removed. [...] parents->ops->remove() /* * BUG: Accessing invalid parent. */ This is similar race like create() racing with mdev_unregister_device(). BUG: unable to handle kernel paging request at ffffffffc0585668 PGD e8f618067 P4D e8f618067 PUD e8f61a067 PMD 85adca067 PTE 0 Oops: 0000 [#1] SMP PTI CPU: 41 PID: 37403 Comm: bash Kdump: loaded Not tainted 5.1.0-rc6-vdevbus+ #6 Hardware name: Supermicro SYS-6028U-TR4+/X10DRU-i+, BIOS 2.0b 08/09/2016 RIP: 0010:mdev_device_remove+0xfa/0x140 [mdev] Call Trace: remove_store+0x71/0x90 [mdev] kernfs_fop_write+0x113/0x1a0 vfs_write+0xad/0x1b0 ksys_write+0x5a/0xe0 do_syscall_64+0x5a/0x210 entry_SYSCALL_64_after_hwframe+0x49/0xbe Therefore, mdev core is improved as below to overcome above issues. Wait for any ongoing mdev create() and remove() to finish before unregistering parent device. This continues to allow multiple create and remove to progress in parallel for different mdev devices as most common case. At the same time guard parent removal while parent is being accessed by create() and remove() callbacks. create()/remove() and unregister_device() are synchronized by the rwsem. Refactor device removal code to mdev_device_remove_common() to avoid acquiring unreg_sem of the parent. Fixes: 7b96953bc640 ("vfio: Mediated device Core driver") Signed-off-by: Parav Pandit <parav@mellanox.com> Reviewed-by: Cornelia Huck <cohuck@redhat.com> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
-rw-r--r--drivers/vfio/mdev/mdev_core.c72
-rw-r--r--drivers/vfio/mdev/mdev_private.h2
2 files changed, 56 insertions, 18 deletions
diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c
index 0bef0cae1d4b..ae23151442cb 100644
--- a/drivers/vfio/mdev/mdev_core.c
+++ b/drivers/vfio/mdev/mdev_core.c
@@ -102,11 +102,35 @@ static void mdev_put_parent(struct mdev_parent *parent)
102 kref_put(&parent->ref, mdev_release_parent); 102 kref_put(&parent->ref, mdev_release_parent);
103} 103}
104 104
105/* Caller must hold parent unreg_sem read or write lock */
106static void mdev_device_remove_common(struct mdev_device *mdev)
107{
108 struct mdev_parent *parent;
109 struct mdev_type *type;
110 int ret;
111
112 type = to_mdev_type(mdev->type_kobj);
113 mdev_remove_sysfs_files(&mdev->dev, type);
114 device_del(&mdev->dev);
115 parent = mdev->parent;
116 lockdep_assert_held(&parent->unreg_sem);
117 ret = parent->ops->remove(mdev);
118 if (ret)
119 dev_err(&mdev->dev, "Remove failed: err=%d\n", ret);
120
121 /* Balances with device_initialize() */
122 put_device(&mdev->dev);
123 mdev_put_parent(parent);
124}
125
105static int mdev_device_remove_cb(struct device *dev, void *data) 126static int mdev_device_remove_cb(struct device *dev, void *data)
106{ 127{
107 if (dev_is_mdev(dev)) 128 if (dev_is_mdev(dev)) {
108 mdev_device_remove(dev); 129 struct mdev_device *mdev;
109 130
131 mdev = to_mdev_device(dev);
132 mdev_device_remove_common(mdev);
133 }
110 return 0; 134 return 0;
111} 135}
112 136
@@ -148,6 +172,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops)
148 } 172 }
149 173
150 kref_init(&parent->ref); 174 kref_init(&parent->ref);
175 init_rwsem(&parent->unreg_sem);
151 176
152 parent->dev = dev; 177 parent->dev = dev;
153 parent->ops = ops; 178 parent->ops = ops;
@@ -206,21 +231,23 @@ void mdev_unregister_device(struct device *dev)
206 dev_info(dev, "MDEV: Unregistering\n"); 231 dev_info(dev, "MDEV: Unregistering\n");
207 232
208 list_del(&parent->next); 233 list_del(&parent->next);
234 mutex_unlock(&parent_list_lock);
235
236 down_write(&parent->unreg_sem);
237
209 class_compat_remove_link(mdev_bus_compat_class, dev, NULL); 238 class_compat_remove_link(mdev_bus_compat_class, dev, NULL);
210 239
211 device_for_each_child(dev, NULL, mdev_device_remove_cb); 240 device_for_each_child(dev, NULL, mdev_device_remove_cb);
212 241
213 parent_remove_sysfs_files(parent); 242 parent_remove_sysfs_files(parent);
243 up_write(&parent->unreg_sem);
214 244
215 mutex_unlock(&parent_list_lock);
216 mdev_put_parent(parent); 245 mdev_put_parent(parent);
217} 246}
218EXPORT_SYMBOL(mdev_unregister_device); 247EXPORT_SYMBOL(mdev_unregister_device);
219 248
220static void mdev_device_release(struct device *dev) 249static void mdev_device_free(struct mdev_device *mdev)
221{ 250{
222 struct mdev_device *mdev = to_mdev_device(dev);
223
224 mutex_lock(&mdev_list_lock); 251 mutex_lock(&mdev_list_lock);
225 list_del(&mdev->next); 252 list_del(&mdev->next);
226 mutex_unlock(&mdev_list_lock); 253 mutex_unlock(&mdev_list_lock);
@@ -229,6 +256,13 @@ static void mdev_device_release(struct device *dev)
229 kfree(mdev); 256 kfree(mdev);
230} 257}
231 258
259static void mdev_device_release(struct device *dev)
260{
261 struct mdev_device *mdev = to_mdev_device(dev);
262
263 mdev_device_free(mdev);
264}
265
232int mdev_device_create(struct kobject *kobj, 266int mdev_device_create(struct kobject *kobj,
233 struct device *dev, const guid_t *uuid) 267 struct device *dev, const guid_t *uuid)
234{ 268{
@@ -265,6 +299,13 @@ int mdev_device_create(struct kobject *kobj,
265 299
266 mdev->parent = parent; 300 mdev->parent = parent;
267 301
302 /* Check if parent unregistration has started */
303 if (!down_read_trylock(&parent->unreg_sem)) {
304 mdev_device_free(mdev);
305 ret = -ENODEV;
306 goto mdev_fail;
307 }
308
268 device_initialize(&mdev->dev); 309 device_initialize(&mdev->dev);
269 mdev->dev.parent = dev; 310 mdev->dev.parent = dev;
270 mdev->dev.bus = &mdev_bus_type; 311 mdev->dev.bus = &mdev_bus_type;
@@ -287,6 +328,7 @@ int mdev_device_create(struct kobject *kobj,
287 328
288 mdev->active = true; 329 mdev->active = true;
289 dev_dbg(&mdev->dev, "MDEV: created\n"); 330 dev_dbg(&mdev->dev, "MDEV: created\n");
331 up_read(&parent->unreg_sem);
290 332
291 return 0; 333 return 0;
292 334
@@ -295,6 +337,7 @@ sysfs_fail:
295add_fail: 337add_fail:
296 parent->ops->remove(mdev); 338 parent->ops->remove(mdev);
297ops_create_fail: 339ops_create_fail:
340 up_read(&parent->unreg_sem);
298 put_device(&mdev->dev); 341 put_device(&mdev->dev);
299mdev_fail: 342mdev_fail:
300 mdev_put_parent(parent); 343 mdev_put_parent(parent);
@@ -305,8 +348,6 @@ int mdev_device_remove(struct device *dev)
305{ 348{
306 struct mdev_device *mdev, *tmp; 349 struct mdev_device *mdev, *tmp;
307 struct mdev_parent *parent; 350 struct mdev_parent *parent;
308 struct mdev_type *type;
309 int ret;
310 351
311 mdev = to_mdev_device(dev); 352 mdev = to_mdev_device(dev);
312 353
@@ -329,18 +370,13 @@ int mdev_device_remove(struct device *dev)
329 mdev->active = false; 370 mdev->active = false;
330 mutex_unlock(&mdev_list_lock); 371 mutex_unlock(&mdev_list_lock);
331 372
332 type = to_mdev_type(mdev->type_kobj);
333 mdev_remove_sysfs_files(dev, type);
334 device_del(&mdev->dev);
335 parent = mdev->parent; 373 parent = mdev->parent;
336 ret = parent->ops->remove(mdev); 374 /* Check if parent unregistration has started */
337 if (ret) 375 if (!down_read_trylock(&parent->unreg_sem))
338 dev_err(&mdev->dev, "Remove failed: err=%d\n", ret); 376 return -ENODEV;
339
340 /* Balances with device_initialize() */
341 put_device(&mdev->dev);
342 mdev_put_parent(parent);
343 377
378 mdev_device_remove_common(mdev);
379 up_read(&parent->unreg_sem);
344 return 0; 380 return 0;
345} 381}
346 382
diff --git a/drivers/vfio/mdev/mdev_private.h b/drivers/vfio/mdev/mdev_private.h
index 924ed2274941..398767526276 100644
--- a/drivers/vfio/mdev/mdev_private.h
+++ b/drivers/vfio/mdev/mdev_private.h
@@ -23,6 +23,8 @@ struct mdev_parent {
23 struct list_head next; 23 struct list_head next;
24 struct kset *mdev_types_kset; 24 struct kset *mdev_types_kset;
25 struct list_head type_list; 25 struct list_head type_list;
26 /* Synchronize device creation/removal with parent unregistration */
27 struct rw_semaphore unreg_sem;
26}; 28};
27 29
28struct mdev_device { 30struct mdev_device {