summaryrefslogtreecommitdiffstats
path: root/drivers/vfio/mdev
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/vfio/mdev
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/vfio/mdev')
-rw-r--r--drivers/vfio/mdev/Kconfig4
-rw-r--r--drivers/vfio/mdev/Makefile5
-rw-r--r--drivers/vfio/mdev/mdev_core.c275
-rw-r--r--drivers/vfio/mdev/mdev_driver.c75
-rw-r--r--drivers/vfio/mdev/mdev_private.h30
-rw-r--r--drivers/vfio/mdev/mdev_sysfs.c302
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));
+}