| // 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/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) | 
 | 		dev_info(&mdev->dev, "MDEV: group_id = %d\n", | 
 | 			 iommu_group_id(group)); | 
 |  | 
 | 	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 = | 
 | 		container_of(dev->driver, struct mdev_driver, driver); | 
 | 	struct mdev_device *mdev = to_mdev_device(dev); | 
 | 	int ret; | 
 |  | 
 | 	ret = mdev_attach_iommu(mdev); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (drv->probe) { | 
 | 		ret = drv->probe(mdev); | 
 | 		if (ret) | 
 | 			mdev_detach_iommu(mdev); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void mdev_remove(struct device *dev) | 
 | { | 
 | 	struct mdev_driver *drv = | 
 | 		container_of(dev->driver, struct mdev_driver, driver); | 
 | 	struct mdev_device *mdev = to_mdev_device(dev); | 
 |  | 
 | 	if (drv->remove) | 
 | 		drv->remove(mdev); | 
 |  | 
 | 	mdev_detach_iommu(mdev); | 
 | } | 
 |  | 
 | static int mdev_match(struct device *dev, struct device_driver *drv) | 
 | { | 
 | 	/* | 
 | 	 * No drivers automatically match. Drivers are only bound by explicit | 
 | 	 * device_driver_attach() | 
 | 	 */ | 
 | 	return 0; | 
 | } | 
 |  | 
 | struct bus_type mdev_bus_type = { | 
 | 	.name		= "mdev", | 
 | 	.probe		= mdev_probe, | 
 | 	.remove		= mdev_remove, | 
 | 	.match		= mdev_match, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(mdev_bus_type); | 
 |  | 
 | /** | 
 |  * 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) | 
 | { | 
 | 	/* initialize common driver fields */ | 
 | 	drv->driver.bus = &mdev_bus_type; | 
 |  | 
 | 	/* 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); | 
 | } |