diff options
Diffstat (limited to 'drivers/xen/pci.c')
| -rw-r--r-- | drivers/xen/pci.c | 156 | 
1 files changed, 141 insertions, 15 deletions
diff --git a/drivers/xen/pci.c b/drivers/xen/pci.c index cef4bafc07d..dd9c249ea31 100644 --- a/drivers/xen/pci.c +++ b/drivers/xen/pci.c @@ -18,6 +18,7 @@   */  #include <linux/pci.h> +#include <linux/acpi.h>  #include <xen/xen.h>  #include <xen/interface/physdev.h>  #include <xen/interface/xen.h> @@ -25,27 +26,89 @@  #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 physdev_pci_device_add add = { +			.seg = pci_domain_nr(pci_dev->bus), +			.bus = pci_dev->bus->number, +			.devfn = pci_dev->devfn +		}; +#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); +		if (!handle && pci_dev->bus->bridge) +			handle = ACPI_HANDLE(pci_dev->bus->bridge);  #ifdef CONFIG_PCI_IOV -	if (pci_dev->is_virtfn) { +		if (!handle && pci_dev->is_virtfn) +			handle = ACPI_HANDLE(physfn->bus->bridge); +#endif +		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	= pci_dev->physfn->bus->number, -			.physfn.devfn	= pci_dev->physfn->devfn, +			.physfn.bus	= physfn->bus->number, +			.physfn.devfn	= physfn->devfn,  		};  		r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_add_ext,  			&manage_pci_ext); -	} else +	}  #endif -	if (pci_ari_enabled(pci_dev->bus) && PCI_SLOT(pci_dev->devfn)) { +	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, @@ -56,7 +119,7 @@ static int xen_add_device(struct device *dev)  			&manage_pci_ext);  	} else {  		struct physdev_manage_pci manage_pci = { -			.bus 	= pci_dev->bus->number, +			.bus	= pci_dev->bus->number,  			.devfn	= pci_dev->devfn,  		}; @@ -71,13 +134,27 @@ static int xen_remove_device(struct device *dev)  {  	int r;  	struct pci_dev *pci_dev = to_pci_dev(dev); -	struct physdev_manage_pci manage_pci; -	manage_pci.bus = pci_dev->bus->number; -	manage_pci.devfn = pci_dev->devfn; +	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); +		r = HYPERVISOR_physdev_op(PHYSDEVOP_manage_pci_remove, +					  &manage_pci); +	}  	return r;  } @@ -96,13 +173,16 @@ static int xen_pci_notifier(struct notifier_block *nb,  		r = xen_remove_device(dev);  		break;  	default: -		break; +		return NOTIFY_DONE;  	} - -	return r; +	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;  } -struct notifier_block device_nb = { +static struct notifier_block device_nb = {  	.notifier_call = xen_pci_notifier,  }; @@ -115,3 +195,49 @@ static int __init register_xen_pci_notifier(void)  }  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  | 
