return (pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA;
}
+static void vfio_pci_probe_mmaps(struct vfio_pci_device *vdev)
+{
+ struct resource *res;
+ int bar;
+ struct vfio_pci_dummy_resource *dummy_res;
+
+ INIT_LIST_HEAD(&vdev->dummy_resources_list);
+
+ for (bar = PCI_STD_RESOURCES; bar <= PCI_STD_RESOURCE_END; bar++) {
+ res = vdev->pdev->resource + bar;
+
+ if (!IS_ENABLED(CONFIG_VFIO_PCI_MMAP))
+ goto no_mmap;
+
+ if (!(res->flags & IORESOURCE_MEM))
+ goto no_mmap;
+
+ /*
+ * The PCI core shouldn't set up a resource with a
+ * type but zero size. But there may be bugs that
+ * cause us to do that.
+ */
+ if (!resource_size(res))
+ goto no_mmap;
+
+ if (resource_size(res) >= PAGE_SIZE) {
+ vdev->bar_mmap_supported[bar] = true;
+ continue;
+ }
+
+ if (!(res->start & ~PAGE_MASK)) {
+ /*
+ * Add a dummy resource to reserve the remainder
+ * of the exclusive page in case that hot-add
+ * device's bar is assigned into it.
+ */
+ dummy_res = kzalloc(sizeof(*dummy_res), GFP_KERNEL);
+ if (dummy_res == NULL)
+ goto no_mmap;
+
+ dummy_res->resource.name = "vfio sub-page reserved";
+ dummy_res->resource.start = res->end + 1;
+ dummy_res->resource.end = res->start + PAGE_SIZE - 1;
+ dummy_res->resource.flags = res->flags;
+ if (request_resource(res->parent,
+ &dummy_res->resource)) {
+ kfree(dummy_res);
+ goto no_mmap;
+ }
+ dummy_res->index = bar;
+ list_add(&dummy_res->res_next,
+ &vdev->dummy_resources_list);
+ vdev->bar_mmap_supported[bar] = true;
+ continue;
+ }
+ /*
+ * Here we don't handle the case when the BAR is not page
+ * aligned because we can't expect the BAR will be
+ * assigned into the same location in a page in guest
+ * when we passthrough the BAR. And it's hard to access
+ * this BAR in userspace because we have no way to get
+ * the BAR's location in a page.
+ */
+no_mmap:
+ vdev->bar_mmap_supported[bar] = false;
+ }
+}
+
static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev);
static void vfio_pci_disable(struct vfio_pci_device *vdev);
}
}
+ vfio_pci_probe_mmaps(vdev);
+
return 0;
}
static void vfio_pci_disable(struct vfio_pci_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
+ struct vfio_pci_dummy_resource *dummy_res, *tmp;
int i, bar;
/* Stop the device from further DMA */
vdev->barmap[bar] = NULL;
}
+ list_for_each_entry_safe(dummy_res, tmp,
+ &vdev->dummy_resources_list, res_next) {
+ list_del(&dummy_res->res_next);
+ release_resource(&dummy_res->resource);
+ kfree(dummy_res);
+ }
+
vdev->needs_reset = true;
/*
info.flags = VFIO_REGION_INFO_FLAG_READ |
VFIO_REGION_INFO_FLAG_WRITE;
- if (IS_ENABLED(CONFIG_VFIO_PCI_MMAP) &&
- pci_resource_flags(pdev, info.index) &
- IORESOURCE_MEM && info.size >= PAGE_SIZE) {
+ if (vdev->bar_mmap_supported[info.index]) {
info.flags |= VFIO_REGION_INFO_FLAG_MMAP;
if (info.index == vdev->msix_bar) {
ret = msix_sparse_mmap_cap(vdev, &caps);
return -EINVAL;
if (index >= VFIO_PCI_ROM_REGION_INDEX)
return -EINVAL;
- if (!(pci_resource_flags(pdev, index) & IORESOURCE_MEM))
+ if (!vdev->bar_mmap_supported[index])
return -EINVAL;
- phys_len = pci_resource_len(pdev, index);
+ phys_len = PAGE_ALIGN(pci_resource_len(pdev, index));
req_len = vma->vm_end - vma->vm_start;
pgoff = vma->vm_pgoff &
((1U << (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT)) - 1);
req_start = pgoff << PAGE_SHIFT;
- if (phys_len < PAGE_SIZE || req_start + req_len > phys_len)
+ if (req_start + req_len > phys_len)
return -EINVAL;
if (index == vdev->msix_bar) {
*/
#include <linux/device.h>
+#include <linux/acpi.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/mutex.h>
#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);
if (!strcmp(iter->compat, compat) &&
try_module_get(iter->owner)) {
*module = iter->owner;
- reset_fn = iter->reset;
+ reset_fn = iter->of_reset;
break;
}
}
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);
}
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;
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);
mutex_lock(&driver_lock);
if (!vdev->refcnt) {
+ const char *extra_dbg = NULL;
+
ret = vfio_platform_regions_init(vdev);
if (ret)
goto err_reg;
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;
}
}
mutex_unlock(&driver_lock);
return 0;
+err_rst:
+ vfio_platform_irq_cleanup(vdev);
err_irq:
vfio_platform_regions_cleanup(vdev);
err_reg:
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;
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;
.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)
{
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;
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;
if (vdev) {
vfio_platform_put_reset(vdev);
- iommu_group_put(dev->iommu_group);
+ vfio_iommu_group_put(dev->iommu_group, dev);
}
return vdev;
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;
}