Message ID | 1478293856-8191-2-git-send-email-kwankhede@nvidia.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
> From: Kirti Wankhede [mailto:kwankhede@nvidia.com] > Sent: Saturday, November 05, 2016 5:11 AM > [...] > > Signed-off-by: Kirti Wankhede <kwankhede@nvidia.com> > Signed-off-by: Neo Jia <cjia@nvidia.com> > Change-Id: I73a5084574270b14541c529461ea2f03c292d510 Jike has given his reviewed-by for some patches in v10. Please include his reviewed-by in new version, which represent acknowledgements from our side and also be informative to other reviewers. Thanks Kevin
* Kirti Wankhede <kwankhede@nvidia.com> [2016-11-05 02:40:35 +0530]: Hi Kirti, [...] > diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig > index da6e2ce77495..23eced02aaf6 100644 > --- a/drivers/vfio/Kconfig > +++ b/drivers/vfio/Kconfig > @@ -48,4 +48,5 @@ menuconfig VFIO_NOIOMMU > > source "drivers/vfio/pci/Kconfig" > source "drivers/vfio/platform/Kconfig" > +source "drivers/vfio/mdev/Kconfig" > source "virt/lib/Kconfig" > diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile > index 7b8a31f63fea..4a23c13b6be4 100644 > --- a/drivers/vfio/Makefile > +++ b/drivers/vfio/Makefile > @@ -7,3 +7,4 @@ obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o > obj-$(CONFIG_VFIO_SPAPR_EEH) += vfio_spapr_eeh.o > obj-$(CONFIG_VFIO_PCI) += pci/ > obj-$(CONFIG_VFIO_PLATFORM) += platform/ > +obj-$(CONFIG_VFIO_MDEV) += mdev/ > diff --git a/drivers/vfio/mdev/Kconfig b/drivers/vfio/mdev/Kconfig > new file mode 100644 > index 000000000000..303c14ce2847 > --- /dev/null > +++ b/drivers/vfio/mdev/Kconfig > @@ -0,0 +1,10 @@ > + > +config VFIO_MDEV > + tristate "Mediated device driver framework" > + depends on VFIO > + default n > + help > + Provides a framework to virtualize devices. > + See Documentation/vfio-mdev/vfio-mediated-device.txt for more details. We don't have this doc at this point of time. > + > + If you don't know what do here, say N. > diff --git a/drivers/vfio/mdev/Makefile b/drivers/vfio/mdev/Makefile > new file mode 100644 > index 000000000000..31bc04801d94 > --- /dev/null > +++ b/drivers/vfio/mdev/Makefile > @@ -0,0 +1,4 @@ > + > +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 000000000000..54c59f325336 > --- /dev/null > +++ b/drivers/vfio/mdev/mdev_core.c [...] > + > +/* > + * 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) ? s/force_remove/force/ > +{ > + struct parent_device *parent = mdev->parent; > + int ret; > + > + /* > + * Vendor driver can return error if VMM or userspace application is > + * using this mdev device. > + */ > + ret = parent->ops->remove(mdev); > + if (ret && !force_remove) > + return -EBUSY; > + > + sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); > + return 0; > +} > + > +static int mdev_device_remove_cb(struct device *dev, void *data) > +{ > + if (!dev_is_mdev(dev)) > + return 0; > + > + return mdev_device_remove(dev, data ? *(bool *)data : true); > +} > + > +/* > + * mdev_register_device : Register a device > + * @dev: device structure representing parent device. > + * @ops: Parent device operation structure to be registered. > + * > + * Add device to list of registered parent devices. > + * Returns a negative value on error, otherwise 0. > + */ > +int mdev_register_device(struct device *dev, const struct parent_ops *ops) > +{ > + int ret; > + struct parent_device *parent; > + > + /* check for mandatory ops */ > + if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) > + return -EINVAL; > + > + dev = get_device(dev); > + if (!dev) > + return -EINVAL; > + > + mutex_lock(&parent_list_lock); > + > + /* Check for duplicate */ > + parent = __find_parent_device(dev); > + if (parent) { > + ret = -EEXIST; > + goto add_dev_err; > + } > + > + parent = kzalloc(sizeof(*parent), GFP_KERNEL); > + if (!parent) { > + ret = -ENOMEM; > + goto add_dev_err; > + } > + > + kref_init(&parent->ref); > + mutex_init(&parent->lock); > + > + parent->dev = dev; > + parent->ops = ops; > + > + if (!mdev_bus_compat_class) { > + mdev_bus_compat_class = class_compat_register("mdev_bus"); > + if (!mdev_bus_compat_class) { > + ret = -ENOMEM; > + goto add_dev_err; > + } > + } > + > + ret = parent_create_sysfs_files(parent); > + if (ret) > + goto add_dev_err; > + > + ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); > + if (ret) > + dev_warn(dev, "Failed to create compatibility class link\n"); > + > + list_add(&parent->next, &parent_list); > + mutex_unlock(&parent_list_lock); > + > + dev_info(dev, "MDEV: Registered\n"); > + return 0; > + > +add_dev_err: > + mutex_unlock(&parent_list_lock); > + if (parent) > + mdev_put_parent(parent); Why do this? I don't find the place that you call mdev_get_parent above. > + else > + put_device(dev); Shouldn't we always do this? > + return ret; > +} > +EXPORT_SYMBOL(mdev_register_device); > + > +/* > + * mdev_unregister_device : Unregister a parent device > + * @dev: device structure representing parent device. > + * > + * Remove device from list of registered parent devices. Give a chance to free > + * existing mediated devices for given device. > + */ > + > +void mdev_unregister_device(struct device *dev) > +{ > + struct parent_device *parent; > + bool force_remove = true; > + > + mutex_lock(&parent_list_lock); > + parent = __find_parent_device(dev); > + > + if (!parent) { > + mutex_unlock(&parent_list_lock); > + return; > + } > + dev_info(dev, "MDEV: Unregistering\n"); > + > + list_del(&parent->next); > + class_compat_remove_link(mdev_bus_compat_class, dev, NULL); > + > + device_for_each_child(dev, (void *)&force_remove, > + mdev_device_remove_cb); > + > + parent_remove_sysfs_files(parent); > + > + mutex_unlock(&parent_list_lock); > + mdev_put_parent(parent); We also need to call put_device(dev) here since we have a get_device during registration, no? Or I must miss something... > +} > +EXPORT_SYMBOL(mdev_unregister_device); > + [...] > +static int __init mdev_init(void) > +{ > + int ret; > + > + ret = mdev_bus_register(); > + if (ret) { > + pr_err("Failed to register mdev bus\n"); > + return ret; > + } > + > + /* > + * Attempt to load known vfio_mdev. This gives us a working environment > + * without the user needing to explicitly load vfio_mdev driver. > + */ > + request_module_nowait("vfio_mdev"); We don't have this module yet. > + > + return ret; > +} > + > +static void __exit mdev_exit(void) > +{ > + if (mdev_bus_compat_class) > + class_compat_unregister(mdev_bus_compat_class); > + > + mdev_bus_unregister(); > +} > + > +module_init(mdev_init) > +module_exit(mdev_exit) > + > +MODULE_VERSION(DRIVER_VERSION); > +MODULE_LICENSE("GPL"); > +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 000000000000..0b3250044a80 > --- /dev/null > +++ b/drivers/vfio/mdev/mdev_driver.c > @@ -0,0 +1,122 @@ > +/* > + * MDEV driver > + * > + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. > + * Author: Neo Jia <cjia@nvidia.com> > + * Kirti Wankhede <kwankhede@nvidia.com> Don't know if you care much for this: There is a TAB before your name. :> > + * > + * 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/device.h> > +#include <linux/iommu.h> > +#include <linux/mdev.h> > + > +#include "mdev_private.h" > + > +static int mdev_attach_iommu(struct mdev_device *mdev) > +{ > + int ret; > + struct iommu_group *group; > + > + group = iommu_group_alloc(); > + if (IS_ERR(group)) > + return PTR_ERR(group); > + > + ret = iommu_group_add_device(group, &mdev->dev); > + if (ret) > + goto attach_fail; > + > + dev_info(&mdev->dev, "MDEV: group_id = %d\n", > + iommu_group_id(group)); nit: strange indentation. The above two lines could be safely merge into one line. > +attach_fail: > + iommu_group_put(group); > + return ret; > +} > + > +static void mdev_detach_iommu(struct mdev_device *mdev) > +{ > + iommu_group_remove_device(&mdev->dev); > + dev_info(&mdev->dev, "MDEV: detaching iommu\n"); > +} > + > +static int mdev_probe(struct device *dev) > +{ > + struct mdev_driver *drv = to_mdev_driver(dev->driver); > + struct mdev_device *mdev = to_mdev_device(dev); > + int ret; > + > + ret = mdev_attach_iommu(mdev); > + if (ret) > + return ret; > + > + if (drv && drv->probe) > + ret = drv->probe(dev); > + > + if (ret) > + mdev_detach_iommu(mdev); ? if (drv && drv->probe) { ret = drv->probe(dev); if (ret) mdev_detach_iommu(mdev); } > + > + return ret; > +} > + > +static int mdev_remove(struct device *dev) > +{ > + struct mdev_driver *drv = to_mdev_driver(dev->driver); > + struct mdev_device *mdev = to_mdev_device(dev); > + > + if (drv && drv->remove) > + drv->remove(dev); > + > + mdev_detach_iommu(mdev); > + > + return 0; > +} > + > +struct bus_type mdev_bus_type = { > + .name = "mdev", > + .probe = mdev_probe, > + .remove = mdev_remove, > +}; > +EXPORT_SYMBOL_GPL(mdev_bus_type); > + > +/* Is this a kernel-doc comment? It should be started with: /** > + * mdev_register_driver - register a new MDEV driver > + * @drv: the driver to register > + * @owner: module owner of driver to be registered > + * > + * Returns a negative value on error, otherwise 0. > + */ > +int mdev_register_driver(struct mdev_driver *drv, struct module *owner) > +{ > + /* initialize common driver fields */ > + drv->driver.name = drv->name; > + drv->driver.bus = &mdev_bus_type; > + drv->driver.owner = owner; > + > + /* register with core */ > + return driver_register(&drv->driver); > +} > +EXPORT_SYMBOL(mdev_register_driver); > + > +/* > + * mdev_unregister_driver - unregister MDEV driver > + * @drv: the driver to unregister > + * Empty line. > + */ > +void mdev_unregister_driver(struct mdev_driver *drv) > +{ > + driver_unregister(&drv->driver); > +} > +EXPORT_SYMBOL(mdev_unregister_driver); > + > +int mdev_bus_register(void) > +{ > + return bus_register(&mdev_bus_type); > +} > + > +void mdev_bus_unregister(void) > +{ > + bus_unregister(&mdev_bus_type); > +} [...] > diff --git a/include/linux/mdev.h b/include/linux/mdev.h > +/* Mediated device */ > +struct mdev_device { > + struct device dev; > + struct parent_device *parent; > + uuid_le uuid; > + void *driver_data; > + > + /* internal */ > + struct kref ref; > + struct list_head next; > + struct kobject *type_kobj; > +}; > + > + Empty line. > +/** > + * struct parent_ops - Structure to be registered for each parent device to > + * register the device to mdev module. > + * [...] > + * @mmap: mmap callback No need a piece of description for arguments of the mmap callback? > + * Parent device that support mediated device should be registered with mdev > + * module with parent_ops structure. > + **/ > + > +struct parent_ops { > + struct module *owner; > + const struct attribute_group **dev_attr_groups; > + const struct attribute_group **mdev_attr_groups; > + struct attribute_group **supported_type_groups; > + > + int (*create)(struct kobject *kobj, struct mdev_device *mdev); > + int (*remove)(struct mdev_device *mdev); > + int (*open)(struct mdev_device *mdev); > + void (*release)(struct mdev_device *mdev); > + ssize_t (*read)(struct mdev_device *mdev, char __user *buf, > + size_t count, loff_t *ppos); > + ssize_t (*write)(struct mdev_device *mdev, const char __user *buf, > + size_t count, loff_t *ppos); > + ssize_t (*ioctl)(struct mdev_device *mdev, unsigned int cmd, > + unsigned long arg); > + int (*mmap)(struct mdev_device *mdev, struct vm_area_struct *vma); > +}; > + [...]
On 11/8/2016 2:55 PM, Dong Jia Shi wrote: > * Kirti Wankhede <kwankhede@nvidia.com> [2016-11-05 02:40:35 +0530]: > > Hi Kirti, > > [...] >> diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig >> index da6e2ce77495..23eced02aaf6 100644 >> --- a/drivers/vfio/Kconfig >> +++ b/drivers/vfio/Kconfig >> @@ -48,4 +48,5 @@ menuconfig VFIO_NOIOMMU >> >> source "drivers/vfio/pci/Kconfig" >> source "drivers/vfio/platform/Kconfig" >> +source "drivers/vfio/mdev/Kconfig" >> source "virt/lib/Kconfig" >> diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile >> index 7b8a31f63fea..4a23c13b6be4 100644 >> --- a/drivers/vfio/Makefile >> +++ b/drivers/vfio/Makefile >> @@ -7,3 +7,4 @@ obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o >> obj-$(CONFIG_VFIO_SPAPR_EEH) += vfio_spapr_eeh.o >> obj-$(CONFIG_VFIO_PCI) += pci/ >> obj-$(CONFIG_VFIO_PLATFORM) += platform/ >> +obj-$(CONFIG_VFIO_MDEV) += mdev/ >> diff --git a/drivers/vfio/mdev/Kconfig b/drivers/vfio/mdev/Kconfig >> new file mode 100644 >> index 000000000000..303c14ce2847 >> --- /dev/null >> +++ b/drivers/vfio/mdev/Kconfig >> @@ -0,0 +1,10 @@ >> + >> +config VFIO_MDEV >> + tristate "Mediated device driver framework" >> + depends on VFIO >> + default n >> + help >> + Provides a framework to virtualize devices. >> + See Documentation/vfio-mdev/vfio-mediated-device.txt for more details. > We don't have this doc at this point of time. > Yes, but I have this doc in this patch series. >> + >> + If you don't know what do here, say N. >> diff --git a/drivers/vfio/mdev/Makefile b/drivers/vfio/mdev/Makefile >> new file mode 100644 >> index 000000000000..31bc04801d94 >> --- /dev/null >> +++ b/drivers/vfio/mdev/Makefile >> @@ -0,0 +1,4 @@ >> + >> +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 000000000000..54c59f325336 >> --- /dev/null >> +++ b/drivers/vfio/mdev/mdev_core.c > [...] > >> + >> +/* >> + * 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) > ? > s/force_remove/force/ > >> +{ >> + struct parent_device *parent = mdev->parent; >> + int ret; >> + >> + /* >> + * Vendor driver can return error if VMM or userspace application is >> + * using this mdev device. >> + */ >> + ret = parent->ops->remove(mdev); >> + if (ret && !force_remove) >> + return -EBUSY; >> + >> + sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); >> + return 0; >> +} >> + >> +static int mdev_device_remove_cb(struct device *dev, void *data) >> +{ >> + if (!dev_is_mdev(dev)) >> + return 0; >> + >> + return mdev_device_remove(dev, data ? *(bool *)data : true); >> +} >> + >> +/* >> + * mdev_register_device : Register a device >> + * @dev: device structure representing parent device. >> + * @ops: Parent device operation structure to be registered. >> + * >> + * Add device to list of registered parent devices. >> + * Returns a negative value on error, otherwise 0. >> + */ >> +int mdev_register_device(struct device *dev, const struct parent_ops *ops) >> +{ >> + int ret; >> + struct parent_device *parent; >> + >> + /* check for mandatory ops */ >> + if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) >> + return -EINVAL; >> + >> + dev = get_device(dev); >> + if (!dev) >> + return -EINVAL; >> + >> + mutex_lock(&parent_list_lock); >> + >> + /* Check for duplicate */ >> + parent = __find_parent_device(dev); >> + if (parent) { >> + ret = -EEXIST; >> + goto add_dev_err; >> + } >> + >> + parent = kzalloc(sizeof(*parent), GFP_KERNEL); >> + if (!parent) { >> + ret = -ENOMEM; >> + goto add_dev_err; >> + } >> + >> + kref_init(&parent->ref); >> + mutex_init(&parent->lock); >> + >> + parent->dev = dev; >> + parent->ops = ops; >> + >> + if (!mdev_bus_compat_class) { >> + mdev_bus_compat_class = class_compat_register("mdev_bus"); >> + if (!mdev_bus_compat_class) { >> + ret = -ENOMEM; >> + goto add_dev_err; >> + } >> + } >> + >> + ret = parent_create_sysfs_files(parent); >> + if (ret) >> + goto add_dev_err; >> + >> + ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); >> + if (ret) >> + dev_warn(dev, "Failed to create compatibility class link\n"); >> + >> + list_add(&parent->next, &parent_list); >> + mutex_unlock(&parent_list_lock); >> + >> + dev_info(dev, "MDEV: Registered\n"); >> + return 0; >> + >> +add_dev_err: >> + mutex_unlock(&parent_list_lock); >> + if (parent) >> + mdev_put_parent(parent); > Why do this? I don't find the place that you call mdev_get_parent above. > kref_init(&parent->ref); Above increments the ref_count, so mdev_put_parent() should be called if anything fails. >> + else >> + put_device(dev); > Shouldn't we always do this? > When mdev_put_parent() is called, its release function do this. So if mdev_put_parent() is called, we don't need this. >> + return ret; >> +} >> +EXPORT_SYMBOL(mdev_register_device); >> + >> +/* >> + * mdev_unregister_device : Unregister a parent device >> + * @dev: device structure representing parent device. >> + * >> + * Remove device from list of registered parent devices. Give a chance to free >> + * existing mediated devices for given device. >> + */ >> + >> +void mdev_unregister_device(struct device *dev) >> +{ >> + struct parent_device *parent; >> + bool force_remove = true; >> + >> + mutex_lock(&parent_list_lock); >> + parent = __find_parent_device(dev); >> + >> + if (!parent) { >> + mutex_unlock(&parent_list_lock); >> + return; >> + } >> + dev_info(dev, "MDEV: Unregistering\n"); >> + >> + list_del(&parent->next); >> + class_compat_remove_link(mdev_bus_compat_class, dev, NULL); >> + >> + device_for_each_child(dev, (void *)&force_remove, >> + mdev_device_remove_cb); >> + >> + parent_remove_sysfs_files(parent); >> + >> + mutex_unlock(&parent_list_lock); >> + mdev_put_parent(parent); > We also need to call put_device(dev) here since we have a get_device > during registration, no? > Or I must miss something... > As explained above. >> +} >> +EXPORT_SYMBOL(mdev_unregister_device); >> + > [...] > >> +static int __init mdev_init(void) >> +{ >> + int ret; >> + >> + ret = mdev_bus_register(); >> + if (ret) { >> + pr_err("Failed to register mdev bus\n"); >> + return ret; >> + } >> + >> + /* >> + * Attempt to load known vfio_mdev. This gives us a working environment >> + * without the user needing to explicitly load vfio_mdev driver. >> + */ >> + request_module_nowait("vfio_mdev"); > We don't have this module yet. > Yes, this module is added in 02/22 patch. I'll move only this call to 02/22 patch in my next update. But ideally it should not fails anything if this vfio_mdev module is not found. >> + >> + return ret; >> +} >> + >> +static void __exit mdev_exit(void) >> +{ >> + if (mdev_bus_compat_class) >> + class_compat_unregister(mdev_bus_compat_class); >> + >> + mdev_bus_unregister(); >> +} >> + >> +module_init(mdev_init) >> +module_exit(mdev_exit) >> + >> +MODULE_VERSION(DRIVER_VERSION); >> +MODULE_LICENSE("GPL"); >> +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 000000000000..0b3250044a80 >> --- /dev/null >> +++ b/drivers/vfio/mdev/mdev_driver.c >> @@ -0,0 +1,122 @@ >> +/* >> + * MDEV driver >> + * >> + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. >> + * Author: Neo Jia <cjia@nvidia.com> >> + * Kirti Wankhede <kwankhede@nvidia.com> > Don't know if you care much for this: > There is a TAB before your name. :> > Oh ok, Thanks for pointing that, I'll remove TAB. I'll also take care of all the nits you suggested below. Thanks, Kirti >> + * >> + * 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/device.h> >> +#include <linux/iommu.h> >> +#include <linux/mdev.h> >> + >> +#include "mdev_private.h" >> + >> +static int mdev_attach_iommu(struct mdev_device *mdev) >> +{ >> + int ret; >> + struct iommu_group *group; >> + >> + group = iommu_group_alloc(); >> + if (IS_ERR(group)) >> + return PTR_ERR(group); >> + >> + ret = iommu_group_add_device(group, &mdev->dev); >> + if (ret) >> + goto attach_fail; >> + >> + dev_info(&mdev->dev, "MDEV: group_id = %d\n", >> + iommu_group_id(group)); > nit: strange indentation. > The above two lines could be safely merge into one line. > >> +attach_fail: >> + iommu_group_put(group); >> + return ret; >> +} >> + >> +static void mdev_detach_iommu(struct mdev_device *mdev) >> +{ >> + iommu_group_remove_device(&mdev->dev); >> + dev_info(&mdev->dev, "MDEV: detaching iommu\n"); >> +} >> + >> +static int mdev_probe(struct device *dev) >> +{ >> + struct mdev_driver *drv = to_mdev_driver(dev->driver); >> + struct mdev_device *mdev = to_mdev_device(dev); >> + int ret; >> + >> + ret = mdev_attach_iommu(mdev); >> + if (ret) >> + return ret; >> + >> + if (drv && drv->probe) >> + ret = drv->probe(dev); >> + >> + if (ret) >> + mdev_detach_iommu(mdev); > ? > if (drv && drv->probe) { > ret = drv->probe(dev); > if (ret) > mdev_detach_iommu(mdev); > } > >> + >> + return ret; >> +} >> + >> +static int mdev_remove(struct device *dev) >> +{ >> + struct mdev_driver *drv = to_mdev_driver(dev->driver); >> + struct mdev_device *mdev = to_mdev_device(dev); >> + >> + if (drv && drv->remove) >> + drv->remove(dev); >> + >> + mdev_detach_iommu(mdev); >> + >> + return 0; >> +} >> + >> +struct bus_type mdev_bus_type = { >> + .name = "mdev", >> + .probe = mdev_probe, >> + .remove = mdev_remove, >> +}; >> +EXPORT_SYMBOL_GPL(mdev_bus_type); >> + >> +/* > Is this a kernel-doc comment? > It should be started with: > /** > >> + * mdev_register_driver - register a new MDEV driver >> + * @drv: the driver to register >> + * @owner: module owner of driver to be registered >> + * >> + * Returns a negative value on error, otherwise 0. >> + */ >> +int mdev_register_driver(struct mdev_driver *drv, struct module *owner) >> +{ >> + /* initialize common driver fields */ >> + drv->driver.name = drv->name; >> + drv->driver.bus = &mdev_bus_type; >> + drv->driver.owner = owner; >> + >> + /* register with core */ >> + return driver_register(&drv->driver); >> +} >> +EXPORT_SYMBOL(mdev_register_driver); >> + >> +/* >> + * mdev_unregister_driver - unregister MDEV driver >> + * @drv: the driver to unregister >> + * > Empty line. > >> + */ >> +void mdev_unregister_driver(struct mdev_driver *drv) >> +{ >> + driver_unregister(&drv->driver); >> +} >> +EXPORT_SYMBOL(mdev_unregister_driver); >> + >> +int mdev_bus_register(void) >> +{ >> + return bus_register(&mdev_bus_type); >> +} >> + >> +void mdev_bus_unregister(void) >> +{ >> + bus_unregister(&mdev_bus_type); >> +} > [...] > >> diff --git a/include/linux/mdev.h b/include/linux/mdev.h >> +/* Mediated device */ >> +struct mdev_device { >> + struct device dev; >> + struct parent_device *parent; >> + uuid_le uuid; >> + void *driver_data; >> + >> + /* internal */ >> + struct kref ref; >> + struct list_head next; >> + struct kobject *type_kobj; >> +}; >> + >> + > Empty line. > >> +/** >> + * struct parent_ops - Structure to be registered for each parent device to >> + * register the device to mdev module. >> + * > [...] > >> + * @mmap: mmap callback > No need a piece of description for arguments of the mmap callback? > >> + * Parent device that support mediated device should be registered with mdev >> + * module with parent_ops structure. >> + **/ >> + >> +struct parent_ops { >> + struct module *owner; >> + const struct attribute_group **dev_attr_groups; >> + const struct attribute_group **mdev_attr_groups; >> + struct attribute_group **supported_type_groups; >> + >> + int (*create)(struct kobject *kobj, struct mdev_device *mdev); >> + int (*remove)(struct mdev_device *mdev); >> + int (*open)(struct mdev_device *mdev); >> + void (*release)(struct mdev_device *mdev); >> + ssize_t (*read)(struct mdev_device *mdev, char __user *buf, >> + size_t count, loff_t *ppos); >> + ssize_t (*write)(struct mdev_device *mdev, const char __user *buf, >> + size_t count, loff_t *ppos); >> + ssize_t (*ioctl)(struct mdev_device *mdev, unsigned int cmd, >> + unsigned long arg); >> + int (*mmap)(struct mdev_device *mdev, struct vm_area_struct *vma); >> +}; >> + > [...] >
* Kirti Wankhede <kwankhede@nvidia.com> [2016-11-09 02:36:12 +0530]: [...] > >> +/* > >> + * mdev_register_device : Register a device > >> + * @dev: device structure representing parent device. > >> + * @ops: Parent device operation structure to be registered. > >> + * > >> + * Add device to list of registered parent devices. > >> + * Returns a negative value on error, otherwise 0. > >> + */ > >> +int mdev_register_device(struct device *dev, const struct parent_ops *ops) > >> +{ > >> + int ret; > >> + struct parent_device *parent; > >> + > >> + /* check for mandatory ops */ > >> + if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) > >> + return -EINVAL; > >> + > >> + dev = get_device(dev); > >> + if (!dev) > >> + return -EINVAL; > >> + > >> + mutex_lock(&parent_list_lock); > >> + > >> + /* Check for duplicate */ > >> + parent = __find_parent_device(dev); > >> + if (parent) { > >> + ret = -EEXIST; > >> + goto add_dev_err; > >> + } > >> + > >> + parent = kzalloc(sizeof(*parent), GFP_KERNEL); > >> + if (!parent) { > >> + ret = -ENOMEM; > >> + goto add_dev_err; > >> + } > >> + > >> + kref_init(&parent->ref); > >> + mutex_init(&parent->lock); > >> + > >> + parent->dev = dev; > >> + parent->ops = ops; > >> + > >> + if (!mdev_bus_compat_class) { > >> + mdev_bus_compat_class = class_compat_register("mdev_bus"); > >> + if (!mdev_bus_compat_class) { > >> + ret = -ENOMEM; > >> + goto add_dev_err; > >> + } > >> + } > >> + > >> + ret = parent_create_sysfs_files(parent); > >> + if (ret) > >> + goto add_dev_err; > >> + > >> + ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); > >> + if (ret) > >> + dev_warn(dev, "Failed to create compatibility class link\n"); > >> + > >> + list_add(&parent->next, &parent_list); > >> + mutex_unlock(&parent_list_lock); > >> + > >> + dev_info(dev, "MDEV: Registered\n"); > >> + return 0; > >> + > >> +add_dev_err: > >> + mutex_unlock(&parent_list_lock); > >> + if (parent) > >> + mdev_put_parent(parent); > > Why do this? I don't find the place that you call mdev_get_parent above. > > > > kref_init(&parent->ref); > Above increments the ref_count, so mdev_put_parent() should be called if > anything fails. > > >> + else > >> + put_device(dev); > > Shouldn't we always do this? > > > > When mdev_put_parent() is called, its release function do this. So if > mdev_put_parent() is called, we don't need this. Sorry for missing that. Thanks for the explanation! > > >> + return ret; > >> +} > >> +EXPORT_SYMBOL(mdev_register_device); > >> + [...]
diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig index da6e2ce77495..23eced02aaf6 100644 --- a/drivers/vfio/Kconfig +++ b/drivers/vfio/Kconfig @@ -48,4 +48,5 @@ menuconfig VFIO_NOIOMMU source "drivers/vfio/pci/Kconfig" source "drivers/vfio/platform/Kconfig" +source "drivers/vfio/mdev/Kconfig" source "virt/lib/Kconfig" diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile index 7b8a31f63fea..4a23c13b6be4 100644 --- a/drivers/vfio/Makefile +++ b/drivers/vfio/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o obj-$(CONFIG_VFIO_SPAPR_EEH) += vfio_spapr_eeh.o obj-$(CONFIG_VFIO_PCI) += pci/ obj-$(CONFIG_VFIO_PLATFORM) += platform/ +obj-$(CONFIG_VFIO_MDEV) += mdev/ diff --git a/drivers/vfio/mdev/Kconfig b/drivers/vfio/mdev/Kconfig new file mode 100644 index 000000000000..303c14ce2847 --- /dev/null +++ b/drivers/vfio/mdev/Kconfig @@ -0,0 +1,10 @@ + +config VFIO_MDEV + tristate "Mediated device driver framework" + depends on VFIO + default n + help + Provides a framework to virtualize devices. + See Documentation/vfio-mdev/vfio-mediated-device.txt for more details. + + If you don't know what do here, say N. diff --git a/drivers/vfio/mdev/Makefile b/drivers/vfio/mdev/Makefile new file mode 100644 index 000000000000..31bc04801d94 --- /dev/null +++ b/drivers/vfio/mdev/Makefile @@ -0,0 +1,4 @@ + +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 000000000000..54c59f325336 --- /dev/null +++ b/drivers/vfio/mdev/mdev_core.c @@ -0,0 +1,388 @@ +/* + * 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> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uuid.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 LIST_HEAD(parent_list); +static DEFINE_MUTEX(parent_list_lock); +static struct class_compat *mdev_bus_compat_class; + +static int _find_mdev_device(struct device *dev, void *data) +{ + struct mdev_device *mdev; + + if (!dev_is_mdev(dev)) + return 0; + + mdev = to_mdev_device(dev); + + if (uuid_le_cmp(mdev->uuid, *(uuid_le *)data) == 0) + return 1; + + return 0; +} + +static bool mdev_device_exist(struct parent_device *parent, uuid_le uuid) +{ + struct device *dev; + + dev = device_find_child(parent->dev, &uuid, _find_mdev_device); + if (dev) { + put_device(dev); + return true; + } + + return false; +} + +/* Should be called holding parent_list_lock */ +static struct parent_device *__find_parent_device(struct device *dev) +{ + struct parent_device *parent; + + list_for_each_entry(parent, &parent_list, next) { + if (parent->dev == dev) + return parent; + } + return NULL; +} + +static void mdev_release_parent(struct kref *kref) +{ + struct parent_device *parent = container_of(kref, struct parent_device, + ref); + struct device *dev = parent->dev; + + kfree(parent); + put_device(dev); +} + +static +inline struct parent_device *mdev_get_parent(struct parent_device *parent) +{ + if (parent) + kref_get(&parent->ref); + + return parent; +} + +static inline void mdev_put_parent(struct parent_device *parent) +{ + if (parent) + kref_put(&parent->ref, mdev_release_parent); +} + +static int mdev_device_create_ops(struct kobject *kobj, + struct mdev_device *mdev) +{ + struct parent_device *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) +{ + struct parent_device *parent = mdev->parent; + int ret; + + /* + * Vendor driver can return error if VMM or userspace application is + * using this mdev device. + */ + ret = parent->ops->remove(mdev); + if (ret && !force_remove) + return -EBUSY; + + sysfs_remove_groups(&mdev->dev.kobj, parent->ops->mdev_attr_groups); + return 0; +} + +static int mdev_device_remove_cb(struct device *dev, void *data) +{ + if (!dev_is_mdev(dev)) + return 0; + + return mdev_device_remove(dev, data ? *(bool *)data : true); +} + +/* + * mdev_register_device : Register a device + * @dev: device structure representing parent device. + * @ops: Parent device operation structure to be registered. + * + * Add device to list of registered parent devices. + * Returns a negative value on error, otherwise 0. + */ +int mdev_register_device(struct device *dev, const struct parent_ops *ops) +{ + int ret; + struct parent_device *parent; + + /* check for mandatory ops */ + if (!ops || !ops->create || !ops->remove || !ops->supported_type_groups) + return -EINVAL; + + dev = get_device(dev); + if (!dev) + return -EINVAL; + + mutex_lock(&parent_list_lock); + + /* Check for duplicate */ + parent = __find_parent_device(dev); + if (parent) { + ret = -EEXIST; + goto add_dev_err; + } + + parent = kzalloc(sizeof(*parent), GFP_KERNEL); + if (!parent) { + ret = -ENOMEM; + goto add_dev_err; + } + + kref_init(&parent->ref); + mutex_init(&parent->lock); + + parent->dev = dev; + parent->ops = ops; + + if (!mdev_bus_compat_class) { + mdev_bus_compat_class = class_compat_register("mdev_bus"); + if (!mdev_bus_compat_class) { + ret = -ENOMEM; + goto add_dev_err; + } + } + + ret = parent_create_sysfs_files(parent); + if (ret) + goto add_dev_err; + + ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); + if (ret) + dev_warn(dev, "Failed to create compatibility class link\n"); + + list_add(&parent->next, &parent_list); + mutex_unlock(&parent_list_lock); + + dev_info(dev, "MDEV: Registered\n"); + return 0; + +add_dev_err: + mutex_unlock(&parent_list_lock); + if (parent) + mdev_put_parent(parent); + else + put_device(dev); + return ret; +} +EXPORT_SYMBOL(mdev_register_device); + +/* + * mdev_unregister_device : Unregister a parent device + * @dev: device structure representing parent device. + * + * Remove device from list of registered parent devices. Give a chance to free + * existing mediated devices for given device. + */ + +void mdev_unregister_device(struct device *dev) +{ + struct parent_device *parent; + bool force_remove = true; + + mutex_lock(&parent_list_lock); + parent = __find_parent_device(dev); + + if (!parent) { + mutex_unlock(&parent_list_lock); + return; + } + dev_info(dev, "MDEV: Unregistering\n"); + + list_del(&parent->next); + class_compat_remove_link(mdev_bus_compat_class, dev, NULL); + + device_for_each_child(dev, (void *)&force_remove, + mdev_device_remove_cb); + + parent_remove_sysfs_files(parent); + + mutex_unlock(&parent_list_lock); + mdev_put_parent(parent); +} +EXPORT_SYMBOL(mdev_unregister_device); + +static void mdev_device_release(struct device *dev) +{ + struct mdev_device *mdev = to_mdev_device(dev); + + dev_dbg(&mdev->dev, "MDEV: destroying\n"); + kfree(mdev); +} + +int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) +{ + int ret; + struct mdev_device *mdev; + struct parent_device *parent; + struct mdev_type *type = to_mdev_type(kobj); + + parent = mdev_get_parent(type->parent); + if (!parent) + return -EINVAL; + + mutex_lock(&parent->lock); + + /* Check for duplicate */ + if (mdev_device_exist(parent, uuid)) { + ret = -EEXIST; + goto create_err; + } + + mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + if (!mdev) { + ret = -ENOMEM; + goto create_err; + } + + memcpy(&mdev->uuid, &uuid, sizeof(uuid_le)); + mdev->parent = parent; + kref_init(&mdev->ref); + + mdev->dev.parent = dev; + mdev->dev.bus = &mdev_bus_type; + mdev->dev.release = mdev_device_release; + dev_set_name(&mdev->dev, "%pUl", uuid.b); + + ret = device_register(&mdev->dev); + if (ret) { + put_device(&mdev->dev); + goto create_err; + } + + ret = mdev_device_create_ops(kobj, mdev); + if (ret) + goto create_failed; + + ret = mdev_create_sysfs_files(&mdev->dev, type); + if (ret) { + mdev_device_remove_ops(mdev, true); + goto create_failed; + } + + mdev->type_kobj = kobj; + dev_dbg(&mdev->dev, "MDEV: created\n"); + + mutex_unlock(&parent->lock); + return ret; + +create_failed: + device_unregister(&mdev->dev); + +create_err: + mutex_unlock(&parent->lock); + mdev_put_parent(parent); + return ret; +} + +int mdev_device_remove(struct device *dev, bool force_remove) +{ + struct mdev_device *mdev; + struct parent_device *parent; + struct mdev_type *type; + int ret; + + mdev = to_mdev_device(dev); + type = to_mdev_type(mdev->type_kobj); + parent = mdev->parent; + mutex_lock(&parent->lock); + + ret = mdev_device_remove_ops(mdev, force_remove); + if (ret) { + mutex_unlock(&parent->lock); + return ret; + } + + mdev_remove_sysfs_files(dev, type); + device_unregister(dev); + mutex_unlock(&parent->lock); + mdev_put_parent(parent); + return ret; +} + +static int __init mdev_init(void) +{ + int ret; + + ret = mdev_bus_register(); + if (ret) { + pr_err("Failed to register mdev bus\n"); + return ret; + } + + /* + * Attempt to load known vfio_mdev. This gives us a working environment + * without the user needing to explicitly load vfio_mdev driver. + */ + request_module_nowait("vfio_mdev"); + + return ret; +} + +static void __exit mdev_exit(void) +{ + if (mdev_bus_compat_class) + class_compat_unregister(mdev_bus_compat_class); + + mdev_bus_unregister(); +} + +module_init(mdev_init) +module_exit(mdev_exit) + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); +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 000000000000..0b3250044a80 --- /dev/null +++ b/drivers/vfio/mdev/mdev_driver.c @@ -0,0 +1,122 @@ +/* + * MDEV 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/device.h> +#include <linux/iommu.h> +#include <linux/mdev.h> + +#include "mdev_private.h" + +static int mdev_attach_iommu(struct mdev_device *mdev) +{ + int ret; + struct iommu_group *group; + + group = iommu_group_alloc(); + if (IS_ERR(group)) + return PTR_ERR(group); + + ret = iommu_group_add_device(group, &mdev->dev); + if (ret) + goto attach_fail; + + dev_info(&mdev->dev, "MDEV: group_id = %d\n", + iommu_group_id(group)); +attach_fail: + iommu_group_put(group); + return ret; +} + +static void mdev_detach_iommu(struct mdev_device *mdev) +{ + iommu_group_remove_device(&mdev->dev); + dev_info(&mdev->dev, "MDEV: detaching iommu\n"); +} + +static int mdev_probe(struct device *dev) +{ + struct mdev_driver *drv = to_mdev_driver(dev->driver); + struct mdev_device *mdev = to_mdev_device(dev); + int ret; + + ret = mdev_attach_iommu(mdev); + if (ret) + return ret; + + if (drv && drv->probe) + ret = drv->probe(dev); + + if (ret) + mdev_detach_iommu(mdev); + + return ret; +} + +static int mdev_remove(struct device *dev) +{ + struct mdev_driver *drv = to_mdev_driver(dev->driver); + struct mdev_device *mdev = to_mdev_device(dev); + + if (drv && drv->remove) + drv->remove(dev); + + mdev_detach_iommu(mdev); + + return 0; +} + +struct bus_type mdev_bus_type = { + .name = "mdev", + .probe = mdev_probe, + .remove = mdev_remove, +}; +EXPORT_SYMBOL_GPL(mdev_bus_type); + +/* + * mdev_register_driver - register a new MDEV driver + * @drv: the driver to register + * @owner: module owner of driver to be registered + * + * Returns a negative value on error, otherwise 0. + */ +int mdev_register_driver(struct mdev_driver *drv, struct module *owner) +{ + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &mdev_bus_type; + drv->driver.owner = owner; + + /* register with core */ + 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); + +int mdev_bus_register(void) +{ + return bus_register(&mdev_bus_type); +} + +void mdev_bus_unregister(void) +{ + bus_unregister(&mdev_bus_type); +} diff --git a/drivers/vfio/mdev/mdev_private.h b/drivers/vfio/mdev/mdev_private.h new file mode 100644 index 000000000000..000c93fcfdbd --- /dev/null +++ b/drivers/vfio/mdev/mdev_private.h @@ -0,0 +1,41 @@ +/* + * Mediated device interal definitions + * + * 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. + */ + +#ifndef MDEV_PRIVATE_H +#define MDEV_PRIVATE_H + +int mdev_bus_register(void); +void mdev_bus_unregister(void); + +struct mdev_type { + struct kobject kobj; + struct kobject *devices_kobj; + struct parent_device *parent; + struct list_head next; + struct attribute_group *group; +}; + +#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 parent_device *parent); +void parent_remove_sysfs_files(struct parent_device *parent); + +int mdev_create_sysfs_files(struct device *dev, struct mdev_type *type); +void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type); + +int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid); +int mdev_device_remove(struct device *dev, bool force_remove); + +#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 000000000000..d808a41e4f68 --- /dev/null +++ b/drivers/vfio/mdev/mdev_sysfs.c @@ -0,0 +1,286 @@ +/* + * File attributes for Mediated devices + * + * 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/sysfs.h> +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include <linux/mdev.h> + +#include "mdev_private.h" + +/* Static functions */ + +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(kobj, type->parent->dev, 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->kobj, type->parent->dev, 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 kobject *kobj, struct device *dev, + const char *buf, size_t count) +{ + char *str; + uuid_le 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 = uuid_le_to_bin(str, &uuid); + kfree(str); + if (ret) + return ret; + + ret = mdev_device_create(kobj, dev, uuid); + if (ret) + return ret; + + return count; +} + +MDEV_TYPE_ATTR_WO(create); + +static void mdev_type_release(struct kobject *kobj) +{ + struct mdev_type *type = to_mdev_type(kobj); + + pr_debug("Releasing group %s\n", kobj->name); + kfree(type); +} + +static struct kobj_type mdev_type_ktype = { + .sysfs_ops = &mdev_type_sysfs_ops, + .release = mdev_type_release, +}; + +struct mdev_type *add_mdev_supported_type(struct parent_device *parent, + struct attribute_group *group) +{ + struct mdev_type *type; + int ret; + + if (!group->name) { + pr_err("%s: Type name empty!\n", __func__); + return ERR_PTR(-EINVAL); + } + + type = kzalloc(sizeof(*type), GFP_KERNEL); + if (!type) + return ERR_PTR(-ENOMEM); + + type->kobj.kset = parent->mdev_types_kset; + + ret = kobject_init_and_add(&type->kobj, &mdev_type_ktype, NULL, + "%s-%s", dev_driver_string(parent->dev), + group->name); + if (ret) { + kfree(type); + return ERR_PTR(ret); + } + + ret = sysfs_create_file(&type->kobj, &mdev_type_attr_create.attr); + if (ret) + goto attr_create_failed; + + type->devices_kobj = kobject_create_and_add("devices", &type->kobj); + if (!type->devices_kobj) { + ret = -ENOMEM; + goto attr_devices_failed; + } + + ret = sysfs_create_files(&type->kobj, + (const struct attribute **)group->attrs); + if (ret) { + ret = -ENOMEM; + goto attrs_failed; + } + + type->group = group; + type->parent = parent; + return type; + +attrs_failed: + kobject_put(type->devices_kobj); +attr_devices_failed: + sysfs_remove_file(&type->kobj, &mdev_type_attr_create.attr); +attr_create_failed: + kobject_del(&type->kobj); + kobject_put(&type->kobj); + return ERR_PTR(ret); +} + +static void remove_mdev_supported_type(struct mdev_type *type) +{ + sysfs_remove_files(&type->kobj, + (const struct attribute **)type->group->attrs); + kobject_put(type->devices_kobj); + sysfs_remove_file(&type->kobj, &mdev_type_attr_create.attr); + kobject_del(&type->kobj); + kobject_put(&type->kobj); +} + +static int add_mdev_supported_type_groups(struct parent_device *parent) +{ + int i; + + for (i = 0; parent->ops->supported_type_groups[i]; i++) { + struct mdev_type *type; + + type = add_mdev_supported_type(parent, + parent->ops->supported_type_groups[i]); + if (IS_ERR(type)) { + struct mdev_type *ltype, *tmp; + + list_for_each_entry_safe(ltype, tmp, &parent->type_list, + next) { + list_del(<ype->next); + remove_mdev_supported_type(ltype); + } + return PTR_ERR(type); + } + list_add(&type->next, &parent->type_list); + } + return 0; +} + +/* mdev sysfs Functions */ +void parent_remove_sysfs_files(struct parent_device *parent) +{ + struct mdev_type *type, *tmp; + + list_for_each_entry_safe(type, tmp, &parent->type_list, next) { + list_del(&type->next); + remove_mdev_supported_type(type); + } + + sysfs_remove_groups(&parent->dev->kobj, parent->ops->dev_attr_groups); + kset_unregister(parent->mdev_types_kset); +} + +int parent_create_sysfs_files(struct parent_device *parent) +{ + int ret; + + parent->mdev_types_kset = kset_create_and_add("mdev_supported_types", + NULL, &parent->dev->kobj); + + if (!parent->mdev_types_kset) + return -ENOMEM; + + INIT_LIST_HEAD(&parent->type_list); + + ret = sysfs_create_groups(&parent->dev->kobj, + parent->ops->dev_attr_groups); + if (ret) + goto create_err; + + ret = add_mdev_supported_type_groups(parent); + if (ret) + sysfs_remove_groups(&parent->dev->kobj, + parent->ops->dev_attr_groups); + else + return ret; + +create_err: + 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) +{ + 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(dev, false); + if (ret) { + device_create_file(dev, attr); + return ret; + } + } + + return count; +} + +static DEVICE_ATTR_WO(remove); + +static const struct attribute *mdev_device_attrs[] = { + &dev_attr_remove.attr, + NULL, +}; + +int mdev_create_sysfs_files(struct device *dev, struct mdev_type *type) +{ + int ret; + + ret = sysfs_create_files(&dev->kobj, mdev_device_attrs); + if (ret) + return ret; + + ret = sysfs_create_link(type->devices_kobj, &dev->kobj, dev_name(dev)); + if (ret) + goto device_link_failed; + + ret = sysfs_create_link(&dev->kobj, &type->kobj, "mdev_type"); + if (ret) + goto type_link_failed; + + return ret; + +type_link_failed: + sysfs_remove_link(type->devices_kobj, dev_name(dev)); +device_link_failed: + sysfs_remove_files(&dev->kobj, mdev_device_attrs); + return ret; +} + +void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type) +{ + sysfs_remove_link(&dev->kobj, "mdev_type"); + sysfs_remove_link(type->devices_kobj, dev_name(dev)); + sysfs_remove_files(&dev->kobj, mdev_device_attrs); +} diff --git a/include/linux/mdev.h b/include/linux/mdev.h new file mode 100644 index 000000000000..0352febc1944 --- /dev/null +++ b/include/linux/mdev.h @@ -0,0 +1,167 @@ +/* + * Mediated device definition + * + * 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. + */ + +#ifndef MDEV_H +#define MDEV_H + +/* Parent Device */ +struct parent_device { + struct device *dev; + const struct parent_ops *ops; + + /* internal */ + struct kref ref; + struct mutex lock; + struct list_head next; + struct kset *mdev_types_kset; + struct list_head type_list; +}; + +/* Mediated device */ +struct mdev_device { + struct device dev; + struct parent_device *parent; + uuid_le uuid; + void *driver_data; + + /* internal */ + struct kref ref; + struct list_head next; + struct kobject *type_kobj; +}; + + +/** + * struct parent_ops - Structure to be registered for each parent device to + * register the device to mdev module. + * + * @owner: The module owner. + * @dev_attr_groups: Attributes of the parent device. + * @mdev_attr_groups: Attributes of the mediated device. + * @supported_type_groups: Attributes to define supported types. It is mandatory + * to provide supported types. + * @create: Called to allocate basic resources in parent device's + * driver for a particular mediated device. It is + * mandatory to provide create ops. + * @kobj: kobject of type for which 'create' is called. + * @mdev: mdev_device structure on of mediated device + * that is being created + * Returns integer: success (0) or error (< 0) + * @remove: Called to free resources in parent device's driver for a + * a mediated device. It is mandatory to provide 'remove' + * ops. + * @mdev: mdev_device device structure which is being + * destroyed + * Returns integer: success (0) or error (< 0) + * @open: Open mediated device. + * @mdev: mediated device. + * Returns integer: success (0) or error (< 0) + * @release: release mediated device + * @mdev: mediated device. + * @read: Read emulation callback + * @mdev: mediated device structure + * @buf: read buffer + * @count: number of bytes to read + * @ppos: address. + * Retuns number on bytes read on success or error. + * @write: Write emulation callback + * @mdev: mediated device structure + * @buf: write buffer + * @count: number of bytes to be written + * @ppos: address. + * Retuns number on bytes written on success or error. + * @ioctl: IOCTL callback + * @mdev: mediated device structure + * @cmd: mediated device structure + * @arg: mediated device structure + * @mmap: mmap callback + * Parent device that support mediated device should be registered with mdev + * module with parent_ops structure. + **/ + +struct parent_ops { + struct module *owner; + const struct attribute_group **dev_attr_groups; + const struct attribute_group **mdev_attr_groups; + struct attribute_group **supported_type_groups; + + int (*create)(struct kobject *kobj, struct mdev_device *mdev); + int (*remove)(struct mdev_device *mdev); + int (*open)(struct mdev_device *mdev); + void (*release)(struct mdev_device *mdev); + ssize_t (*read)(struct mdev_device *mdev, char __user *buf, + size_t count, loff_t *ppos); + ssize_t (*write)(struct mdev_device *mdev, const char __user *buf, + size_t count, loff_t *ppos); + ssize_t (*ioctl)(struct mdev_device *mdev, unsigned int cmd, + unsigned long arg); + int (*mmap)(struct mdev_device *mdev, struct vm_area_struct *vma); +}; + +/* interface for exporting mdev supported type attributes */ +struct mdev_type_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct device *dev, char *buf); + ssize_t (*store)(struct kobject *kobj, struct device *dev, + const char *buf, size_t count); +}; + +#define MDEV_TYPE_ATTR(_name, _mode, _show, _store) \ +struct mdev_type_attribute mdev_type_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) +#define MDEV_TYPE_ATTR_RW(_name) \ + struct mdev_type_attribute mdev_type_attr_##_name = __ATTR_RW(_name) +#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) + +/** + * struct mdev_driver - Mediated device driver + * @name: driver name + * @probe: called when new device created + * @remove: called when device removed + * @driver: device driver structure + * + **/ +struct mdev_driver { + const char *name; + int (*probe)(struct device *dev); + void (*remove)(struct device *dev); + struct device_driver driver; +}; + +#define to_mdev_driver(drv) container_of(drv, struct mdev_driver, driver) +#define to_mdev_device(dev) container_of(dev, struct mdev_device, dev) + +static inline void *mdev_get_drvdata(struct mdev_device *mdev) +{ + return mdev->driver_data; +} + +static inline void mdev_set_drvdata(struct mdev_device *mdev, void *data) +{ + mdev->driver_data = data; +} + +extern struct bus_type mdev_bus_type; + +#define dev_is_mdev(d) ((d)->bus == &mdev_bus_type) + +extern int mdev_register_device(struct device *dev, + const struct parent_ops *ops); +extern void mdev_unregister_device(struct device *dev); + +extern int mdev_register_driver(struct mdev_driver *drv, struct module *owner); +extern void mdev_unregister_driver(struct mdev_driver *drv); + +#endif /* MDEV_H */