diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/vfio/mdev | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/vfio/mdev')
| -rw-r--r-- | drivers/vfio/mdev/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/vfio/mdev/Makefile | 5 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_core.c | 275 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_driver.c | 75 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_private.h | 30 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_sysfs.c | 302 |
6 files changed, 691 insertions, 0 deletions
diff --git a/drivers/vfio/mdev/Kconfig b/drivers/vfio/mdev/Kconfig new file mode 100644 index 000000000..e5fb84e07 --- /dev/null +++ b/drivers/vfio/mdev/Kconfig @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VFIO_MDEV + tristate diff --git a/drivers/vfio/mdev/Makefile b/drivers/vfio/mdev/Makefile new file mode 100644 index 000000000..7c236ba1b --- /dev/null +++ b/drivers/vfio/mdev/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +mdev-y := mdev_core.o mdev_sysfs.o mdev_driver.o + +obj-$(CONFIG_VFIO_MDEV) += mdev.o diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c new file mode 100644 index 000000000..3b4aa0632 --- /dev/null +++ b/drivers/vfio/mdev/mdev_core.c @@ -0,0 +1,275 @@ +// 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> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/mdev.h> + +#include "mdev_private.h" + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "NVIDIA Corporation" +#define DRIVER_DESC "Mediated device Core Driver" + +static struct class_compat *mdev_bus_compat_class; + +static LIST_HEAD(mdev_list); +static DEFINE_MUTEX(mdev_list_lock); + +/* 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->type->parent; + + mdev_remove_sysfs_files(mdev); + device_del(&mdev->dev); + lockdep_assert_held(&parent->unreg_sem); + /* Balances with device_initialize() */ + put_device(&mdev->dev); +} + +static int mdev_device_remove_cb(struct device *dev, void *data) +{ + if (dev->bus == &mdev_bus_type) + mdev_device_remove_common(to_mdev_device(dev)); + return 0; +} + +/* + * mdev_register_parent: Register a device as parent for mdevs + * @parent: parent structure registered + * @dev: device structure representing parent device. + * @mdev_driver: Device driver to bind to the newly created mdev + * @types: Array of supported mdev types + * @nr_types: Number of entries in @types + * + * Registers the @parent stucture as a parent for mdev types and thus mdev + * devices. The caller needs to hold a reference on @dev that must not be + * released until after the call to mdev_unregister_parent(). + * + * Returns a negative value on error, otherwise 0. + */ +int mdev_register_parent(struct mdev_parent *parent, struct device *dev, + struct mdev_driver *mdev_driver, struct mdev_type **types, + unsigned int nr_types) +{ + char *env_string = "MDEV_STATE=registered"; + char *envp[] = { env_string, NULL }; + int ret; + + memset(parent, 0, sizeof(*parent)); + init_rwsem(&parent->unreg_sem); + parent->dev = dev; + parent->mdev_driver = mdev_driver; + parent->types = types; + parent->nr_types = nr_types; + atomic_set(&parent->available_instances, mdev_driver->max_instances); + + ret = parent_create_sysfs_files(parent); + if (ret) + return ret; + + ret = class_compat_create_link(mdev_bus_compat_class, dev); + if (ret) + dev_warn(dev, "Failed to create compatibility class link\n"); + + dev_info(dev, "MDEV: Registered\n"); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + return 0; +} +EXPORT_SYMBOL(mdev_register_parent); + +/* + * mdev_unregister_parent : Unregister a parent device + * @parent: parent structure to unregister + */ +void mdev_unregister_parent(struct mdev_parent *parent) +{ + char *env_string = "MDEV_STATE=unregistered"; + char *envp[] = { env_string, NULL }; + + dev_info(parent->dev, "MDEV: Unregistering\n"); + + down_write(&parent->unreg_sem); + class_compat_remove_link(mdev_bus_compat_class, parent->dev); + device_for_each_child(parent->dev, NULL, mdev_device_remove_cb); + parent_remove_sysfs_files(parent); + up_write(&parent->unreg_sem); + + kobject_uevent_env(&parent->dev->kobj, KOBJ_CHANGE, envp); +} +EXPORT_SYMBOL(mdev_unregister_parent); + +static void mdev_device_release(struct device *dev) +{ + struct mdev_device *mdev = to_mdev_device(dev); + struct mdev_parent *parent = mdev->type->parent; + + mutex_lock(&mdev_list_lock); + list_del(&mdev->next); + if (!parent->mdev_driver->get_available) + atomic_inc(&parent->available_instances); + mutex_unlock(&mdev_list_lock); + + /* Pairs with the get in mdev_device_create() */ + kobject_put(&mdev->type->kobj); + + dev_dbg(&mdev->dev, "MDEV: destroying\n"); + kfree(mdev); +} + +int mdev_device_create(struct mdev_type *type, const guid_t *uuid) +{ + int ret; + struct mdev_device *mdev, *tmp; + struct mdev_parent *parent = type->parent; + struct mdev_driver *drv = parent->mdev_driver; + + mutex_lock(&mdev_list_lock); + + /* Check for duplicate */ + list_for_each_entry(tmp, &mdev_list, next) { + if (guid_equal(&tmp->uuid, uuid)) { + mutex_unlock(&mdev_list_lock); + return -EEXIST; + } + } + + if (!drv->get_available) { + /* + * Note: that non-atomic read and dec is fine here because + * all modifications are under mdev_list_lock. + */ + if (!atomic_read(&parent->available_instances)) { + mutex_unlock(&mdev_list_lock); + return -EUSERS; + } + atomic_dec(&parent->available_instances); + } + + mdev = kzalloc_obj(*mdev); + if (!mdev) { + mutex_unlock(&mdev_list_lock); + return -ENOMEM; + } + + device_initialize(&mdev->dev); + mdev->dev.parent = parent->dev; + mdev->dev.bus = &mdev_bus_type; + mdev->dev.release = mdev_device_release; + mdev->dev.groups = mdev_device_groups; + mdev->type = type; + /* Pairs with the put in mdev_device_release() */ + kobject_get(&type->kobj); + + guid_copy(&mdev->uuid, uuid); + list_add(&mdev->next, &mdev_list); + mutex_unlock(&mdev_list_lock); + + ret = dev_set_name(&mdev->dev, "%pUl", uuid); + if (ret) + goto out_put_device; + + /* Check if parent unregistration has started */ + if (!down_read_trylock(&parent->unreg_sem)) { + ret = -ENODEV; + goto out_put_device; + } + + ret = device_add(&mdev->dev); + if (ret) + goto out_unlock; + + ret = device_driver_attach(&drv->driver, &mdev->dev); + if (ret) + goto out_del; + + ret = mdev_create_sysfs_files(mdev); + if (ret) + goto out_del; + + mdev->active = true; + dev_dbg(&mdev->dev, "MDEV: created\n"); + up_read(&parent->unreg_sem); + + return 0; + +out_del: + device_del(&mdev->dev); +out_unlock: + up_read(&parent->unreg_sem); +out_put_device: + put_device(&mdev->dev); + return ret; +} + +int mdev_device_remove(struct mdev_device *mdev) +{ + struct mdev_device *tmp; + struct mdev_parent *parent = mdev->type->parent; + + mutex_lock(&mdev_list_lock); + list_for_each_entry(tmp, &mdev_list, next) { + if (tmp == mdev) + break; + } + + if (tmp != mdev) { + mutex_unlock(&mdev_list_lock); + return -ENODEV; + } + + if (!mdev->active) { + mutex_unlock(&mdev_list_lock); + return -EAGAIN; + } + + mdev->active = false; + mutex_unlock(&mdev_list_lock); + + /* Check if parent unregistration has started */ + if (!down_read_trylock(&parent->unreg_sem)) + return -ENODEV; + + mdev_device_remove_common(mdev); + up_read(&parent->unreg_sem); + return 0; +} + +static int __init mdev_init(void) +{ + int ret; + + ret = bus_register(&mdev_bus_type); + if (ret) + return ret; + + mdev_bus_compat_class = class_compat_register("mdev_bus"); + if (!mdev_bus_compat_class) { + bus_unregister(&mdev_bus_type); + return -ENOMEM; + } + + return 0; +} + +static void __exit mdev_exit(void) +{ + class_compat_unregister(mdev_bus_compat_class); + bus_unregister(&mdev_bus_type); +} + +subsys_initcall(mdev_init) +module_exit(mdev_exit) + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/vfio/mdev/mdev_driver.c b/drivers/vfio/mdev/mdev_driver.c new file mode 100644 index 000000000..ad5b83480 --- /dev/null +++ b/drivers/vfio/mdev/mdev_driver.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MDEV driver + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * Author: Neo Jia <cjia@nvidia.com> + * Kirti Wankhede <kwankhede@nvidia.com> + */ + +#include <linux/iommu.h> +#include <linux/mdev.h> + +#include "mdev_private.h" + +static int mdev_probe(struct device *dev) +{ + struct mdev_driver *drv = + container_of(dev->driver, struct mdev_driver, driver); + + if (!drv->probe) + return 0; + return drv->probe(to_mdev_device(dev)); +} + +static void mdev_remove(struct device *dev) +{ + struct mdev_driver *drv = + container_of(dev->driver, struct mdev_driver, driver); + + if (drv->remove) + drv->remove(to_mdev_device(dev)); +} + +static int mdev_match(struct device *dev, const struct device_driver *drv) +{ + /* + * No drivers automatically match. Drivers are only bound by explicit + * device_driver_attach() + */ + return 0; +} + +const struct bus_type mdev_bus_type = { + .name = "mdev", + .probe = mdev_probe, + .remove = mdev_remove, + .match = mdev_match, +}; + +/** + * mdev_register_driver - register a new MDEV driver + * @drv: the driver to register + * + * Returns a negative value on error, otherwise 0. + **/ +int mdev_register_driver(struct mdev_driver *drv) +{ + if (!drv->device_api) + return -EINVAL; + + /* initialize common driver fields */ + drv->driver.bus = &mdev_bus_type; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL(mdev_register_driver); + +/* + * mdev_unregister_driver - unregister MDEV driver + * @drv: the driver to unregister + */ +void mdev_unregister_driver(struct mdev_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(mdev_unregister_driver); diff --git a/drivers/vfio/mdev/mdev_private.h b/drivers/vfio/mdev/mdev_private.h new file mode 100644 index 000000000..5f61acd0f --- /dev/null +++ b/drivers/vfio/mdev/mdev_private.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Mediated device internal definitions + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * Author: Neo Jia <cjia@nvidia.com> + * Kirti Wankhede <kwankhede@nvidia.com> + */ + +#ifndef MDEV_PRIVATE_H +#define MDEV_PRIVATE_H + +extern const struct bus_type mdev_bus_type; +extern const struct attribute_group *mdev_device_groups[]; + +#define to_mdev_type_attr(_attr) \ + container_of(_attr, struct mdev_type_attribute, attr) +#define to_mdev_type(_kobj) \ + container_of(_kobj, struct mdev_type, kobj) + +int parent_create_sysfs_files(struct mdev_parent *parent); +void parent_remove_sysfs_files(struct mdev_parent *parent); + +int mdev_create_sysfs_files(struct mdev_device *mdev); +void mdev_remove_sysfs_files(struct mdev_device *mdev); + +int mdev_device_create(struct mdev_type *kobj, const guid_t *uuid); +int mdev_device_remove(struct mdev_device *dev); + +#endif /* MDEV_PRIVATE_H */ diff --git a/drivers/vfio/mdev/mdev_sysfs.c b/drivers/vfio/mdev/mdev_sysfs.c new file mode 100644 index 000000000..b2596020e --- /dev/null +++ b/drivers/vfio/mdev/mdev_sysfs.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * File attributes for Mediated devices + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * Author: Neo Jia <cjia@nvidia.com> + * Kirti Wankhede <kwankhede@nvidia.com> + */ + +#include <linux/sysfs.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/mdev.h> + +#include "mdev_private.h" + +struct mdev_type_attribute { + struct attribute attr; + ssize_t (*show)(struct mdev_type *mtype, + struct mdev_type_attribute *attr, char *buf); + ssize_t (*store)(struct mdev_type *mtype, + struct mdev_type_attribute *attr, const char *buf, + size_t count); +}; + +#define MDEV_TYPE_ATTR_RO(_name) \ + struct mdev_type_attribute mdev_type_attr_##_name = __ATTR_RO(_name) +#define MDEV_TYPE_ATTR_WO(_name) \ + struct mdev_type_attribute mdev_type_attr_##_name = __ATTR_WO(_name) + +static ssize_t mdev_type_attr_show(struct kobject *kobj, + struct attribute *__attr, char *buf) +{ + struct mdev_type_attribute *attr = to_mdev_type_attr(__attr); + struct mdev_type *type = to_mdev_type(kobj); + ssize_t ret = -EIO; + + if (attr->show) + ret = attr->show(type, attr, buf); + return ret; +} + +static ssize_t mdev_type_attr_store(struct kobject *kobj, + struct attribute *__attr, + const char *buf, size_t count) +{ + struct mdev_type_attribute *attr = to_mdev_type_attr(__attr); + struct mdev_type *type = to_mdev_type(kobj); + ssize_t ret = -EIO; + + if (attr->store) + ret = attr->store(type, attr, buf, count); + return ret; +} + +static const struct sysfs_ops mdev_type_sysfs_ops = { + .show = mdev_type_attr_show, + .store = mdev_type_attr_store, +}; + +static ssize_t create_store(struct mdev_type *mtype, + struct mdev_type_attribute *attr, const char *buf, + size_t count) +{ + char *str; + guid_t uuid; + int ret; + + if ((count < UUID_STRING_LEN) || (count > UUID_STRING_LEN + 1)) + return -EINVAL; + + str = kstrndup(buf, count, GFP_KERNEL); + if (!str) + return -ENOMEM; + + ret = guid_parse(str, &uuid); + kfree(str); + if (ret) + return ret; + + ret = mdev_device_create(mtype, &uuid); + if (ret) + return ret; + + return count; +} +static MDEV_TYPE_ATTR_WO(create); + +static ssize_t device_api_show(struct mdev_type *mtype, + struct mdev_type_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", mtype->parent->mdev_driver->device_api); +} +static MDEV_TYPE_ATTR_RO(device_api); + +static ssize_t name_show(struct mdev_type *mtype, + struct mdev_type_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", + mtype->pretty_name ? mtype->pretty_name : mtype->sysfs_name); +} + +static MDEV_TYPE_ATTR_RO(name); + +static ssize_t available_instances_show(struct mdev_type *mtype, + struct mdev_type_attribute *attr, + char *buf) +{ + struct mdev_driver *drv = mtype->parent->mdev_driver; + + if (drv->get_available) + return sysfs_emit(buf, "%u\n", drv->get_available(mtype)); + return sysfs_emit(buf, "%u\n", + atomic_read(&mtype->parent->available_instances)); +} +static MDEV_TYPE_ATTR_RO(available_instances); + +static ssize_t description_show(struct mdev_type *mtype, + struct mdev_type_attribute *attr, + char *buf) +{ + return mtype->parent->mdev_driver->show_description(mtype, buf); +} +static MDEV_TYPE_ATTR_RO(description); + +static struct attribute *mdev_types_core_attrs[] = { + &mdev_type_attr_create.attr, + &mdev_type_attr_device_api.attr, + &mdev_type_attr_name.attr, + &mdev_type_attr_available_instances.attr, + &mdev_type_attr_description.attr, + NULL, +}; + +static umode_t mdev_types_core_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + if (attr == &mdev_type_attr_description.attr && + !to_mdev_type(kobj)->parent->mdev_driver->show_description) + return 0; + return attr->mode; +} + +static struct attribute_group mdev_type_core_group = { + .attrs = mdev_types_core_attrs, + .is_visible = mdev_types_core_is_visible, +}; + +static const struct attribute_group *mdev_type_groups[] = { + &mdev_type_core_group, + NULL, +}; + +static void mdev_type_release(struct kobject *kobj) +{ + struct mdev_type *type = to_mdev_type(kobj); + + pr_debug("Releasing group %s\n", kobj->name); + /* Pairs with the get in mdev_type_add() */ + put_device(type->parent->dev); +} + +static const struct kobj_type mdev_type_ktype = { + .sysfs_ops = &mdev_type_sysfs_ops, + .release = mdev_type_release, + .default_groups = mdev_type_groups, +}; + +static int mdev_type_add(struct mdev_parent *parent, struct mdev_type *type) +{ + int ret; + + type->kobj.kset = parent->mdev_types_kset; + type->parent = parent; + /* Pairs with the put in mdev_type_release() */ + get_device(parent->dev); + + ret = kobject_init_and_add(&type->kobj, &mdev_type_ktype, NULL, + "%s-%s", dev_driver_string(parent->dev), + type->sysfs_name); + if (ret) { + kobject_put(&type->kobj); + return ret; + } + + type->devices_kobj = kobject_create_and_add("devices", &type->kobj); + if (!type->devices_kobj) { + ret = -ENOMEM; + goto attr_devices_failed; + } + + return 0; + +attr_devices_failed: + kobject_del(&type->kobj); + kobject_put(&type->kobj); + return ret; +} + +static void mdev_type_remove(struct mdev_type *type) +{ + kobject_put(type->devices_kobj); + kobject_del(&type->kobj); + kobject_put(&type->kobj); +} + +/* mdev sysfs functions */ +void parent_remove_sysfs_files(struct mdev_parent *parent) +{ + int i; + + for (i = 0; i < parent->nr_types; i++) + mdev_type_remove(parent->types[i]); + kset_unregister(parent->mdev_types_kset); +} + +int parent_create_sysfs_files(struct mdev_parent *parent) +{ + int ret, i; + + parent->mdev_types_kset = kset_create_and_add("mdev_supported_types", + NULL, &parent->dev->kobj); + if (!parent->mdev_types_kset) + return -ENOMEM; + + for (i = 0; i < parent->nr_types; i++) { + ret = mdev_type_add(parent, parent->types[i]); + if (ret) + goto out_err; + } + return 0; + +out_err: + while (--i >= 0) + mdev_type_remove(parent->types[i]); + kset_unregister(parent->mdev_types_kset); + return ret; +} + +static ssize_t remove_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mdev_device *mdev = to_mdev_device(dev); + unsigned long val; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + if (val && device_remove_file_self(dev, attr)) { + int ret; + + ret = mdev_device_remove(mdev); + if (ret) + return ret; + } + + return count; +} + +static DEVICE_ATTR_WO(remove); + +static struct attribute *mdev_device_attrs[] = { + &dev_attr_remove.attr, + NULL, +}; + +static const struct attribute_group mdev_device_group = { + .attrs = mdev_device_attrs, +}; + +const struct attribute_group *mdev_device_groups[] = { + &mdev_device_group, + NULL +}; + +int mdev_create_sysfs_files(struct mdev_device *mdev) +{ + struct mdev_type *type = mdev->type; + struct kobject *kobj = &mdev->dev.kobj; + int ret; + + ret = sysfs_create_link(type->devices_kobj, kobj, dev_name(&mdev->dev)); + if (ret) + return ret; + + ret = sysfs_create_link(kobj, &type->kobj, "mdev_type"); + if (ret) + goto type_link_failed; + return ret; + +type_link_failed: + sysfs_remove_link(mdev->type->devices_kobj, dev_name(&mdev->dev)); + return ret; +} + +void mdev_remove_sysfs_files(struct mdev_device *mdev) +{ + struct kobject *kobj = &mdev->dev.kobj; + + sysfs_remove_link(kobj, "mdev_type"); + sysfs_remove_link(mdev->type->devices_kobj, dev_name(&mdev->dev)); +} |
