Commit c23b0712 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'vfio-v5.2-rc5' of git://github.com/awilliam/linux-vfio

Pull VFIO fixes from Alex Williamson:
 "Fix mdev device create/remove paths to provide initialized device for
  parent driver create callback and correct ordering of device removal
  from bus prior to initiating removal by parent.

  Also resolve races between parent removal and device create/remove
  paths (all from Parav Pandit)"

* tag 'vfio-v5.2-rc5' of git://github.com/awilliam/linux-vfio:
  vfio/mdev: Synchronize device create/remove with parent removal
  vfio/mdev: Avoid creating sysfs remove file on stale device removal
  vfio/mdev: Improve the create/remove sequence
parents 6fa425a2 5715c4dd
...@@ -102,56 +102,35 @@ static void mdev_put_parent(struct mdev_parent *parent) ...@@ -102,56 +102,35 @@ static void mdev_put_parent(struct mdev_parent *parent)
kref_put(&parent->ref, mdev_release_parent); kref_put(&parent->ref, mdev_release_parent);
} }
static int mdev_device_create_ops(struct kobject *kobj, /* Caller must hold parent unreg_sem read or write lock */
struct mdev_device *mdev) static void mdev_device_remove_common(struct mdev_device *mdev)
{ {
struct mdev_parent *parent = mdev->parent; struct mdev_parent *parent;
int ret; struct mdev_type *type;
ret = parent->ops->create(kobj, mdev);
if (ret)
return ret;
ret = sysfs_create_groups(&mdev->dev.kobj,
parent->ops->mdev_attr_groups);
if (ret)
parent->ops->remove(mdev);
return ret;
}
/*
* mdev_device_remove_ops gets called from sysfs's 'remove' and when parent
* device is being unregistered from mdev device framework.
* - 'force_remove' is set to 'false' when called from sysfs's 'remove' which
* indicates that if the mdev device is active, used by VMM or userspace
* application, vendor driver could return error then don't remove the device.
* - 'force_remove' is set to 'true' when called from mdev_unregister_device()
* which indicate that parent device is being removed from mdev device
* framework so remove mdev device forcefully.
*/
static int mdev_device_remove_ops(struct mdev_device *mdev, bool force_remove)
{
struct mdev_parent *parent = mdev->parent;
int ret; int ret;
/* type = to_mdev_type(mdev->type_kobj);
* Vendor driver can return error if VMM or userspace application is mdev_remove_sysfs_files(&mdev->dev, type);
* using this mdev device. device_del(&mdev->dev);
*/ parent = mdev->parent;
lockdep_assert_held(&parent->unreg_sem);
ret = parent->ops->remove(mdev); ret = parent->ops->remove(mdev);
if (ret && !force_remove) if (ret)
return ret; dev_err(&mdev->dev, "Remove failed: err=%d\n", ret);
sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); /* Balances with device_initialize() */
return 0; put_device(&mdev->dev);
mdev_put_parent(parent);
} }
static int mdev_device_remove_cb(struct device *dev, void *data) static int mdev_device_remove_cb(struct device *dev, void *data)
{ {
if (dev_is_mdev(dev)) if (dev_is_mdev(dev)) {
mdev_device_remove(dev, true); struct mdev_device *mdev;
mdev = to_mdev_device(dev);
mdev_device_remove_common(mdev);
}
return 0; return 0;
} }
...@@ -193,6 +172,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) ...@@ -193,6 +172,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops)
} }
kref_init(&parent->ref); kref_init(&parent->ref);
init_rwsem(&parent->unreg_sem);
parent->dev = dev; parent->dev = dev;
parent->ops = ops; parent->ops = ops;
...@@ -251,21 +231,23 @@ void mdev_unregister_device(struct device *dev) ...@@ -251,21 +231,23 @@ void mdev_unregister_device(struct device *dev)
dev_info(dev, "MDEV: Unregistering\n"); dev_info(dev, "MDEV: Unregistering\n");
list_del(&parent->next); list_del(&parent->next);
mutex_unlock(&parent_list_lock);
down_write(&parent->unreg_sem);
class_compat_remove_link(mdev_bus_compat_class, dev, NULL); class_compat_remove_link(mdev_bus_compat_class, dev, NULL);
device_for_each_child(dev, NULL, mdev_device_remove_cb); device_for_each_child(dev, NULL, mdev_device_remove_cb);
parent_remove_sysfs_files(parent); parent_remove_sysfs_files(parent);
up_write(&parent->unreg_sem);
mutex_unlock(&parent_list_lock);
mdev_put_parent(parent); mdev_put_parent(parent);
} }
EXPORT_SYMBOL(mdev_unregister_device); EXPORT_SYMBOL(mdev_unregister_device);
static void mdev_device_release(struct device *dev) static void mdev_device_free(struct mdev_device *mdev)
{ {
struct mdev_device *mdev = to_mdev_device(dev);
mutex_lock(&mdev_list_lock); mutex_lock(&mdev_list_lock);
list_del(&mdev->next); list_del(&mdev->next);
mutex_unlock(&mdev_list_lock); mutex_unlock(&mdev_list_lock);
...@@ -274,6 +256,13 @@ static void mdev_device_release(struct device *dev) ...@@ -274,6 +256,13 @@ static void mdev_device_release(struct device *dev)
kfree(mdev); kfree(mdev);
} }
static void mdev_device_release(struct device *dev)
{
struct mdev_device *mdev = to_mdev_device(dev);
mdev_device_free(mdev);
}
int mdev_device_create(struct kobject *kobj, int mdev_device_create(struct kobject *kobj,
struct device *dev, const guid_t *uuid) struct device *dev, const guid_t *uuid)
{ {
...@@ -310,46 +299,55 @@ int mdev_device_create(struct kobject *kobj, ...@@ -310,46 +299,55 @@ int mdev_device_create(struct kobject *kobj,
mdev->parent = parent; mdev->parent = parent;
/* Check if parent unregistration has started */
if (!down_read_trylock(&parent->unreg_sem)) {
mdev_device_free(mdev);
ret = -ENODEV;
goto mdev_fail;
}
device_initialize(&mdev->dev);
mdev->dev.parent = dev; mdev->dev.parent = dev;
mdev->dev.bus = &mdev_bus_type; mdev->dev.bus = &mdev_bus_type;
mdev->dev.release = mdev_device_release; mdev->dev.release = mdev_device_release;
dev_set_name(&mdev->dev, "%pUl", uuid); dev_set_name(&mdev->dev, "%pUl", uuid);
mdev->dev.groups = parent->ops->mdev_attr_groups;
mdev->type_kobj = kobj;
ret = device_register(&mdev->dev); ret = parent->ops->create(kobj, mdev);
if (ret) { if (ret)
put_device(&mdev->dev); goto ops_create_fail;
goto mdev_fail;
}
ret = mdev_device_create_ops(kobj, mdev); ret = device_add(&mdev->dev);
if (ret) if (ret)
goto create_fail; goto add_fail;
ret = mdev_create_sysfs_files(&mdev->dev, type); ret = mdev_create_sysfs_files(&mdev->dev, type);
if (ret) { if (ret)
mdev_device_remove_ops(mdev, true); goto sysfs_fail;
goto create_fail;
}
mdev->type_kobj = kobj;
mdev->active = true; mdev->active = true;
dev_dbg(&mdev->dev, "MDEV: created\n"); dev_dbg(&mdev->dev, "MDEV: created\n");
up_read(&parent->unreg_sem);
return 0; return 0;
create_fail: sysfs_fail:
device_unregister(&mdev->dev); device_del(&mdev->dev);
add_fail:
parent->ops->remove(mdev);
ops_create_fail:
up_read(&parent->unreg_sem);
put_device(&mdev->dev);
mdev_fail: mdev_fail:
mdev_put_parent(parent); mdev_put_parent(parent);
return ret; return ret;
} }
int mdev_device_remove(struct device *dev, bool force_remove) int mdev_device_remove(struct device *dev)
{ {
struct mdev_device *mdev, *tmp; struct mdev_device *mdev, *tmp;
struct mdev_parent *parent; struct mdev_parent *parent;
struct mdev_type *type;
int ret;
mdev = to_mdev_device(dev); mdev = to_mdev_device(dev);
...@@ -372,19 +370,13 @@ int mdev_device_remove(struct device *dev, bool force_remove) ...@@ -372,19 +370,13 @@ int mdev_device_remove(struct device *dev, bool force_remove)
mdev->active = false; mdev->active = false;
mutex_unlock(&mdev_list_lock); mutex_unlock(&mdev_list_lock);
type = to_mdev_type(mdev->type_kobj);
parent = mdev->parent; parent = mdev->parent;
/* Check if parent unregistration has started */
if (!down_read_trylock(&parent->unreg_sem))
return -ENODEV;
ret = mdev_device_remove_ops(mdev, force_remove); mdev_device_remove_common(mdev);
if (ret) { up_read(&parent->unreg_sem);
mdev->active = true;
return ret;
}
mdev_remove_sysfs_files(dev, type);
device_unregister(dev);
mdev_put_parent(parent);
return 0; return 0;
} }
......
...@@ -23,6 +23,8 @@ struct mdev_parent { ...@@ -23,6 +23,8 @@ struct mdev_parent {
struct list_head next; struct list_head next;
struct kset *mdev_types_kset; struct kset *mdev_types_kset;
struct list_head type_list; struct list_head type_list;
/* Synchronize device creation/removal with parent unregistration */
struct rw_semaphore unreg_sem;
}; };
struct mdev_device { struct mdev_device {
...@@ -60,6 +62,6 @@ void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type); ...@@ -60,6 +62,6 @@ void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type);
int mdev_device_create(struct kobject *kobj, int mdev_device_create(struct kobject *kobj,
struct device *dev, const guid_t *uuid); struct device *dev, const guid_t *uuid);
int mdev_device_remove(struct device *dev, bool force_remove); int mdev_device_remove(struct device *dev);
#endif /* MDEV_PRIVATE_H */ #endif /* MDEV_PRIVATE_H */
...@@ -236,11 +236,9 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr, ...@@ -236,11 +236,9 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr,
if (val && device_remove_file_self(dev, attr)) { if (val && device_remove_file_self(dev, attr)) {
int ret; int ret;
ret = mdev_device_remove(dev, false); ret = mdev_device_remove(dev);
if (ret) { if (ret)
device_create_file(dev, attr);
return ret; return ret;
}
} }
return count; return count;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment