Commit 32a9a682 authored by Yuji Shimada's avatar Yuji Shimada Committed by Jesse Barnes

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: default avatarAlex Chiang <achiang@hp.com>
Reviewed-by: default avatarYu Zhao <yu.zhao@intel.com>
Signed-off-by: default avatarYuji Shimada <shimada-yxb@necst.nec.co.jp>
Signed-off-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
parent 1c8d7b0a
...@@ -1760,6 +1760,15 @@ and is between 256 and 4096 characters. It is defined in the file ...@@ -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 cbmemsize=nn[KMG] The fixed amount of bus space which is
reserved for the CardBus bridge's memory reserved for the CardBus bridge's memory
window. The default value is 64 megabytes. 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 pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power
Management. Management.
......
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include <linux/pm_wakeup.h> #include <linux/pm_wakeup.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <asm/dma.h> /* isa_dma_bridge_buggy */ #include <asm/dma.h> /* isa_dma_bridge_buggy */
#include <linux/device.h>
#include <asm/setup.h>
#include "pci.h" #include "pci.h"
unsigned int pci_pm_d3_delay = PCI_PM_D3_WAIT; 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) ...@@ -2370,6 +2372,121 @@ int pci_resource_bar(struct pci_dev *dev, int resno, enum pci_bar_type *type)
return 0; 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) static void __devinit pci_no_domains(void)
{ {
#ifdef CONFIG_PCI_DOMAINS #ifdef CONFIG_PCI_DOMAINS
...@@ -2418,6 +2535,9 @@ static int __init pci_setup(char *str) ...@@ -2418,6 +2535,9 @@ static int __init pci_setup(char *str)
pci_cardbus_io_size = memparse(str + 9, &str); pci_cardbus_io_size = memparse(str + 9, &str);
} else if (!strncmp(str, "cbmemsize=", 10)) { } else if (!strncmp(str, "cbmemsize=", 10)) {
pci_cardbus_mem_size = memparse(str + 10, &str); 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 { } else {
printk(KERN_ERR "PCI: Unknown option `%s'\n", printk(KERN_ERR "PCI: Unknown option `%s'\n",
str); str);
......
...@@ -195,4 +195,10 @@ static inline int pci_ari_enabled(struct pci_bus *bus) ...@@ -195,4 +195,10 @@ static inline int pci_ari_enabled(struct pci_bus *bus)
return bus->self && bus->self->ari_enabled; 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 */ #endif /* DRIVERS_PCI_H */
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/kallsyms.h> #include <linux/kallsyms.h>
#include <linux/dmi.h> #include <linux/dmi.h>
#include <linux/pci-aspm.h> #include <linux/pci-aspm.h>
#include <linux/ioport.h>
#include "pci.h" #include "pci.h"
int isa_dma_bridge_buggy; int isa_dma_bridge_buggy;
...@@ -34,6 +35,65 @@ int pcie_mch_quirk; ...@@ -34,6 +35,65 @@ int pcie_mch_quirk;
EXPORT_SYMBOL(pcie_mch_quirk); EXPORT_SYMBOL(pcie_mch_quirk);
#ifdef CONFIG_PCI_QUIRKS #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 /* The Mellanox Tavor device gives false positive parity errors
* Mark this device with a broken_parity_status, to allow * Mark this device with a broken_parity_status, to allow
* PCI scanning code to "skip" this now blacklisted device. * PCI scanning code to "skip" this now blacklisted device.
......
...@@ -120,6 +120,21 @@ int pci_claim_resource(struct pci_dev *dev, int resource) ...@@ -120,6 +120,21 @@ int pci_claim_resource(struct pci_dev *dev, int resource)
return err; 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) int pci_assign_resource(struct pci_dev *dev, int resno)
{ {
struct pci_bus *bus = dev->bus; struct pci_bus *bus = dev->bus;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment