Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / xen / pci.c
diff --git a/kernel/drivers/xen/pci.c b/kernel/drivers/xen/pci.c
new file mode 100644 (file)
index 0000000..7494dbe
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2009, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Author: Weidong Han <weidong.han@intel.com>
+ */
+
+#include <linux/pci.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <xen/xen.h>
+#include <xen/interface/physdev.h>
+#include <xen/interface/xen.h>
+
+#include <asm/xen/hypervisor.h>
+#include <asm/xen/hypercall.h>
+#include "../pci/pci.h"
+#ifdef CONFIG_PCI_MMCONFIG
+#include <asm/pci_x86.h>
+#endif
+
+static bool __read_mostly pci_seg_supported = true;
+
+static int xen_add_device(struct device *dev)
+{
+       int r;
+       struct pci_dev *pci_dev = to_pci_dev(dev);
+#ifdef CONFIG_PCI_IOV
+       struct pci_dev *physfn = pci_dev->physfn;
+#endif
+
+       if (pci_seg_supported) {
+               struct {
+                       struct physdev_pci_device_add add;
+                       uint32_t pxm;
+               } add_ext = {
+                       .add.seg = pci_domain_nr(pci_dev->bus),
+                       .add.bus = pci_dev->bus->number,
+                       .add.devfn = pci_dev->devfn
+               };
+               struct physdev_pci_device_add *add = &add_ext.add;
+
+#ifdef CONFIG_ACPI
+               acpi_handle handle;
+#endif
+
+#ifdef CONFIG_PCI_IOV
+               if (pci_dev->is_virtfn) {
+                       add->flags = XEN_PCI_DEV_VIRTFN;
+                       add->physfn.bus = physfn->bus->number;
+                       add->physfn.devfn = physfn->devfn;
+               } else
+#endif
+               if (pci_ari_enabled(pci_dev->bus) && PCI_SLOT(pci_dev->devfn))
+                       add->flags = XEN_PCI_DEV_EXTFN;
+
+#ifdef CONFIG_ACPI
+               handle = ACPI_HANDLE(&pci_dev->dev);
+#ifdef CONFIG_PCI_IOV
+               if (!handle && pci_dev->is_virtfn)
+                       handle = ACPI_HANDLE(physfn->bus->bridge);
+#endif
+               if (!handle) {
+                       /*
+                        * This device was not listed in the ACPI name space at
+                        * all. Try to get acpi handle of parent pci bus.
+                        */
+                       struct pci_bus *pbus;
+                       for (pbus = pci_dev->bus; pbus; pbus = pbus->parent) {
+                               handle = acpi_pci_get_bridge_handle(pbus);
+                               if (handle)
+                                       break;
+                       }
+               }
+               if (handle) {
+                       acpi_status status;
+
+                       do {
+                               unsigned long long pxm;
+
+                               status = acpi_evaluate_integer(handle, "_PXM",
+                                                              NULL, &pxm);
+                               if (ACPI_SUCCESS(status)) {
+                                       add->optarr[0] = pxm;
+                                       add->flags |= XEN_PCI_DEV_PXM;
+                                       break;
+                               }
+                               status = acpi_get_parent(handle, &handle);
+                       } while (ACPI_SUCCESS(status));
+               }
+#endif /* CONFIG_ACPI */
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_pci_device_add, add);
+               if (r != -ENOSYS)
+                       return r;
+               pci_seg_supported = false;
+       }
+
+       if (pci_domain_nr(pci_dev->bus))
+               r = -ENOSYS;
+#ifdef CONFIG_PCI_IOV
+       else if (pci_dev->is_virtfn) {
+               struct physdev_manage_pci_ext manage_pci_ext = {
+                       .bus            = pci_dev->bus->number,
+                       .devfn          = pci_dev->devfn,
+                       .is_virtfn      = 1,
+                       .physfn.bus     = physfn->bus->number,
+                       .physfn.devfn   = physfn->devfn,
+               };
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_add_ext,
+                       &manage_pci_ext);
+       }
+#endif
+       else if (pci_ari_enabled(pci_dev->bus) && PCI_SLOT(pci_dev->devfn)) {
+               struct physdev_manage_pci_ext manage_pci_ext = {
+                       .bus            = pci_dev->bus->number,
+                       .devfn          = pci_dev->devfn,
+                       .is_extfn       = 1,
+               };
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_add_ext,
+                       &manage_pci_ext);
+       } else {
+               struct physdev_manage_pci manage_pci = {
+                       .bus    = pci_dev->bus->number,
+                       .devfn  = pci_dev->devfn,
+               };
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_add,
+                       &manage_pci);
+       }
+
+       return r;
+}
+
+static int xen_remove_device(struct device *dev)
+{
+       int r;
+       struct pci_dev *pci_dev = to_pci_dev(dev);
+
+       if (pci_seg_supported) {
+               struct physdev_pci_device device = {
+                       .seg = pci_domain_nr(pci_dev->bus),
+                       .bus = pci_dev->bus->number,
+                       .devfn = pci_dev->devfn
+               };
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_pci_device_remove,
+                                         &device);
+       } else if (pci_domain_nr(pci_dev->bus))
+               r = -ENOSYS;
+       else {
+               struct physdev_manage_pci manage_pci = {
+                       .bus = pci_dev->bus->number,
+                       .devfn = pci_dev->devfn
+               };
+
+               r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_remove,
+                                         &manage_pci);
+       }
+
+       return r;
+}
+
+static int xen_pci_notifier(struct notifier_block *nb,
+                           unsigned long action, void *data)
+{
+       struct device *dev = data;
+       int r = 0;
+
+       switch (action) {
+       case BUS_NOTIFY_ADD_DEVICE:
+               r = xen_add_device(dev);
+               break;
+       case BUS_NOTIFY_DEL_DEVICE:
+               r = xen_remove_device(dev);
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+       if (r)
+               dev_err(dev, "Failed to %s - passthrough or MSI/MSI-X might fail!\n",
+                       action == BUS_NOTIFY_ADD_DEVICE ? "add" :
+                       (action == BUS_NOTIFY_DEL_DEVICE ? "delete" : "?"));
+       return NOTIFY_OK;
+}
+
+static struct notifier_block device_nb = {
+       .notifier_call = xen_pci_notifier,
+};
+
+static int __init register_xen_pci_notifier(void)
+{
+       if (!xen_initial_domain())
+               return 0;
+
+       return bus_register_notifier(&pci_bus_type, &device_nb);
+}
+
+arch_initcall(register_xen_pci_notifier);
+
+#ifdef CONFIG_PCI_MMCONFIG
+static int __init xen_mcfg_late(void)
+{
+       struct pci_mmcfg_region *cfg;
+       int rc;
+
+       if (!xen_initial_domain())
+               return 0;
+
+       if ((pci_probe & PCI_PROBE_MMCONF) == 0)
+               return 0;
+
+       if (list_empty(&pci_mmcfg_list))
+               return 0;
+
+       /* Check whether they are in the right area. */
+       list_for_each_entry(cfg, &pci_mmcfg_list, list) {
+               struct physdev_pci_mmcfg_reserved r;
+
+               r.address = cfg->address;
+               r.segment = cfg->segment;
+               r.start_bus = cfg->start_bus;
+               r.end_bus = cfg->end_bus;
+               r.flags = XEN_PCI_MMCFG_RESERVED;
+
+               rc = HYPERVISOR_physdev_op(PHYSDEVOP_pci_mmcfg_reserved, &r);
+               switch (rc) {
+               case 0:
+               case -ENOSYS:
+                       continue;
+
+               default:
+                       pr_warn("Failed to report MMCONFIG reservation"
+                               " state for %s to hypervisor"
+                               " (%d)\n",
+                               cfg->name, rc);
+               }
+       }
+       return 0;
+}
+/*
+ * Needs to be done after acpi_init which are subsys_initcall.
+ */
+subsys_initcall_sync(xen_mcfg_late);
+#endif