forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
5.6 KiB
256 lines
5.6 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (c) 2009, Intel Corporation. |
|
* |
|
* Author: Weidong Han <[email protected]> |
|
*/ |
|
|
|
#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> |
|
|
|
static int xen_mcfg_late(void); |
|
#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 |
|
#ifdef CONFIG_PCI_MMCONFIG |
|
static bool pci_mcfg_reserved = false; |
|
/* |
|
* Reserve MCFG areas in Xen on first invocation due to this being |
|
* potentially called from inside of acpi_init immediately after |
|
* MCFG table has been finally parsed. |
|
*/ |
|
if (!pci_mcfg_reserved) { |
|
xen_mcfg_late(); |
|
pci_mcfg_reserved = true; |
|
} |
|
#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 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; |
|
} |
|
#endif
|
|
|