Commit c689209b authored by Bjorn Helgaas's avatar Bjorn Helgaas

Merge branch 'pci/peer-to-peer'

  - Add "pci=disable_acs_redir=" parameter to disable ACS redirection for
    peer-to-peer DMA support (we don't have the peer-to-peer support yet;
    this is just one piece) (Logan Gunthorpe)

* pci/peer-to-peer:
  PCI: Add ACS Redirect disable quirk for Intel Sunrise Point
  PCI: Add device-specific ACS Redirect disable infrastructure
  PCI: Convert device-specific ACS quirks from NULL termination to ARRAY_SIZE
  PCI: Add "pci=disable_acs_redir=" parameter for peer-to-peer support
  PCI: Allow specifying devices using a base bus and path of devfns
  PCI: Make specifying PCI devices in kernel parameters reusable
  PCI: Hide ACS quirk declarations inside PCI core
parents eadf3d32 10dbc9fe
...@@ -2994,7 +2994,30 @@ ...@@ -2994,7 +2994,30 @@
See header of drivers/block/paride/pcd.c. See header of drivers/block/paride/pcd.c.
See also Documentation/blockdev/paride.txt. See also Documentation/blockdev/paride.txt.
pci=option[,option...] [PCI] various PCI subsystem options: pci=option[,option...] [PCI] various PCI subsystem options.
Some options herein operate on a specific device
or a set of devices (<pci_dev>). These are
specified in one of the following formats:
[<domain>:]<bus>:<dev>.<func>[/<dev>.<func>]*
pci:<vendor>:<device>[:<subvendor>:<subdevice>]
Note: the first format specifies a PCI
bus/device/function address which may change
if new hardware is inserted, if motherboard
firmware changes, or due to changes caused
by other kernel parameters. If the
domain is left unspecified, it is
taken to be zero. Optionally, a path
to a device through multiple device/function
addresses can be specified after the base
address (this is more robust against
renumbering issues). The second format
selects devices using IDs from the
configuration space which may match multiple
devices in the system.
earlydump [X86] dump PCI config space before the kernel earlydump [X86] dump PCI config space before the kernel
changes anything changes anything
off [X86] don't probe for the PCI bus off [X86] don't probe for the PCI bus
...@@ -3123,11 +3146,10 @@ ...@@ -3123,11 +3146,10 @@
window. The default value is 64 megabytes. window. The default value is 64 megabytes.
resource_alignment= resource_alignment=
Format: Format:
[<order of align>@][<domain>:]<bus>:<slot>.<func>[; ...] [<order of align>@]<pci_dev>[; ...]
[<order of align>@]pci:<vendor>:<device>\
[:<subvendor>:<subdevice>][; ...]
Specifies alignment and device to reassign Specifies alignment and device to reassign
aligned memory resources. aligned memory resources. How to
specify the device is described above.
If <order of align> is not specified, If <order of align> is not specified,
PAGE_SIZE is used as alignment. PAGE_SIZE is used as alignment.
PCI-PCI bridge can be specified, if resource PCI-PCI bridge can be specified, if resource
...@@ -3170,6 +3192,15 @@ ...@@ -3170,6 +3192,15 @@
Adding the window is slightly risky (it may Adding the window is slightly risky (it may
conflict with unreported devices), so this conflict with unreported devices), so this
taints the kernel. taints the kernel.
disable_acs_redir=<pci_dev>[; ...]
Specify one or more PCI devices (in the format
specified above) separated by semicolons.
Each device specified will have the PCI ACS
redirect capabilities forced off which will
allow P2P traffic between devices through
bridges without forcing it upstream. Note:
this removes isolation between devices and
may put more devices in an IOMMU group.
pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power
Management. Management.
......
...@@ -190,6 +190,168 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar) ...@@ -190,6 +190,168 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar)
EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar);
#endif #endif
/**
* pci_dev_str_match_path - test if a path string matches a device
* @dev: the PCI device to test
* @p: string to match the device against
* @endptr: pointer to the string after the match
*
* Test if a string (typically from a kernel parameter) formatted as a
* path of device/function addresses matches a PCI device. The string must
* be of the form:
*
* [<domain>:]<bus>:<device>.<func>[/<device>.<func>]*
*
* A path for a device can be obtained using 'lspci -t'. Using a path
* is more robust against bus renumbering than using only a single bus,
* device and function address.
*
* Returns 1 if the string matches the device, 0 if it does not and
* a negative error code if it fails to parse the string.
*/
static int pci_dev_str_match_path(struct pci_dev *dev, const char *path,
const char **endptr)
{
int ret;
int seg, bus, slot, func;
char *wpath, *p;
char end;
*endptr = strchrnul(path, ';');
wpath = kmemdup_nul(path, *endptr - path, GFP_KERNEL);
if (!wpath)
return -ENOMEM;
while (1) {
p = strrchr(wpath, '/');
if (!p)
break;
ret = sscanf(p, "/%x.%x%c", &slot, &func, &end);
if (ret != 2) {
ret = -EINVAL;
goto free_and_exit;
}
if (dev->devfn != PCI_DEVFN(slot, func)) {
ret = 0;
goto free_and_exit;
}
/*
* Note: we don't need to get a reference to the upstream
* bridge because we hold a reference to the top level
* device which should hold a reference to the bridge,
* and so on.
*/
dev = pci_upstream_bridge(dev);
if (!dev) {
ret = 0;
goto free_and_exit;
}
*p = 0;
}
ret = sscanf(wpath, "%x:%x:%x.%x%c", &seg, &bus, &slot,
&func, &end);
if (ret != 4) {
seg = 0;
ret = sscanf(wpath, "%x:%x.%x%c", &bus, &slot, &func, &end);
if (ret != 3) {
ret = -EINVAL;
goto free_and_exit;
}
}
ret = (seg == pci_domain_nr(dev->bus) &&
bus == dev->bus->number &&
dev->devfn == PCI_DEVFN(slot, func));
free_and_exit:
kfree(wpath);
return ret;
}
/**
* pci_dev_str_match - test if a string matches a device
* @dev: the PCI device to test
* @p: string to match the device against
* @endptr: pointer to the string after the match
*
* Test if a string (typically from a kernel parameter) matches a specified
* PCI device. The string may be of one of the following formats:
*
* [<domain>:]<bus>:<device>.<func>[/<device>.<func>]*
* pci:<vendor>:<device>[:<subvendor>:<subdevice>]
*
* The first format specifies a PCI bus/device/function address which
* may change if new hardware is inserted, if motherboard firmware changes,
* or due to changes caused in kernel parameters. If the domain is
* left unspecified, it is taken to be 0. In order to be robust against
* bus renumbering issues, a path of PCI device/function numbers may be used
* to address the specific device. The path for a device can be determined
* through the use of 'lspci -t'.
*
* The second format matches devices using IDs in the configuration
* space which may match multiple devices in the system. A value of 0
* for any field will match all devices. (Note: this differs from
* in-kernel code that uses PCI_ANY_ID which is ~0; this is for
* legacy reasons and convenience so users don't have to specify
* FFFFFFFFs on the command line.)
*
* Returns 1 if the string matches the device, 0 if it does not and
* a negative error code if the string cannot be parsed.
*/
static int pci_dev_str_match(struct pci_dev *dev, const char *p,
const char **endptr)
{
int ret;
int count;
unsigned short vendor, device, subsystem_vendor, subsystem_device;
if (strncmp(p, "pci:", 4) == 0) {
/* PCI vendor/device (subvendor/subdevice) IDs are specified */
p += 4;
ret = sscanf(p, "%hx:%hx:%hx:%hx%n", &vendor, &device,
&subsystem_vendor, &subsystem_device, &count);
if (ret != 4) {
ret = sscanf(p, "%hx:%hx%n", &vendor, &device, &count);
if (ret != 2)
return -EINVAL;
subsystem_vendor = 0;
subsystem_device = 0;
}
p += count;
if ((!vendor || vendor == dev->vendor) &&
(!device || device == dev->device) &&
(!subsystem_vendor ||
subsystem_vendor == dev->subsystem_vendor) &&
(!subsystem_device ||
subsystem_device == dev->subsystem_device))
goto found;
} else {
/*
* PCI Bus, Device, Function IDs are specified
* (optionally, may include a path of devfns following it)
*/
ret = pci_dev_str_match_path(dev, p, &p);
if (ret < 0)
return ret;
else if (ret)
goto found;
}
*endptr = p;
return 0;
found:
*endptr = p;
return 1;
}
static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn,
u8 pos, int cap, int *ttl) u8 pos, int cap, int *ttl)
...@@ -2829,6 +2991,66 @@ void pci_request_acs(void) ...@@ -2829,6 +2991,66 @@ void pci_request_acs(void)
pci_acs_enable = 1; pci_acs_enable = 1;
} }
static const char *disable_acs_redir_param;
/**
* pci_disable_acs_redir - disable ACS redirect capabilities
* @dev: the PCI device
*
* For only devices specified in the disable_acs_redir parameter.
*/
static void pci_disable_acs_redir(struct pci_dev *dev)
{
int ret = 0;
const char *p;
int pos;
u16 ctrl;
if (!disable_acs_redir_param)
return;
p = disable_acs_redir_param;
while (*p) {
ret = pci_dev_str_match(dev, p, &p);
if (ret < 0) {
pr_info_once("PCI: Can't parse disable_acs_redir parameter: %s\n",
disable_acs_redir_param);
break;
} else if (ret == 1) {
/* Found a match */
break;
}
if (*p != ';' && *p != ',') {
/* End of param or invalid format */
break;
}
p++;
}
if (ret != 1)
return;
if (!pci_dev_specific_disable_acs_redir(dev))
return;
pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS);
if (!pos) {
pci_warn(dev, "cannot disable ACS redirect for this hardware as it does not have ACS capabilities\n");
return;
}
pci_read_config_word(dev, pos + PCI_ACS_CTRL, &ctrl);
/* P2P Request & Completion Redirect */
ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC);
pci_write_config_word(dev, pos + PCI_ACS_CTRL, ctrl);
pci_info(dev, "disabled ACS redirect\n");
}
/** /**
* pci_std_enable_acs - enable ACS on devices using standard ACS capabilites * pci_std_enable_acs - enable ACS on devices using standard ACS capabilites
* @dev: the PCI device * @dev: the PCI device
...@@ -2868,12 +3090,22 @@ static void pci_std_enable_acs(struct pci_dev *dev) ...@@ -2868,12 +3090,22 @@ static void pci_std_enable_acs(struct pci_dev *dev)
void pci_enable_acs(struct pci_dev *dev) void pci_enable_acs(struct pci_dev *dev)
{ {
if (!pci_acs_enable) if (!pci_acs_enable)
return; goto disable_acs_redir;
if (!pci_dev_specific_enable_acs(dev)) if (!pci_dev_specific_enable_acs(dev))
return; goto disable_acs_redir;
pci_std_enable_acs(dev); pci_std_enable_acs(dev);
disable_acs_redir:
/*
* Note: pci_disable_acs_redir() must be called even if ACS was not
* enabled by the kernel because it may have been enabled by
* platform firmware. So if we are told to disable it, we should
* always disable it after setting the kernel's default
* preferences.
*/
pci_disable_acs_redir(dev);
} }
static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
...@@ -5514,10 +5746,10 @@ static DEFINE_SPINLOCK(resource_alignment_lock); ...@@ -5514,10 +5746,10 @@ static DEFINE_SPINLOCK(resource_alignment_lock);
static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev,
bool *resize) bool *resize)
{ {
int seg, bus, slot, func, align_order, count; int align_order, count;
unsigned short vendor, device, subsystem_vendor, subsystem_device;
resource_size_t align = pcibios_default_alignment(); resource_size_t align = pcibios_default_alignment();
char *p; const char *p;
int ret;
spin_lock(&resource_alignment_lock); spin_lock(&resource_alignment_lock);
p = resource_alignment_param; p = resource_alignment_param;
...@@ -5537,58 +5769,21 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, ...@@ -5537,58 +5769,21 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev,
} else { } else {
align_order = -1; align_order = -1;
} }
if (strncmp(p, "pci:", 4) == 0) {
/* PCI vendor/device (subvendor/subdevice) ids are specified */ ret = pci_dev_str_match(dev, p, &p);
p += 4; if (ret == 1) {
if (sscanf(p, "%hx:%hx:%hx:%hx%n",
&vendor, &device, &subsystem_vendor, &subsystem_device, &count) != 4) {
if (sscanf(p, "%hx:%hx%n", &vendor, &device, &count) != 2) {
printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: pci:%s\n",
p);
break;
}
subsystem_vendor = subsystem_device = 0;
}
p += count;
if ((!vendor || (vendor == dev->vendor)) &&
(!device || (device == dev->device)) &&
(!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) &&
(!subsystem_device || (subsystem_device == dev->subsystem_device))) {
*resize = true; *resize = true;
if (align_order == -1) if (align_order == -1)
align = PAGE_SIZE; align = PAGE_SIZE;
else else
align = 1 << align_order; align = 1 << align_order;
/* Found */
break; break;
} } else if (ret < 0) {
} pr_err("PCI: Can't parse resource_alignment parameter: %s\n",
else {
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); p);
break; break;
} }
}
p += count;
if (seg == pci_domain_nr(dev->bus) &&
bus == dev->bus->number &&
slot == PCI_SLOT(dev->devfn) &&
func == PCI_FUNC(dev->devfn)) {
*resize = true;
if (align_order == -1)
align = PAGE_SIZE;
else
align = 1 << align_order;
/* Found */
break;
}
}
if (*p != ';' && *p != ',') { if (*p != ';' && *p != ',') {
/* End of param or invalid format */ /* End of param or invalid format */
break; break;
...@@ -5901,6 +6096,8 @@ static int __init pci_setup(char *str) ...@@ -5901,6 +6096,8 @@ static int __init pci_setup(char *str)
pcie_bus_config = PCIE_BUS_PEER2PEER; pcie_bus_config = PCIE_BUS_PEER2PEER;
} else if (!strncmp(str, "pcie_scan_all", 13)) { } else if (!strncmp(str, "pcie_scan_all", 13)) {
pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS); pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS);
} else if (!strncmp(str, "disable_acs_redir=", 18)) {
disable_acs_redir_param = str + 18;
} else { } else {
printk(KERN_ERR "PCI: Unknown option `%s'\n", printk(KERN_ERR "PCI: Unknown option `%s'\n",
str); str);
......
...@@ -400,6 +400,25 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, ...@@ -400,6 +400,25 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev,
} }
void pci_enable_acs(struct pci_dev *dev); void pci_enable_acs(struct pci_dev *dev);
#ifdef CONFIG_PCI_QUIRKS
int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);
int pci_dev_specific_enable_acs(struct pci_dev *dev);
int pci_dev_specific_disable_acs_redir(struct pci_dev *dev);
#else
static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev,
u16 acs_flags)
{
return -ENOTTY;
}
static inline int pci_dev_specific_enable_acs(struct pci_dev *dev)
{
return -ENOTTY;
}
static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
{
return -ENOTTY;
}
#endif
/* PCI error reporting and recovery */ /* PCI error reporting and recovery */
void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service); void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service);
......
...@@ -4555,27 +4555,79 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev) ...@@ -4555,27 +4555,79 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev)
return 0; return 0;
} }
static const struct pci_dev_enable_acs { static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)
{
int pos;
u32 cap, ctrl;
if (!pci_quirk_intel_spt_pch_acs_match(dev))
return -ENOTTY;
pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS);
if (!pos)
return -ENOTTY;
pci_read_config_dword(dev, pos + PCI_ACS_CAP, &cap);
pci_read_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, &ctrl);
ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC);
pci_write_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, ctrl);
pci_info(dev, "Intel SPT PCH root port workaround: disabled ACS redirect\n");
return 0;
}
static const struct pci_dev_acs_ops {
u16 vendor; u16 vendor;
u16 device; u16 device;
int (*enable_acs)(struct pci_dev *dev); int (*enable_acs)(struct pci_dev *dev);
} pci_dev_enable_acs[] = { int (*disable_acs_redir)(struct pci_dev *dev);
{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_pch_acs }, } pci_dev_acs_ops[] = {
{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_spt_pch_acs }, { PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
{ 0 } .enable_acs = pci_quirk_enable_intel_pch_acs,
},
{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
.enable_acs = pci_quirk_enable_intel_spt_pch_acs,
.disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir,
},
}; };
int pci_dev_specific_enable_acs(struct pci_dev *dev) int pci_dev_specific_enable_acs(struct pci_dev *dev)
{ {
const struct pci_dev_enable_acs *i; const struct pci_dev_acs_ops *p;
int ret; int i, ret;
for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
p = &pci_dev_acs_ops[i];
if ((p->vendor == dev->vendor ||
p->vendor == (u16)PCI_ANY_ID) &&
(p->device == dev->device ||
p->device == (u16)PCI_ANY_ID) &&
p->enable_acs) {
ret = p->enable_acs(dev);
if (ret >= 0)
return ret;
}
}
for (i = pci_dev_enable_acs; i->enable_acs; i++) { return -ENOTTY;
if ((i->vendor == dev->vendor || }
i->vendor == (u16)PCI_ANY_ID) &&
(i->device == dev->device || int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
i->device == (u16)PCI_ANY_ID)) { {
ret = i->enable_acs(dev); const struct pci_dev_acs_ops *p;
int i, ret;
for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
p = &pci_dev_acs_ops[i];
if ((p->vendor == dev->vendor ||
p->vendor == (u16)PCI_ANY_ID) &&
(p->device == dev->device ||
p->device == (u16)PCI_ANY_ID) &&
p->disable_acs_redir) {
ret = p->disable_acs_redir(dev);
if (ret >= 0) if (ret >= 0)
return ret; return ret;
} }
......
...@@ -1890,20 +1890,9 @@ enum pci_fixup_pass { ...@@ -1890,20 +1890,9 @@ enum pci_fixup_pass {
#ifdef CONFIG_PCI_QUIRKS #ifdef CONFIG_PCI_QUIRKS
void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev); void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev);
int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);
int pci_dev_specific_enable_acs(struct pci_dev *dev);
#else #else
static inline void pci_fixup_device(enum pci_fixup_pass pass, static inline void pci_fixup_device(enum pci_fixup_pass pass,
struct pci_dev *dev) { } struct pci_dev *dev) { }
static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev,
u16 acs_flags)
{
return -ENOTTY;
}
static inline int pci_dev_specific_enable_acs(struct pci_dev *dev)
{
return -ENOTTY;
}
#endif #endif
void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen); void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen);
......
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