PCI: allow assignment of memory resources with a specified alignment
This patch allows memory resources to be assigned with a specified
alignment at boot-time or run-time. The patch is useful when we use PCI
pass-through, because page-aligned memory resources are required to
securely share PCI resources with guest drivers.
If you want to assign the resource at boot time, please set
"pci=resource_alignment=" boot parameter.
This is format of "pci=resource_alignment=" boot parameter:
        [<order of align>@][<domain>:]<bus>:<slot>.<func>[; ...]
                Specifies alignment and device to reassign
                aligned memory resources.
                If <order of align> is not specified, PAGE_SIZE is
                used as alignment.
                PCI-PCI bridge can be specified, if resource
                windows need to be expanded.
This is example:
        pci=resource_alignment=20@07:00.0;18@0f:00.0;00:1d.7
If you want to assign the resource at run-time, please set
"/sys/bus/pci/resource_alignment" file, and hot-remove the device and
hot-add the device.  For this purpose, fakephp or PCI hotplug interfaces
can be used.
The format of "/sys/bus/pci/resource_alignment" file is the same with
boot parameter. You can use "," instead of ";".
For example:
        # cd /sys/bus/pci
        # echo -n 20@12:00.0 > resource_alignment
        # echo 1 > devices/0000:12:00.0/remove
        # echo 1 > rescan
Reviewed-by: Alex Chiang <achiang@hp.com>
Reviewed-by: Yu Zhao <yu.zhao@intel.com>
Signed-off-by: Yuji Shimada <shimada-yxb@necst.nec.co.jp>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
			
			
This commit is contained in:
		
							parent
							
								
									1c8d7b0a56
								
							
						
					
					
						commit
						32a9a682be
					
				| @ -1760,6 +1760,15 @@ and is between 256 and 4096 characters. It is defined in the file | ||||
| 		cbmemsize=nn[KMG]	The fixed amount of bus space which is | ||||
| 				reserved for the CardBus bridge's memory | ||||
| 				window. The default value is 64 megabytes. | ||||
| 		resource_alignment= | ||||
| 				Format: | ||||
| 				[<order of align>@][<domain>:]<bus>:<slot>.<func>[; ...] | ||||
| 				Specifies alignment and device to reassign | ||||
| 				aligned memory resources. | ||||
| 				If <order of align> is not specified, | ||||
| 				PAGE_SIZE is used as alignment. | ||||
| 				PCI-PCI bridge can be specified, if resource | ||||
| 				windows need to be expanded. | ||||
| 
 | ||||
| 	pcie_aspm=	[PCIE] Forcibly enable or disable PCIe Active State Power | ||||
| 			Management. | ||||
|  | ||||
| @ -20,6 +20,8 @@ | ||||
| #include <linux/pm_wakeup.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <asm/dma.h>	/* isa_dma_bridge_buggy */ | ||||
| #include <linux/device.h> | ||||
| #include <asm/setup.h> | ||||
| #include "pci.h" | ||||
| 
 | ||||
