summaryrefslogtreecommitdiff
path: root/drivers/pci/controller/vmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/controller/vmd.c')
-rw-r--r--drivers/pci/controller/vmd.c102
1 files changed, 91 insertions, 11 deletions
diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c
index e3fcdfec58b3..a45e8e59d3d4 100644
--- a/drivers/pci/controller/vmd.c
+++ b/drivers/pci/controller/vmd.c
@@ -6,19 +6,19 @@
#include <linux/device.h>
#include <linux/interrupt.h>
+#include <linux/iommu.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/pci.h>
+#include <linux/pci-acpi.h>
#include <linux/pci-ecam.h>
#include <linux/srcu.h>
#include <linux/rculist.h>
#include <linux/rcupdate.h>
#include <asm/irqdomain.h>
-#include <asm/device.h>
-#include <asm/msi.h>
#define VMD_CFGBAR 0
#define VMD_MEMBAR1 2
@@ -69,6 +69,8 @@ enum vmd_features {
VMD_FEAT_CAN_BYPASS_MSI_REMAP = (1 << 4),
};
+static DEFINE_IDA(vmd_instance_ida);
+
/*
* Lock for manipulating VMD IRQ lists.
*/
@@ -119,6 +121,8 @@ struct vmd_dev {
struct pci_bus *bus;
u8 busn_start;
u8 first_vec;
+ char *name;
+ int instance;
};
static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
@@ -447,6 +451,56 @@ static struct pci_ops vmd_ops = {
.write = vmd_pci_write,
};
+#ifdef CONFIG_ACPI
+static struct acpi_device *vmd_acpi_find_companion(struct pci_dev *pci_dev)
+{
+ struct pci_host_bridge *bridge;
+ u32 busnr, addr;
+
+ if (pci_dev->bus->ops != &vmd_ops)
+ return NULL;
+
+ bridge = pci_find_host_bridge(pci_dev->bus);
+ busnr = pci_dev->bus->number - bridge->bus->number;
+ /*
+ * The address computation below is only applicable to relative bus
+ * numbers below 32.
+ */
+ if (busnr > 31)
+ return NULL;
+
+ addr = (busnr << 24) | ((u32)pci_dev->devfn << 16) | 0x8000FFFFU;
+
+ dev_dbg(&pci_dev->dev, "Looking for ACPI companion (address 0x%x)\n",
+ addr);
+
+ return acpi_find_child_device(ACPI_COMPANION(bridge->dev.parent), addr,
+ false);
+}
+
+static bool hook_installed;
+
+static void vmd_acpi_begin(void)
+{
+ if (pci_acpi_set_companion_lookup_hook(vmd_acpi_find_companion))
+ return;
+
+ hook_installed = true;
+}
+
+static void vmd_acpi_end(void)
+{
+ if (!hook_installed)
+ return;
+
+ pci_acpi_clear_companion_lookup_hook();
+ hook_installed = false;
+}
+#else
+static inline void vmd_acpi_begin(void) { }
+static inline void vmd_acpi_end(void) { }
+#endif /* CONFIG_ACPI */
+
static void vmd_attach_resources(struct vmd_dev *vmd)
{
vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[1];
@@ -599,7 +653,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd)
INIT_LIST_HEAD(&vmd->irqs[i].irq_list);
err = devm_request_irq(&dev->dev, pci_irq_vector(dev, i),
vmd_irq, IRQF_NO_THREAD,
- "vmd", &vmd->irqs[i]);
+ vmd->name, &vmd->irqs[i]);
if (err)
return err;
}
@@ -710,7 +764,8 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
* acceptable because the guest is usually CPU-limited and MSI
* remapping doesn't become a performance bottleneck.
*/
- if (!(features & VMD_FEAT_CAN_BYPASS_MSI_REMAP) ||
+ if (iommu_capable(vmd->dev->dev.bus, IOMMU_CAP_INTR_REMAP) ||
+ !(features & VMD_FEAT_CAN_BYPASS_MSI_REMAP) ||
offset[0] || offset[1]) {
ret = vmd_alloc_irqs(vmd);
if (ret)
@@ -747,6 +802,8 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
if (vmd->irq_domain)
dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain);
+ vmd_acpi_begin();
+
pci_scan_child_bus(vmd->bus);
pci_assign_unassigned_bus_resources(vmd->bus);
@@ -760,6 +817,8 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
pci_bus_add_devices(vmd->bus);
+ vmd_acpi_end();
+
WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj,
"domain"), "Can't create symlink to domain\n");
return 0;
@@ -779,18 +838,32 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
return -ENOMEM;
vmd->dev = dev;
+ vmd->instance = ida_simple_get(&vmd_instance_ida, 0, 0, GFP_KERNEL);
+ if (vmd->instance < 0)
+ return vmd->instance;
+
+ vmd->name = kasprintf(GFP_KERNEL, "vmd%d", vmd->instance);
+ if (!vmd->name) {
+ err = -ENOMEM;
+ goto out_release_instance;
+ }
+
err = pcim_enable_device(dev);
if (err < 0)
- return err;
+ goto out_release_instance;
vmd->cfgbar = pcim_iomap(dev, VMD_CFGBAR, 0);
- if (!vmd->cfgbar)
- return -ENOMEM;
+ if (!vmd->cfgbar) {
+ err = -ENOMEM;
+ goto out_release_instance;
+ }
pci_set_master(dev);
if (dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(64)) &&
- dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)))
- return -ENODEV;
+ dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32))) {
+ err = -ENODEV;
+ goto out_release_instance;
+ }
if (features & VMD_FEAT_OFFSET_FIRST_VECTOR)
vmd->first_vec = 1;
@@ -799,11 +872,16 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
pci_set_drvdata(dev, vmd);
err = vmd_enable_domain(vmd, features);
if (err)
- return err;
+ goto out_release_instance;
dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n",
vmd->sysdata.domain);
return 0;
+
+ out_release_instance:
+ ida_simple_remove(&vmd_instance_ida, vmd->instance);
+ kfree(vmd->name);
+ return err;
}
static void vmd_cleanup_srcu(struct vmd_dev *vmd)
@@ -824,6 +902,8 @@ static void vmd_remove(struct pci_dev *dev)
vmd_cleanup_srcu(vmd);
vmd_detach_resources(vmd);
vmd_remove_irq_domain(vmd);
+ ida_simple_remove(&vmd_instance_ida, vmd->instance);
+ kfree(vmd->name);
}
#ifdef CONFIG_PM_SLEEP
@@ -848,7 +928,7 @@ static int vmd_resume(struct device *dev)
for (i = 0; i < vmd->msix_count; i++) {
err = devm_request_irq(dev, pci_irq_vector(pdev, i),
vmd_irq, IRQF_NO_THREAD,
- "vmd", &vmd->irqs[i]);
+ vmd->name, &vmd->irqs[i]);
if (err)
return err;
}