Merge branch 'for-linus-4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/mason...
[cascardo/linux.git] / drivers / vfio / platform / vfio_platform_common.c
index e65b142..1cf2d46 100644 (file)
@@ -13,6 +13,7 @@
  */
 
 #include <linux/device.h>
+#include <linux/acpi.h>
 #include <linux/iommu.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
@@ -27,6 +28,8 @@
 #define DRIVER_AUTHOR   "Antonios Motakis <a.motakis@virtualopensystems.com>"
 #define DRIVER_DESC     "VFIO platform base module"
 
+#define VFIO_PLATFORM_IS_ACPI(vdev) ((vdev)->acpihid != NULL)
+
 static LIST_HEAD(reset_list);
 static DEFINE_MUTEX(driver_lock);
 
@@ -41,7 +44,7 @@ static vfio_platform_reset_fn_t vfio_platform_lookup_reset(const char *compat,
                if (!strcmp(iter->compat, compat) &&
                        try_module_get(iter->owner)) {
                        *module = iter->owner;
-                       reset_fn = iter->reset;
+                       reset_fn = iter->of_reset;
                        break;
                }
        }
@@ -49,20 +52,91 @@ static vfio_platform_reset_fn_t vfio_platform_lookup_reset(const char *compat,
        return reset_fn;
 }
 
-static void vfio_platform_get_reset(struct vfio_platform_device *vdev)
+static int vfio_platform_acpi_probe(struct vfio_platform_device *vdev,
+                                   struct device *dev)
 {
-       vdev->reset = vfio_platform_lookup_reset(vdev->compat,
-                                               &vdev->reset_module);
-       if (!vdev->reset) {
+       struct acpi_device *adev;
+
+       if (acpi_disabled)
+               return -ENOENT;
+
+       adev = ACPI_COMPANION(dev);
+       if (!adev) {
+               pr_err("VFIO: ACPI companion device not found for %s\n",
+                       vdev->name);
+               return -ENODEV;
+       }
+
+#ifdef CONFIG_ACPI
+       vdev->acpihid = acpi_device_hid(adev);
+#endif
+       return WARN_ON(!vdev->acpihid) ? -EINVAL : 0;
+}
+
+int vfio_platform_acpi_call_reset(struct vfio_platform_device *vdev,
+                                 const char **extra_dbg)
+{
+#ifdef CONFIG_ACPI
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+       struct device *dev = vdev->device;
+       acpi_handle handle = ACPI_HANDLE(dev);
+       acpi_status acpi_ret;
+
+       acpi_ret = acpi_evaluate_object(handle, "_RST", NULL, &buffer);
+       if (ACPI_FAILURE(acpi_ret)) {
+               if (extra_dbg)
+                       *extra_dbg = acpi_format_exception(acpi_ret);
+               return -EINVAL;
+       }
+
+       return 0;
+#else
+       return -ENOENT;
+#endif
+}
+
+bool vfio_platform_acpi_has_reset(struct vfio_platform_device *vdev)
+{
+#ifdef CONFIG_ACPI
+       struct device *dev = vdev->device;
+       acpi_handle handle = ACPI_HANDLE(dev);
+
+       return acpi_has_method(handle, "_RST");
+#else
+       return false;
+#endif
+}
+
+static bool vfio_platform_has_reset(struct vfio_platform_device *vdev)
+{
+       if (VFIO_PLATFORM_IS_ACPI(vdev))
+               return vfio_platform_acpi_has_reset(vdev);
+
+       return vdev->of_reset ? true : false;
+}
+
+static int vfio_platform_get_reset(struct vfio_platform_device *vdev)
+{
+       if (VFIO_PLATFORM_IS_ACPI(vdev))
+               return vfio_platform_acpi_has_reset(vdev) ? 0 : -ENOENT;
+
+       vdev->of_reset = vfio_platform_lookup_reset(vdev->compat,
+                                                   &vdev->reset_module);
+       if (!vdev->of_reset) {
                request_module("vfio-reset:%s", vdev->compat);
-               vdev->reset = vfio_platform_lookup_reset(vdev->compat,
-                                                        &vdev->reset_module);
+               vdev->of_reset = vfio_platform_lookup_reset(vdev->compat,
+                                                       &vdev->reset_module);
        }
+
+       return vdev->of_reset ? 0 : -ENOENT;
 }
 
 static void vfio_platform_put_reset(struct vfio_platform_device *vdev)
 {
-       if (vdev->reset)
+       if (VFIO_PLATFORM_IS_ACPI(vdev))
+               return;
+
+       if (vdev->of_reset)
                module_put(vdev->reset_module);
 }
 
@@ -134,6 +208,21 @@ static void vfio_platform_regions_cleanup(struct vfio_platform_device *vdev)
        kfree(vdev->regions);
 }
 
