diff options
Diffstat (limited to 'drivers/vfio/mdev/mdev_core.c')
-rw-r--r-- | drivers/vfio/mdev/mdev_core.c | 167 |
1 files changed, 86 insertions, 81 deletions
diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c index b96fedc77ee5..ed8608763134 100644 --- a/drivers/vfio/mdev/mdev_core.c +++ b/drivers/vfio/mdev/mdev_core.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Mediated device Core Driver * * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. * Author: Neo Jia <cjia@nvidia.com> * Kirti Wankhede <kwankhede@nvidia.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #include <linux/module.h> @@ -88,7 +85,7 @@ static void mdev_release_parent(struct kref *kref) put_device(dev); } -static inline struct mdev_parent *mdev_get_parent(struct mdev_parent *parent) +static struct mdev_parent *mdev_get_parent(struct mdev_parent *parent) { if (parent) kref_get(&parent->ref); @@ -96,63 +93,42 @@ static inline struct mdev_parent *mdev_get_parent(struct mdev_parent *parent) return parent; } -static inline void mdev_put_parent(struct mdev_parent *parent) +static void mdev_put_parent(struct mdev_parent *parent) { if (parent) kref_put(&parent->ref, mdev_release_parent); } -static int mdev_device_create_ops(struct kobject *kobj, - struct mdev_device *mdev) -{ - struct mdev_parent *parent = mdev->parent; - 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) +/* 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; - /* - * 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 -EBUSY; + 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)) - return 0; + if (dev_is_mdev(dev)) { + struct mdev_device *mdev; - return mdev_device_remove(dev, data ? *(bool *)data : true); + mdev = to_mdev_device(dev); + mdev_device_remove_common(mdev); + } + return 0; } /* @@ -181,6 +157,7 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) /* Check for duplicate */ parent = __find_parent_device(dev); if (parent) { + parent = NULL; ret = -EEXIST; goto add_dev_err; } @@ -192,6 +169,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; @@ -239,7 +217,6 @@ EXPORT_SYMBOL(mdev_register_device); void mdev_unregister_device(struct device *dev) { struct mdev_parent *parent; - bool force_remove = true; mutex_lock(&parent_list_lock); parent = __find_parent_device(dev); @@ -251,22 +228,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, (void *)&force_remove, - mdev_device_remove_cb); + 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); @@ -275,6 +253,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,48 +295,56 @@ int mdev_device_create(struct kobject *kobj, mutex_unlock(&mdev_list_lock); mdev->parent = parent; - kref_init(&mdev->ref); + /* 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 = parent->ops->create(kobj, mdev); + if (ret) + goto ops_create_fail; - ret = mdev_device_create_ops(kobj, mdev); + ret = device_add(&mdev->dev); if (ret) - goto create_fail; + 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); @@ -374,21 +367,33 @@ 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_device_remove_common(mdev); + up_read(&parent->unreg_sem); + return 0; +} - mdev_remove_sysfs_files(dev, type); - device_unregister(dev); - mdev_put_parent(parent); +int mdev_set_iommu_device(struct device *dev, struct device *iommu_device) +{ + struct mdev_device *mdev = to_mdev_device(dev); + + mdev->iommu_device = iommu_device; return 0; } +EXPORT_SYMBOL(mdev_set_iommu_device); + +struct device *mdev_get_iommu_device(struct device *dev) +{ + struct mdev_device *mdev = to_mdev_device(dev); + + return mdev->iommu_device; +} +EXPORT_SYMBOL(mdev_get_iommu_device); static int __init mdev_init(void) { |