| unsigned int pci_pm_d3_delay = PCI_PM_D3_WAIT; | ||||
| @ -2370,6 +2372,121 @@ int pci_resource_bar(struct pci_dev *dev, int resno, enum pci_bar_type *type) | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #define RESOURCE_ALIGNMENT_PARAM_SIZE COMMAND_LINE_SIZE | ||||
| static char resource_alignment_param[RESOURCE_ALIGNMENT_PARAM_SIZE] = {0}; | ||||
| spinlock_t resource_alignment_lock = SPIN_LOCK_UNLOCKED; | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_specified_resource_alignment - get resource alignment specified by user. | ||||
|  * @dev: the PCI device to get | ||||
|  * | ||||
|  * RETURNS: Resource alignment if it is specified. | ||||
|  *          Zero if it is not specified. | ||||
|  */ | ||||
| resource_size_t pci_specified_resource_alignment(struct pci_dev *dev) | ||||
| { | ||||
| 	int seg, bus, slot, func, align_order, count; | ||||
| 	resource_size_t align = 0; | ||||
| 	char *p; | ||||
| 
 | ||||
| 	spin_lock(&resource_alignment_lock); | ||||
| 	p = resource_alignment_param; | ||||
| 	while (*p) { | ||||
| 		count = 0; | ||||
| 		if (sscanf(p, "%d%n", &align_order, &count) == 1 && | ||||
| 							p[count] == '@') { | ||||
| 			p += count + 1; | ||||
| 		} else { | ||||
| 			align_order = -1; | ||||
| 		} | ||||
| 		if (sscanf(p, "%x:%x:%x.%x%n", | ||||
| 			&seg, &bus, &slot, &func, &count) != 4) { | ||||
| 			seg = 0; | ||||
| 			if (sscanf(p, "%x:%x.%x%n", | ||||
| 					&bus, &slot, &func, &count) != 3) { | ||||
| 				/* Invalid format */ | ||||
| 				printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n", | ||||
| 					p); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		p += count; | ||||
| 		if (seg == pci_domain_nr(dev->bus) && | ||||
| 			bus == dev->bus->number && | ||||
| 			slot == PCI_SLOT(dev->devfn) && | ||||
| 			func == PCI_FUNC(dev->devfn)) { | ||||
| 			if (align_order == -1) { | ||||
| 				align = PAGE_SIZE; | ||||
| 			} else { | ||||
| 				align = 1 << align_order; | ||||
| 			} | ||||
| 			/* Found */ | ||||
| 			break; | ||||
| 		} | ||||
| 		if (*p != ';' && *p != ',') { | ||||
| 			/* End of param or invalid format */ | ||||
| 			break; | ||||
| 		} | ||||
| 		p++; | ||||
| 	} | ||||
| 	spin_unlock(&resource_alignment_lock); | ||||
| 	return align; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_is_reassigndev - check if specified PCI is target device to reassign | ||||
|  * @dev: the PCI device to check | ||||
|  * | ||||
|  * RETURNS: non-zero for PCI device is a target device to reassign, | ||||
|  *          or zero is not. | ||||
|  */ | ||||
| int pci_is_reassigndev(struct pci_dev *dev) | ||||
| { | ||||
| 	return (pci_specified_resource_alignment(dev) != 0); | ||||
| } | ||||
| 
 | ||||
| ssize_t pci_set_resource_alignment_param(const char *buf, size_t count) | ||||
| { | ||||
| 	if (count > RESOURCE_ALIGNMENT_PARAM_SIZE - 1) | ||||
| 		count = RESOURCE_ALIGNMENT_PARAM_SIZE - 1; | ||||
| 	spin_lock(&resource_alignment_lock); | ||||
| 	strncpy(resource_alignment_param, buf, count); | ||||
| 	resource_alignment_param[count] = '\0'; | ||||
| 	spin_unlock(&resource_alignment_lock); | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| ssize_t pci_get_resource_alignment_param(char *buf, size_t size) | ||||
| { | ||||
| 	size_t count; | ||||
| 	spin_lock(&resource_alignment_lock); | ||||
| 	count = snprintf(buf, size, "%s", resource_alignment_param); | ||||
| 	spin_unlock(&resource_alignment_lock); | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| static ssize_t pci_resource_alignment_show(struct bus_type *bus, char *buf) | ||||
| { | ||||
| 	return pci_get_resource_alignment_param(buf, PAGE_SIZE); | ||||
| } | ||||
| 
 | ||||
| static ssize_t pci_resource_alignment_store(struct bus_type *bus, | ||||
| 					const char *buf, size_t count) | ||||
| { | ||||
| 	return pci_set_resource_alignment_param(buf, count); | ||||
| } | ||||
| 
 | ||||
| BUS_ATTR(resource_alignment, 0644, pci_resource_alignment_show, | ||||
| 					pci_resource_alignment_store); | ||||
| 
 | ||||
| static int __init pci_resource_alignment_sysfs_init(void) | ||||
| { | ||||
| 	return bus_create_file(&pci_bus_type, | ||||
| 					&bus_attr_resource_alignment); | ||||
| } | ||||
| 
 | ||||
| late_initcall(pci_resource_alignment_sysfs_init); | ||||
| 
 | ||||
| static void __devinit pci_no_domains(void) | ||||
| { | ||||
| #ifdef CONFIG_PCI_DOMAINS | ||||
| @ -2418,6 +2535,9 @@ static int __init pci_setup(char *str) | ||||
| 				pci_cardbus_io_size = memparse(str + 9, &str); | ||||
| 			} else if (!strncmp(str, "cbmemsize=", 10)) { | ||||
| 				pci_cardbus_mem_size = memparse(str + 10, &str); | ||||
| 			} else if (!strncmp(str, "resource_alignment=", 19)) { | ||||
| 				pci_set_resource_alignment_param(str + 19, | ||||
| 							strlen(str + 19)); | ||||
| 			} else { | ||||
| 				printk(KERN_ERR "PCI: Unknown option `%s'\n", | ||||
| 						str); | ||||
|  | ||||
| @ -195,4 +195,10 @@ static inline int pci_ari_enabled(struct pci_bus *bus) | ||||
| 	return bus->self && bus->self->ari_enabled; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PCI_QUIRKS | ||||
| extern int pci_is_reassigndev(struct pci_dev *dev); | ||||
| resource_size_t pci_specified_resource_alignment(struct pci_dev *dev); | ||||
| extern void pci_disable_bridge_window(struct pci_dev *dev); | ||||
| #endif | ||||
| 
 | ||||
| #endif /* DRIVERS_PCI_H */ | ||||
|  | ||||
| @ -24,6 +24,7 @@ | ||||
| #include <linux/kallsyms.h> | ||||
| #include <linux/dmi.h> | ||||
| #include <linux/pci-aspm.h> | ||||
| #include <linux/ioport.h> | ||||
| #include "pci.h" | ||||
| 
 | ||||
| int isa_dma_bridge_buggy; | ||||
| @ -34,6 +35,65 @@ int pcie_mch_quirk; | ||||
| EXPORT_SYMBOL(pcie_mch_quirk); | ||||
| 
 | ||||
| #ifdef CONFIG_PCI_QUIRKS | ||||
| /*
 | ||||
|  * This quirk function disables the device and releases resources | ||||
|  * which is specified by kernel's boot parameter 'pci=resource_alignment='. | ||||
|  * It also rounds up size to specified alignment. | ||||
|  * Later on, the kernel will assign page-aligned memory resource back | ||||
|  * to that device. | ||||
|  */ | ||||
| static void __devinit quirk_resource_alignment(struct pci_dev *dev) | ||||
| { | ||||
| 	int i; | ||||
| 	struct resource *r; | ||||
| 	resource_size_t align, size; | ||||
| 
 | ||||
| 	if (!pci_is_reassigndev(dev)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (dev->hdr_type == PCI_HEADER_TYPE_NORMAL && | ||||
| 	    (dev->class >> 8) == PCI_CLASS_BRIDGE_HOST) { | ||||
| 		dev_warn(&dev->dev, | ||||
| 			"Can't reassign resources to host bridge.\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dev_info(&dev->dev, "Disabling device and release resources.\n"); | ||||
| 	pci_disable_device(dev); | ||||
| 
 | ||||
| 	align = pci_specified_resource_alignment(dev); | ||||
| 	for (i=0; i < PCI_BRIDGE_RESOURCES; i++) { | ||||
| 		r = &dev->resource[i]; | ||||
| 		if (!(r->flags & IORESOURCE_MEM)) | ||||
| 			continue; | ||||
| 		size = resource_size(r); | ||||
| 		if (size < align) { | ||||
| 			size = align; | ||||
| 			dev_info(&dev->dev, | ||||
| 				"Rounding up size of resource #%d to %#llx.\n", | ||||
| 				i, (unsigned long long)size); | ||||
| 		} | ||||
| 		r->end = size - 1; | ||||
| 		r->start = 0; | ||||
| 	} | ||||
| 	/* Need to disable bridge's resource window,
 | ||||
| 	 * to enable the kernel to reassign new resource | ||||
| 	 * window later on. | ||||
| 	 */ | ||||
| 	if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && | ||||
| 	    (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { | ||||
| 		for (i = PCI_BRIDGE_RESOURCES; i < PCI_NUM_RESOURCES; i++) { | ||||
| 			r = &dev->resource[i]; | ||||
| 			if (!(r->flags & IORESOURCE_MEM)) | ||||
| 				continue; | ||||
| 			r->end = resource_size(r) - 1; | ||||
| 			r->start = 0; | ||||
| 		} | ||||
| 		pci_disable_bridge_window(dev); | ||||
| 	} | ||||
| } | ||||
| DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_resource_alignment); | ||||
| 
 | ||||
| /* The Mellanox Tavor device gives false positive parity errors
 | ||||
|  * Mark this device with a broken_parity_status, to allow | ||||
|  * PCI scanning code to "skip" this now blacklisted device. | ||||
|  | ||||
| @ -120,6 +120,21 @@ int pci_claim_resource(struct pci_dev *dev, int resource) | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PCI_QUIRKS | ||||
| void pci_disable_bridge_window(struct pci_dev *dev) | ||||
| { | ||||
| 	dev_dbg(&dev->dev, "Disabling bridge window.\n"); | ||||
| 
 | ||||
| 	/* MMIO Base/Limit */ | ||||
| 	pci_write_config_dword(dev, PCI_MEMORY_BASE, 0x0000fff0); | ||||
| 
 | ||||
| 	/* Prefetchable MMIO Base/Limit */ | ||||
| 	pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, 0); | ||||
| 	pci_write_config_dword(dev, PCI_PREF_MEMORY_BASE, 0x0000fff0); | ||||
| 	pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0xffffffff); | ||||
| } | ||||
| #endif	/* CONFIG_PCI_QUIRKS */ | ||||
| 
 | ||||
| int pci_assign_resource(struct pci_dev *dev, int resno) | ||||
| { | ||||
| 	struct pci_bus *bus = dev->bus; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user