| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // 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, NULL);
- 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, NULL);
- 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(sizeof(*mdev), GFP_KERNEL);
- 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);
|