VFIO fixes for v5.2-rc5
- 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. (Parav Pandit) -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.14 (GNU/Linux) iQIcBAABAgAGBQJc/+HbAAoJECObm247sIsiCDUP/Azz8EnURPlDNjTFSn9/7S1M 4ul/K3XU1iwkSP2NuUGSnt10qlsOp4o4EVLB1iYOUJBRiQ9ZIYhAG0YWM1BOrztQ WU41cXQwLS8wvEWL/gpnaKTC3czSh99eaZI0N2+P2j2D6l46JHUUWJjHxcns6LEz F5SiJO4adbbspZwDtkqupJ4qk8orYdGYUQpwGTCCEoUxuXo8t6so5xec93/7Epjw oLUJIHVV4EjxAobVM46F6OKPTCGPf8RF9FHqBf/Btq1EVdeQLoNe4Vk5c+apGpoh owObA/9+x4G+MRWbFHG6t7pEdFvJvcTUgTj4CMsyL4CjOIEGPmyjfH/NKc8TJ+Sr p57X/i5Zqd9B2vufaJABk3mOCKk1UT8Tv+9WPQH5tsEvQvfkx35pgMM4WP3DkmaS 3RpGVhR9u0PzWUtQhQrd9y7IIwdvo65ICr8o4O11gn710Cqc33OyGzF14NET3yeD HqY0eByZPanflI2hFhTTZzDHkFIfE1yP/ZCT13WrBQHAVZMk6v6+VqJl6rxDPCoH YB/K+Xu8p4N2qfMXo/HeXwh4uRzyrOCxyXUjJ/YVZffGe77eyWhsgGgtZxeMkG0M YwHj6s9zeSJu+70EKIp0c0k1AYPvWpH3naNj/8XqEBnBCr0QDOHi/hIomGeVp7f9 rhOeCBl5ZJgMoLvRi1hG =CXX0 -----END PGP SIGNATURE----- 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
This commit is contained in:
Коммит
c23b07125f
|
@ -102,56 +102,35 @@ static void mdev_put_parent(struct mdev_parent *parent)
|
|||
kref_put(&parent->ref, mdev_release_parent);
|
||||
}
|
||||
|
||||
static int mdev_device_create_ops(struct kobject *kobj,
|
||||
struct mdev_device *mdev)
|
||||
/* Caller must hold parent unreg_sem read or write lock */
|
||||
static void mdev_device_remove_common(struct mdev_device *mdev)
|
||||
{
|
||||
struct mdev_parent *parent = mdev->parent;
|
||||
struct mdev_parent *parent;
|
||||
struct mdev_type *type;
|
||||
int ret;
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* Vendor driver can return error if VMM or userspace application is
|
||||
* using this mdev device.
|
||||
*/
|
||||
type = to_mdev_type(mdev->type_kobj);
|
||||
mdev_remove_sysfs_files(&mdev->dev, type);
|
||||
device_del(&mdev->dev);
|
||||
parent = mdev->parent;
|
||||
lockdep_assert_held(&parent->unreg_sem);
|
||||
ret = parent->ops->remove(mdev);
|
||||
if (ret && !force_remove)
|
||||
return ret;
|
||||
if (ret)
|
||||
dev_err(&mdev->dev, "Remove failed: err=%d\n", ret);
|
||||
|
||||
sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups);
|
||||
return 0;
|
||||
/* Balances with device_initialize() */
|
||||
put_device(&mdev->dev);
|
||||
mdev_put_parent(parent);
|
||||
}
|
||||
|
||||
static int mdev_device_remove_cb(struct device *dev, void *data)
|
||||
{
|
||||
if (dev_is_mdev(dev))
|
||||
mdev_device_remove(dev, true);
|
||||
if (dev_is_mdev(dev)) {
|
||||
struct mdev_device *mdev;
|
||||
|
||||
mdev = to_mdev_device(dev);
|
||||
mdev_device_remove_common(mdev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -193,6 +172,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops)
|
|||
}
|
||||
|
||||
kref_init(&parent->ref);
|
||||
init_rwsem(&parent->unreg_sem);
|
||||
|
||||
parent->dev = dev;
|
||||
parent->ops = ops;
|
||||
|
@ -251,21 +231,23 @@ void mdev_unregister_device(struct device *dev)
|
|||
dev_info(dev, "MDEV: Unregistering\n");
|
||||
|
||||
list_del(&parent->next);
|
||||
mutex_unlock(&parent_list_lock);
|
||||
|
||||
down_write(&parent->unreg_sem);
|
||||
|
||||
class_compat_remove_link(mdev_bus_compat_class, dev, NULL);
|
||||
|
||||
device_for_each_child(dev, NULL, mdev_device_remove_cb);
|
||||
|
||||
parent_remove_sysfs_files(parent);
|
||||
up_write(&parent->unreg_sem);
|
||||
|
||||
mutex_unlock(&parent_list_lock);
|
||||
mdev_put_parent(parent);
|
||||
}
|
||||
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);
|
||||
list_del(&mdev->next);
|
||||
mutex_unlock(&mdev_list_lock);
|
||||
|
@ -274,6 +256,13 @@ static void mdev_device_release(struct device *dev)
|
|||
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,
|
||||
struct device *dev, const guid_t *uuid)
|
||||
{
|
||||
|
@ -310,46 +299,55 @@ int mdev_device_create(struct kobject *kobj,
|
|||
|
||||
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.bus = &mdev_bus_type;
|
||||
mdev->dev.release = mdev_device_release;
|
||||
dev_set_name(&mdev->dev, "%pUl", uuid);
|
||||
mdev->dev.groups = parent->ops->mdev_attr_groups;
|
||||
mdev->type_kobj = kobj;
|
||||
|
||||
ret = device_register(&mdev->dev);
|
||||
if (ret) {
|
||||
put_device(&mdev->dev);
|
||||
goto mdev_fail;
|
||||
}
|
||||
|
||||
ret = mdev_device_create_ops(kobj, mdev);
|
||||
ret = parent->ops->create(kobj, mdev);
|
||||
if (ret)
|
||||
goto create_fail;
|
||||
goto ops_create_fail;
|
||||
|
||||
ret = device_add(&mdev->dev);
|
||||
if (ret)
|
||||
goto add_fail;
|
||||
|
||||
ret = mdev_create_sysfs_files(&mdev->dev, type);
|
||||
if (ret) {
|
||||
mdev_device_remove_ops(mdev, true);
|
||||
goto create_fail;
|
||||
}
|
||||
if (ret)
|
||||
goto sysfs_fail;
|
||||
|
||||
mdev->type_kobj = kobj;
|
||||
mdev->active = true;
|
||||
dev_dbg(&mdev->dev, "MDEV: created\n");
|
||||
up_read(&parent->unreg_sem);
|
||||
|
||||
return 0;
|
||||
|
||||
create_fail:
|
||||
device_unregister(&mdev->dev);
|
||||
sysfs_fail:
|
||||
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_put_parent(parent);
|
||||
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_parent *parent;
|
||||
struct mdev_type *type;
|
||||
int ret;
|
||||
|
||||
mdev = to_mdev_device(dev);
|
||||
|
||||
|
@ -372,19 +370,13 @@ int mdev_device_remove(struct device *dev, bool force_remove)
|
|||
mdev->active = false;
|
||||
mutex_unlock(&mdev_list_lock);
|
||||
|
||||
type = to_mdev_type(mdev->type_kobj);
|
||||
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);
|
||||
if (ret) {
|
||||
mdev->active = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
mdev_remove_sysfs_files(dev, type);
|
||||
device_unregister(dev);
|
||||
mdev_put_parent(parent);
|
||||
|
||||
mdev_device_remove_common(mdev);
|
||||
up_read(&parent->unreg_sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ struct mdev_parent {
|
|||
struct list_head next;
|
||||
struct kset *mdev_types_kset;
|
||||
struct list_head type_list;
|
||||
/* Synchronize device creation/removal with parent unregistration */
|
||||
struct rw_semaphore unreg_sem;
|
||||
};
|
||||
|
||||
struct mdev_device {
|
||||
|
@ -60,6 +62,6 @@ void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type);
|
|||
|
||||
int mdev_device_create(struct kobject *kobj,
|
||||
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 */
|
||||
|
|
|
@ -236,11 +236,9 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr,
|
|||
if (val && device_remove_file_self(dev, attr)) {
|
||||
int ret;
|
||||
|
||||
ret = mdev_device_remove(dev, false);
|
||||
if (ret) {
|
||||
device_create_file(dev, attr);
|
||||
ret = mdev_device_remove(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
|
|
Загрузка…
Ссылка в новой задаче