vfio: Fix lockdep issue
[platform/kernel/linux-rpi.git] / drivers / vfio / vfio.c
index e1278fe..563c510 100644 (file)
@@ -661,23 +661,51 @@ int vfio_add_group_dev(struct device *dev,
 EXPORT_SYMBOL_GPL(vfio_add_group_dev);
 
 /**
- * Get a reference to the vfio_device for a device that is known to
- * be bound to a vfio driver.  The driver implicitly holds a
- * vfio_device reference between vfio_add_group_dev and
- * vfio_del_group_dev.  We can therefore use drvdata to increment
- * that reference from the struct device.  This additional
- * reference must be released by calling vfio_device_put.
+ * Get a reference to the vfio_device for a device.  Even if the
+ * caller thinks they own the device, they could be racing with a
+ * release call path, so we can't trust drvdata for the shortcut.
+ * Go the long way around, from the iommu_group to the vfio_group
+ * to the vfio_device.
  */
 struct vfio_device *vfio_device_get_from_dev(struct device *dev)
 {
-       struct vfio_device *device = dev_get_drvdata(dev);
+       struct iommu_group *iommu_group;
+       struct vfio_group *group;
+       struct vfio_device *device;
 
-       vfio_device_get(device);
+       iommu_group = iommu_group_get(dev);
+       if (!iommu_group)
+               return NULL;
+
+       group = vfio_group_get_from_iommu(iommu_group);
+       iommu_group_put(iommu_group);
+       if (!group)
+               return NULL;
+
+       device = vfio_group_get_device(group, dev);
+       vfio_group_put(group);
 
        return device;
 }
 EXPORT_SYMBOL_GPL(vfio_device_get_from_dev);
 
+static struct vfio_device *vfio_device_get_from_name(struct vfio_group *group,
+                                                    char *buf)
+{
+       struct vfio_device *device;
+
+       mutex_lock(&group->device_lock);
+       list_for_each_entry(device, &group->device_list, group_next) {
+               if (!strcmp(dev_name(device->dev), buf)) {
+                       vfio_device_get(device);
+                       break;
+               }
+       }
+       mutex_unlock(&group->device_lock);
+
+       return device;
+}
+
 /*
  * Caller must hold a reference to the vfio_device
  */
@@ -1187,53 +1215,53 @@ static int vfio_group_get_device_fd(struct vfio_group *group, char *buf)
 {
        struct vfio_device *device;
        struct file *filep;
-       int ret = -ENODEV;
+       int ret;
 
        if (0 == atomic_read(&group->container_users) ||
            !group->container->iommu_driver || !vfio_group_viable(group))
                return -EINVAL;
 
-       mutex_lock(&group->device_lock);
-       list_for_each_entry(device, &group->device_list, group_next) {
-               if (strcmp(dev_name(device->dev), buf))
-                       continue;
+       device = vfio_device_get_from_name(group, buf);
+       if (!device)
+               return -ENODEV;
 
-               ret = device->ops->open(device->device_data);
-               if (ret)
-                       break;
-               /*
-                * We can't use anon_inode_getfd() because we need to modify
-                * the f_mode flags directly to allow more than just ioctls
-                */
-               ret = get_unused_fd_flags(O_CLOEXEC);
-               if (ret < 0) {
-                       device->ops->release(device->device_data);
-                       break;
-               }
+       ret = device->ops->open(device->device_data);
+       if (ret) {
+               vfio_device_put(device);
+               return ret;
+       }
 
-               filep = anon_inode_getfile("[vfio-device]", &vfio_device_fops,
-                                          device, O_RDWR);
-               if (IS_ERR(filep)) {
-                       put_unused_fd(ret);
-                       ret = PTR_ERR(filep);
-                       device->ops->release(device->device_data);
-                       break;
-               }
+       /*
+        * We can't use anon_inode_getfd() because we need to modify
+        * the f_mode flags directly to allow more than just ioctls
+        */
+       ret = get_unused_fd_flags(O_CLOEXEC);
+       if (ret < 0) {
+               device->ops->release(device->device_data);
+               vfio_device_put(device);
+               return ret;
+       }
 
-               /*
-                * TODO: add an anon_inode interface to do this.
-                * Appears to be missing by lack of need rather than
-                * explicitly prevented.  Now there's need.
-                */
-               filep->f_mode |= (FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
+       filep = anon_inode_getfile("[vfio-device]", &vfio_device_fops,
+                                  device, O_RDWR);
+       if (IS_ERR(filep)) {
+               put_unused_fd(ret);
+               ret = PTR_ERR(filep);
+               device->ops->release(device->device_data);
+               vfio_device_put(device);
+               return ret;
+       }
 
-               vfio_device_get(device);
-               atomic_inc(&group->container_users);
+       /*
+        * TODO: add an anon_inode interface to do this.
+        * Appears to be missing by lack of need rather than
+        * explicitly prevented.  Now there's need.
+        */
+       filep->f_mode |= (FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
 
-               fd_install(ret, filep);
-               break;
-       }
-       mutex_unlock(&group->device_lock);
+       atomic_inc(&group->container_users);
+
+       fd_install(ret, filep);
 
        return ret;
 }