+static int vfio_platform_call_reset(struct vfio_platform_device *vdev,
+                                   const char **extra_dbg)
+{
+       if (VFIO_PLATFORM_IS_ACPI(vdev)) {
+               dev_info(vdev->device, "reset\n");
+               return vfio_platform_acpi_call_reset(vdev, extra_dbg);
+       } else if (vdev->of_reset) {
+               dev_info(vdev->device, "reset\n");
+               return vdev->of_reset(vdev);
+       }
+
+       dev_warn(vdev->device, "no reset function found!\n");
+       return -EINVAL;
+}
+
 static void vfio_platform_release(void *device_data)
 {
        struct vfio_platform_device *vdev = device_data;
@@ -141,11 +230,14 @@ static void vfio_platform_release(void *device_data)
        mutex_lock(&driver_lock);
 
        if (!(--vdev->refcnt)) {
-               if (vdev->reset) {
-                       dev_info(vdev->device, "reset\n");
-                       vdev->reset(vdev);
-               } else {
-                       dev_warn(vdev->device, "no reset function found!\n");
+               const char *extra_dbg = NULL;
+               int ret;
+
+               ret = vfio_platform_call_reset(vdev, &extra_dbg);
+               if (ret && vdev->reset_required) {
+                       dev_warn(vdev->device, "reset driver is required and reset call failed in release (%d) %s\n",
+                                ret, extra_dbg ? extra_dbg : "");
+                       WARN_ON(1);
                }
                vfio_platform_regions_cleanup(vdev);
                vfio_platform_irq_cleanup(vdev);
@@ -167,6 +259,8 @@ static int vfio_platform_open(void *device_data)
        mutex_lock(&driver_lock);
 
        if (!vdev->refcnt) {
+               const char *extra_dbg = NULL;
+
                ret = vfio_platform_regions_init(vdev);
                if (ret)
                        goto err_reg;
@@ -175,11 +269,11 @@ static int vfio_platform_open(void *device_data)
                if (ret)
                        goto err_irq;
 
-               if (vdev->reset) {
-                       dev_info(vdev->device, "reset\n");
-                       vdev->reset(vdev);
-               } else {
-                       dev_warn(vdev->device, "no reset function found!\n");
+               ret = vfio_platform_call_reset(vdev, &extra_dbg);
+               if (ret && vdev->reset_required) {
+                       dev_warn(vdev->device, "reset driver is required and reset call failed in open (%d) %s\n",
+                                ret, extra_dbg ? extra_dbg : "");
+                       goto err_rst;
                }
        }
 
@@ -188,6 +282,8 @@ static int vfio_platform_open(void *device_data)
        mutex_unlock(&driver_lock);
        return 0;
 
+err_rst:
+       vfio_platform_irq_cleanup(vdev);
 err_irq:
        vfio_platform_regions_cleanup(vdev);
 err_reg:
@@ -213,7 +309,7 @@ static long vfio_platform_ioctl(void *device_data,
                if (info.argsz < minsz)
                        return -EINVAL;
 
-               if (vdev->reset)
+               if (vfio_platform_has_reset(vdev))
                        vdev->flags |= VFIO_DEVICE_FLAGS_RESET;
                info.flags = vdev->flags;
                info.num_regions = vdev->num_regions;
@@ -312,10 +408,7 @@ static long vfio_platform_ioctl(void *device_data,
                return ret;
 
        } else if (cmd == VFIO_DEVICE_RESET) {
-               if (vdev->reset)
-                       return vdev->reset(vdev);
-               else
-                       return -EINVAL;
+               return vfio_platform_call_reset(vdev, NULL);
        }
 
        return -ENOTTY;
@@ -544,6 +637,37 @@ static const struct vfio_device_ops vfio_platform_ops = {
        .mmap           = vfio_platform_mmap,
 };
 
+int vfio_platform_of_probe(struct vfio_platform_device *vdev,
+                          struct device *dev)
+{
+       int ret;
+
+       ret = device_property_read_string(dev, "compatible",
+                                         &vdev->compat);
+       if (ret)
+               pr_err("VFIO: cannot retrieve compat for %s\n",
+                       vdev->name);
+
+       return ret;
+}
+
+/*
+ * There can be two kernel build combinations. One build where
+ * ACPI is not selected in Kconfig and another one with the ACPI Kconfig.
+ *
+ * In the first case, vfio_platform_acpi_probe will return since
+ * acpi_disabled is 1. DT user will not see any kind of messages from
+ * ACPI.
+ *
+ * In the second case, both DT and ACPI is compiled in but the system is
+ * booting with any of these combinations.
+ *
+ * If the firmware is DT type, then acpi_disabled is 1. The ACPI probe routine
+ * terminates immediately without any messages.
+ *
+ * If the firmware is ACPI type, then acpi_disabled is 0. All other checks are
+ * valid checks. We cannot claim that this system is DT.
+ */
 int vfio_platform_probe_common(struct vfio_platform_device *vdev,
                               struct device *dev)
 {
@@ -553,15 +677,23 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev,
        if (!vdev)
                return -EINVAL;
 
-       ret = device_property_read_string(dev, "compatible", &vdev->compat);
-       if (ret) {
-               pr_err("VFIO: cannot retrieve compat for %s\n", vdev->name);
-               return -EINVAL;
-       }
+       ret = vfio_platform_acpi_probe(vdev, dev);
+       if (ret)
+               ret = vfio_platform_of_probe(vdev, dev);
+
+       if (ret)
+               return ret;
 
        vdev->device = dev;
 
-       group = iommu_group_get(dev);
+       ret = vfio_platform_get_reset(vdev);
+       if (ret && vdev->reset_required) {
+               pr_err("vfio: no reset function found for device %s\n",
+                      vdev->name);
+               return ret;
+       }
+
+       group = vfio_iommu_group_get(dev);
        if (!group) {
                pr_err("VFIO: No IOMMU group for device %s\n", vdev->name);
                return -EINVAL;
@@ -569,12 +701,10 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev,
 
        ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev);
        if (ret) {
-               iommu_group_put(group);
+               vfio_iommu_group_put(group, dev);
                return ret;
        }
 
-       vfio_platform_get_reset(vdev);
-
        mutex_init(&vdev->igate);
 
        return 0;
@@ -589,7 +719,7 @@ struct vfio_platform_device *vfio_platform_remove_common(struct device *dev)
 
        if (vdev) {
                vfio_platform_put_reset(vdev);
-               iommu_group_put(dev->iommu_group);
+               vfio_iommu_group_put(dev->iommu_group, dev);
        }
 
        return vdev;
@@ -611,7 +741,7 @@ void vfio_platform_unregister_reset(const char *compat,
 
        mutex_lock(&driver_lock);
        list_for_each_entry_safe(iter, temp, &reset_list, link) {
-               if (!strcmp(iter->compat, compat) && (iter->reset == fn)) {
+               if (!strcmp(iter->compat, compat) && (iter->of_reset == fn)) {
                        list_del(&iter->link);
                        break;
                }