diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2018-08-15 15:58:58 -0400 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2018-08-15 15:58:58 -0400 |
commit | c689209be23166b340c224df8ecd5deea163da56 (patch) | |
tree | 3e7a5ec634d0db624644a79c08e983e38d3ad736 | |
parent | eadf3d3209435faa500a8e39ff6181c41ecea29d (diff) | |
parent | 10dbc9fedcf151ab794f5e22d4f34f1eff01a08f (diff) |
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
-rw-r--r-- | Documentation/admin-guide/kernel-parameters.txt | 41 | ||||
-rw-r--r-- | drivers/pci/pci.c | 309 | ||||
-rw-r--r-- | drivers/pci/pci.h | 19 | ||||
-rw-r--r-- | drivers/pci/quirks.c | 78 | ||||
-rw-r--r-- | include/linux/pci.h | 11 |
5 files changed, 373 insertions, 85 deletions
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index efc7aa7a0670..d5c27d947c2e 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt | |||
@@ -2994,7 +2994,30 @@ | |||
2994 | See header of drivers/block/paride/pcd.c. | 2994 | See header of drivers/block/paride/pcd.c. |
2995 | See also Documentation/blockdev/paride.txt. | 2995 | See also Documentation/blockdev/paride.txt. |
2996 | 2996 | ||
2997 | pci=option[,option...] [PCI] various PCI subsystem options: | 2997 | pci=option[,option...] [PCI] various PCI subsystem options. |
2998 | |||
2999 | Some options herein operate on a specific device | ||
3000 | or a set of devices (<pci_dev>). These are | ||
3001 | specified in one of the following formats: | ||
3002 | |||
3003 | [<domain>:]<bus>:<dev>.<func>[/<dev>.<func>]* | ||
3004 | pci:<vendor>:<device>[:<subvendor>:<subdevice>] | ||
3005 | |||
3006 | Note: the first format specifies a PCI | ||
3007 | bus/device/function address which may change | ||
3008 | if new hardware is inserted, if motherboard | ||
3009 | firmware changes, or due to changes caused | ||
3010 | by other kernel parameters. If the | ||
3011 | domain is left unspecified, it is | ||
3012 | taken to be zero. Optionally, a path | ||
3013 | to a device through multiple device/function | ||
3014 | addresses can be specified after the base | ||
3015 | address (this is more robust against | ||
3016 | renumbering issues). The second format | ||
3017 | selects devices using IDs from the | ||
3018 | configuration space which may match multiple | ||
3019 | devices in the system. | ||
3020 | |||
2998 | earlydump [X86] dump PCI config space before the kernel | 3021 | earlydump [X86] dump PCI config space before the kernel |
2999 | changes anything | 3022 | changes anything |
3000 | off [X86] don't probe for the PCI bus | 3023 | off [X86] don't probe for the PCI bus |
@@ -3123,11 +3146,10 @@ | |||
3123 | window. The default value is 64 megabytes. | 3146 | window. The default value is 64 megabytes. |
3124 | resource_alignment= | 3147 | resource_alignment= |
3125 | Format: | 3148 | Format: |
3126 | [<order of align>@][<domain>:]<bus>:<slot>.<func>[; ...] | 3149 | [<order of align>@]<pci_dev>[; ...] |
3127 | [<order of align>@]pci:<vendor>:<device>\ | ||
3128 | [:<subvendor>:<subdevice>][; ...] | ||
3129 | Specifies alignment and device to reassign | 3150 | Specifies alignment and device to reassign |
3130 | aligned memory resources. | 3151 | aligned memory resources. How to |
3152 | specify the device is described above. | ||
3131 | If <order of align> is not specified, | 3153 | If <order of align> is not specified, |
3132 | PAGE_SIZE is used as alignment. | 3154 | PAGE_SIZE is used as alignment. |
3133 | PCI-PCI bridge can be specified, if resource | 3155 | PCI-PCI bridge can be specified, if resource |
@@ -3170,6 +3192,15 @@ | |||
3170 | Adding the window is slightly risky (it may | 3192 | Adding the window is slightly risky (it may |
3171 | conflict with unreported devices), so this | 3193 | conflict with unreported devices), so this |
3172 | taints the kernel. | 3194 | taints the kernel. |
3195 | disable_acs_redir=<pci_dev>[; ...] | ||
3196 | Specify one or more PCI devices (in the format | ||
3197 | specified above) separated by semicolons. | ||
3198 | Each device specified will have the PCI ACS | ||
3199 | redirect capabilities forced off which will | ||
3200 | allow P2P traffic between devices through | ||
3201 | bridges without forcing it upstream. Note: | ||
3202 | this removes isolation between devices and | ||
3203 | may put more devices in an IOMMU group. | ||
3173 | 3204 | ||
3174 | pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power | 3205 | pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power |
3175 | Management. | 3206 | Management. |
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 0306fa44653a..f68217abc1ef 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c | |||
@@ -190,6 +190,168 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar) | |||
190 | EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); | 190 | EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); |
191 | #endif | 191 | #endif |
192 | 192 | ||
193 | /** | ||
194 | * pci_dev_str_match_path - test if a path string matches a device | ||
195 | * @dev: the PCI device to test | ||
196 | * @p: string to match the device against | ||
197 | * @endptr: pointer to the string after the match | ||
198 | * | ||
199 | * Test if a string (typically from a kernel parameter) formatted as a | ||
200 | * path of device/function addresses matches a PCI device. The string must | ||
201 | * be of the form: | ||
202 | * | ||
203 | * [<domain>:]<bus>:<device>.<func>[/<device>.<func>]* | ||
204 | * | ||
205 | * A path for a device can be obtained using 'lspci -t'. Using a path | ||
206 | * is more robust against bus renumbering than using only a single bus, | ||
207 | * device and function address. | ||
208 | * | ||
209 | * Returns 1 if the string matches the device, 0 if it does not and | ||
210 | * a negative error code if it fails to parse the string. | ||
211 | */ | ||
212 | static int pci_dev_str_match_path(struct pci_dev *dev, const char *path, | ||
213 | const char **endptr) | ||
214 | { | ||
215 | int ret; | ||
216 | int seg, bus, slot, func; | ||
217 | char *wpath, *p; | ||
218 | char end; | ||
219 | |||
220 | *endptr = strchrnul(path, ';'); | ||
221 | |||
222 | wpath = kmemdup_nul(path, *endptr - path, GFP_KERNEL); | ||
223 | if (!wpath) | ||
224 | return -ENOMEM; | ||
225 | |||
226 | while (1) { | ||
227 | p = strrchr(wpath, '/'); | ||
228 | if (!p) | ||
229 | break; | ||
230 | ret = sscanf(p, "/%x.%x%c", &slot, &func, &end); | ||
231 | if (ret != 2) { | ||
232 | ret = -EINVAL; | ||
233 | goto free_and_exit; | ||
234 | } | ||
235 | |||
236 | if (dev->devfn != PCI_DEVFN(slot, func)) { | ||
237 | ret = 0; | ||
238 | goto free_and_exit; | ||
239 | } | ||
240 | |||
241 | /* | ||
242 | * Note: we don't need to get a reference to the upstream | ||
243 | * bridge because we hold a reference to the top level | ||
244 | * device which should hold a reference to the bridge, | ||
245 | * and so on. | ||
246 | */ | ||
247 | dev = pci_upstream_bridge(dev); | ||
248 | if (!dev) { | ||
249 | ret = 0; | ||
250 | goto free_and_exit; | ||
251 | } | ||
252 | |||
253 | *p = 0; | ||
254 | } | ||
255 | |||
256 | ret = sscanf(wpath, "%x:%x:%x.%x%c", &seg, &bus, &slot, | ||
257 | &func, &end); | ||
258 | if (ret != 4) { | ||
259 | seg = 0; | ||
260 | ret = sscanf(wpath, "%x:%x.%x%c", &bus, &slot, &func, &end); | ||
261 | if (ret != 3) { | ||
262 | ret = -EINVAL; | ||
263 | goto free_and_exit; | ||
264 | } | ||
265 | } | ||
266 | |||
267 | ret = (seg == pci_domain_nr(dev->bus) && | ||
268 | bus == dev->bus->number && | ||
269 | dev->devfn == PCI_DEVFN(slot, func)); | ||
270 | |||
271 | free_and_exit: | ||
272 | kfree(wpath); | ||
273 | return ret; | ||
274 | } | ||
275 | |||
276 | /** | ||
277 | * pci_dev_str_match - test if a string matches a device | ||
278 | * @dev: the PCI device to test | ||
279 | * @p: string to match the device against | ||
280 | * @endptr: pointer to the string after the match | ||
281 | * | ||
282 | * Test if a string (typically from a kernel parameter) matches a specified | ||
283 | * PCI device. The string may be of one of the following formats: | ||
284 | * | ||
285 | * [<domain>:]<bus>:<device>.<func>[/<device>.<func>]* | ||
286 | * pci:<vendor>:<device>[:<subvendor>:<subdevice>] | ||
287 | * | ||
288 | * The first format specifies a PCI bus/device/function address which | ||
289 | * may change if new hardware is inserted, if motherboard firmware changes, | ||
290 | * or due to changes caused in kernel parameters. If the domain is | ||
291 | * left unspecified, it is taken to be 0. In order to be robust against | ||
292 | * bus renumbering issues, a path of PCI device/function numbers may be used | ||
293 | * to address the specific device. The path for a device can be determined | ||
294 | * through the use of 'lspci -t'. | ||
295 | * | ||
296 | * The second format matches devices using IDs in the configuration | ||
297 | * space which may match multiple devices in the system. A value of 0 | ||
298 | * for any field will match all devices. (Note: this differs from | ||
299 | * in-kernel code that uses PCI_ANY_ID which is ~0; this is for | ||
300 | * legacy reasons and convenience so users don't have to specify | ||
301 | * FFFFFFFFs on the command line.) | ||
302 | * | ||
303 | * Returns 1 if the string matches the device, 0 if it does not and | ||
304 | * a negative error code if the string cannot be parsed. | ||
305 | */ | ||
306 | static int pci_dev_str_match(struct pci_dev *dev, const char *p, | ||
307 | const char **endptr) | ||
308 | { | ||
309 | int ret; | ||
310 | int count; | ||
311 | unsigned short vendor, device, subsystem_vendor, subsystem_device; | ||
312 | |||
313 | if (strncmp(p, "pci:", 4) == 0) { | ||
314 | /* PCI vendor/device (subvendor/subdevice) IDs are specified */ | ||
315 | p += 4; | ||
316 | ret = sscanf(p, "%hx:%hx:%hx:%hx%n", &vendor, &device, | ||
317 | &subsystem_vendor, &subsystem_device, &count); | ||
318 | if (ret != 4) { | ||
319 | ret = sscanf(p, "%hx:%hx%n", &vendor, &device, &count); | ||
320 | if (ret != 2) | ||
321 | return -EINVAL; | ||
322 | |||
323 | subsystem_vendor = 0; | ||
324 | subsystem_device = 0; | ||
325 | } | ||
326 | |||
327 | p += count; | ||
328 | |||
329 | if ((!vendor || vendor == dev->vendor) && | ||
330 | (!device || device == dev->device) && | ||
331 | (!subsystem_vendor || | ||
332 | subsystem_vendor == dev->subsystem_vendor) && | ||
333 | (!subsystem_device || | ||
334 | subsystem_device == dev->subsystem_device)) | ||
335 | goto found; | ||
336 | } else { | ||
337 | /* | ||
338 | * PCI Bus, Device, Function IDs are specified | ||
339 | * (optionally, may include a path of devfns following it) | ||
340 | */ | ||
341 | ret = pci_dev_str_match_path(dev, p, &p); | ||
342 | if (ret < 0) | ||
343 | return ret; | ||
344 | else if (ret) | ||
345 | goto found; | ||
346 | } | ||
347 | |||
348 | *endptr = p; | ||
349 | return 0; | ||
350 | |||
351 | found: | ||
352 | *endptr = p; | ||
353 | return 1; | ||
354 | } | ||
193 | 355 | ||
194 | static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, | 356 | static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, |
195 | u8 pos, int cap, int *ttl) | 357 | u8 pos, int cap, int *ttl) |
@@ -2829,6 +2991,66 @@ void pci_request_acs(void) | |||
2829 | pci_acs_enable = 1; | 2991 | pci_acs_enable = 1; |
2830 | } | 2992 | } |
2831 | 2993 | ||
2994 | static const char *disable_acs_redir_param; | ||
2995 | |||
2996 | /** | ||
2997 | * pci_disable_acs_redir - disable ACS redirect capabilities | ||
2998 | * @dev: the PCI device | ||
2999 | * | ||
3000 | * For only devices specified in the disable_acs_redir parameter. | ||
3001 | */ | ||
3002 | static void pci_disable_acs_redir(struct pci_dev *dev) | ||
3003 | { | ||
3004 | int ret = 0; | ||
3005 | const char *p; | ||
3006 | int pos; | ||
3007 | u16 ctrl; | ||
3008 | |||
3009 | if (!disable_acs_redir_param) | ||
3010 | return; | ||
3011 | |||
3012 | p = disable_acs_redir_param; | ||
3013 | while (*p) { | ||
3014 | ret = pci_dev_str_match(dev, p, &p); | ||
3015 | if (ret < 0) { | ||
3016 | pr_info_once("PCI: Can't parse disable_acs_redir parameter: %s\n", | ||
3017 | disable_acs_redir_param); | ||
3018 | |||
3019 | break; | ||
3020 | } else if (ret == 1) { | ||
3021 | /* Found a match */ | ||
3022 | break; | ||
3023 | } | ||
3024 | |||
3025 | if (*p != ';' && *p != ',') { | ||
3026 | /* End of param or invalid format */ | ||
3027 | break; | ||
3028 | } | ||
3029 | p++; | ||
3030 | } | ||
3031 | |||
3032 | if (ret != 1) | ||
3033 | return; | ||
3034 | |||
3035 | if (!pci_dev_specific_disable_acs_redir(dev)) | ||
3036 | return; | ||
3037 | |||
3038 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); | ||
3039 | if (!pos) { | ||
3040 | pci_warn(dev, "cannot disable ACS redirect for this hardware as it does not have ACS capabilities\n"); | ||
3041 | return; | ||
3042 | } | ||
3043 | |||
3044 | pci_read_config_word(dev, pos + PCI_ACS_CTRL, &ctrl); | ||
3045 | |||
3046 | /* P2P Request & Completion Redirect */ | ||
3047 | ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC); | ||
3048 | |||
3049 | pci_write_config_word(dev, pos + PCI_ACS_CTRL, ctrl); | ||
3050 | |||
3051 | pci_info(dev, "disabled ACS redirect\n"); | ||
3052 | } | ||
3053 | |||
2832 | /** | 3054 | /** |
2833 | * pci_std_enable_acs - enable ACS on devices using standard ACS capabilites | 3055 | * pci_std_enable_acs - enable ACS on devices using standard ACS capabilites |
2834 | * @dev: the PCI device | 3056 | * @dev: the PCI device |
@@ -2868,12 +3090,22 @@ static void pci_std_enable_acs(struct pci_dev *dev) | |||
2868 | void pci_enable_acs(struct pci_dev *dev) | 3090 | void pci_enable_acs(struct pci_dev *dev) |
2869 | { | 3091 | { |
2870 | if (!pci_acs_enable) | 3092 | if (!pci_acs_enable) |
2871 | return; | 3093 | goto disable_acs_redir; |
2872 | 3094 | ||
2873 | if (!pci_dev_specific_enable_acs(dev)) | 3095 | if (!pci_dev_specific_enable_acs(dev)) |
2874 | return; | 3096 | goto disable_acs_redir; |
2875 | 3097 | ||
2876 | pci_std_enable_acs(dev); | 3098 | pci_std_enable_acs(dev); |
3099 | |||
3100 | disable_acs_redir: | ||
3101 | /* | ||
3102 | * Note: pci_disable_acs_redir() must be called even if ACS was not | ||
3103 | * enabled by the kernel because it may have been enabled by | ||
3104 | * platform firmware. So if we are told to disable it, we should | ||
3105 | * always disable it after setting the kernel's default | ||
3106 | * preferences. | ||
3107 | */ | ||
3108 | pci_disable_acs_redir(dev); | ||
2877 | } | 3109 | } |
2878 | 3110 | ||
2879 | static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) | 3111 | static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) |
@@ -5514,10 +5746,10 @@ static DEFINE_SPINLOCK(resource_alignment_lock); | |||
5514 | static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, | 5746 | static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, |
5515 | bool *resize) | 5747 | bool *resize) |
5516 | { | 5748 | { |
5517 | int seg, bus, slot, func, align_order, count; | 5749 | int align_order, count; |
5518 | unsigned short vendor, device, subsystem_vendor, subsystem_device; | ||
5519 | resource_size_t align = pcibios_default_alignment(); | 5750 | resource_size_t align = pcibios_default_alignment(); |
5520 | char *p; | 5751 | const char *p; |
5752 | int ret; | ||
5521 | 5753 | ||
5522 | spin_lock(&resource_alignment_lock); | 5754 | spin_lock(&resource_alignment_lock); |
5523 | p = resource_alignment_param; | 5755 | p = resource_alignment_param; |
@@ -5537,58 +5769,21 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, | |||
5537 | } else { | 5769 | } else { |
5538 | align_order = -1; | 5770 | align_order = -1; |
5539 | } | 5771 | } |
5540 | if (strncmp(p, "pci:", 4) == 0) { | 5772 | |
5541 | /* PCI vendor/device (subvendor/subdevice) ids are specified */ | 5773 | ret = pci_dev_str_match(dev, p, &p); |
5542 | p += 4; | 5774 | if (ret == 1) { |
5543 | if (sscanf(p, "%hx:%hx:%hx:%hx%n", | 5775 | *resize = true; |
5544 | &vendor, &device, &subsystem_vendor, &subsystem_device, &count) != 4) { | 5776 | if (align_order == -1) |
5545 | if (sscanf(p, "%hx:%hx%n", &vendor, &device, &count) != 2) { | 5777 | align = PAGE_SIZE; |
5546 | printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: pci:%s\n", | 5778 | else |
5547 | p); | 5779 | align = 1 << align_order; |
5548 | break; | 5780 | break; |
5549 | } | 5781 | } else if (ret < 0) { |
5550 | subsystem_vendor = subsystem_device = 0; | 5782 | pr_err("PCI: Can't parse resource_alignment parameter: %s\n", |
5551 | } | 5783 | p); |
5552 | p += count; | 5784 | break; |
5553 | if ((!vendor || (vendor == dev->vendor)) && | ||
5554 | (!device || (device == dev->device)) && | ||
5555 | (!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) && | ||
5556 | (!subsystem_device || (subsystem_device == dev->subsystem_device))) { | ||
5557 | *resize = true; | ||
5558 | if (align_order == -1) | ||
5559 | align = PAGE_SIZE; | ||
5560 | else | ||
5561 | align = 1 << align_order; | ||
5562 | /* Found */ | ||
5563 | break; | ||
5564 | } | ||
5565 | } | ||
5566 | else { | ||
5567 | if (sscanf(p, "%x:%x:%x.%x%n", | ||
5568 | &seg, &bus, &slot, &func, &count) != 4) { | ||
5569 | seg = 0; | ||
5570 | if (sscanf(p, "%x:%x.%x%n", | ||
5571 | &bus, &slot, &func, &count) != 3) { | ||
5572 | /* Invalid format */ | ||
5573 | printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n", | ||
5574 | p); | ||
5575 | break; | ||
5576 | } | ||
5577 | } | ||
5578 | p += count; | ||
5579 | if (seg == pci_domain_nr(dev->bus) && | ||
5580 | bus == dev->bus->number && | ||
5581 | slot == PCI_SLOT(dev->devfn) && | ||
5582 | func == PCI_FUNC(dev->devfn)) { | ||
5583 | *resize = true; | ||
5584 | if (align_order == -1) | ||
5585 | align = PAGE_SIZE; | ||
5586 | else | ||
5587 | align = 1 << align_order; | ||
5588 | /* Found */ | ||
5589 | break; | ||
5590 | } | ||
5591 | } | 5785 | } |
5786 | |||
5592 | if (*p != ';' && *p != ',') { | 5787 | if (*p != ';' && *p != ',') { |
5593 | /* End of param or invalid format */ | 5788 | /* End of param or invalid format */ |
5594 | break; | 5789 | break; |
@@ -5901,6 +6096,8 @@ static int __init pci_setup(char *str) | |||
5901 | pcie_bus_config = PCIE_BUS_PEER2PEER; | 6096 | pcie_bus_config = PCIE_BUS_PEER2PEER; |
5902 | } else if (!strncmp(str, "pcie_scan_all", 13)) { | 6097 | } else if (!strncmp(str, "pcie_scan_all", 13)) { |
5903 | pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS); | 6098 | pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS); |
6099 | } else if (!strncmp(str, "disable_acs_redir=", 18)) { | ||
6100 | disable_acs_redir_param = str + 18; | ||
5904 | } else { | 6101 | } else { |
5905 | printk(KERN_ERR "PCI: Unknown option `%s'\n", | 6102 | printk(KERN_ERR "PCI: Unknown option `%s'\n", |
5906 | str); | 6103 | str); |
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ccb0537d5d89..8907ceb88233 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h | |||
@@ -400,6 +400,25 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, | |||
400 | } | 400 | } |
401 | 401 | ||
402 | void pci_enable_acs(struct pci_dev *dev); | 402 | void pci_enable_acs(struct pci_dev *dev); |
403 | #ifdef CONFIG_PCI_QUIRKS | ||
404 | int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); | ||
405 | int pci_dev_specific_enable_acs(struct pci_dev *dev); | ||
406 | int pci_dev_specific_disable_acs_redir(struct pci_dev *dev); | ||
407 | #else | ||
408 | static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, | ||
409 | u16 acs_flags) | ||
410 | { | ||
411 | return -ENOTTY; | ||
412 | } | ||
413 | static inline int pci_dev_specific_enable_acs(struct pci_dev *dev) | ||
414 | { | ||
415 | return -ENOTTY; | ||
416 | } | ||
417 | static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) | ||
418 | { | ||
419 | return -ENOTTY; | ||
420 | } | ||
421 | #endif | ||
403 | 422 | ||
404 | /* PCI error reporting and recovery */ | 423 | /* PCI error reporting and recovery */ |
405 | void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service); | 424 | void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service); |
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 2c6d987c271f..eb57d8b610fe 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c | |||
@@ -4555,27 +4555,79 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev) | |||
4555 | return 0; | 4555 | return 0; |
4556 | } | 4556 | } |
4557 | 4557 | ||
4558 | static const struct pci_dev_enable_acs { | 4558 | static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev) |
4559 | { | ||
4560 | int pos; | ||
4561 | u32 cap, ctrl; | ||
4562 | |||
4563 | if (!pci_quirk_intel_spt_pch_acs_match(dev)) | ||
4564 | return -ENOTTY; | ||
4565 | |||
4566 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); | ||
4567 | if (!pos) | ||
4568 | return -ENOTTY; | ||
4569 | |||
4570 | pci_read_config_dword(dev, pos + PCI_ACS_CAP, &cap); | ||
4571 | pci_read_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, &ctrl); | ||
4572 | |||
4573 | ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC); | ||
4574 | |||
4575 | pci_write_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, ctrl); | ||
4576 | |||
4577 | pci_info(dev, "Intel SPT PCH root port workaround: disabled ACS redirect\n"); | ||
4578 | |||
4579 | return 0; | ||
4580 | } | ||
4581 | |||
4582 | static const struct pci_dev_acs_ops { | ||
4559 | u16 vendor; | 4583 | u16 vendor; |
4560 | u16 device; | 4584 | u16 device; |
4561 | int (*enable_acs)(struct pci_dev *dev); | 4585 | int (*enable_acs)(struct pci_dev *dev); |
4562 | } pci_dev_enable_acs[] = { | 4586 | int (*disable_acs_redir)(struct pci_dev *dev); |
4563 | { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_pch_acs }, | 4587 | } pci_dev_acs_ops[] = { |
4564 | { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_spt_pch_acs }, | 4588 | { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, |
4565 | { 0 } | 4589 | .enable_acs = pci_quirk_enable_intel_pch_acs, |
4590 | }, | ||
4591 | { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, | ||
4592 | .enable_acs = pci_quirk_enable_intel_spt_pch_acs, | ||
4593 | .disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir, | ||
4594 | }, | ||
4566 | }; | 4595 | }; |
4567 | 4596 | ||
4568 | int pci_dev_specific_enable_acs(struct pci_dev *dev) | 4597 | int pci_dev_specific_enable_acs(struct pci_dev *dev) |
4569 | { | 4598 | { |
4570 | const struct pci_dev_enable_acs *i; | 4599 | const struct pci_dev_acs_ops *p; |
4571 | int ret; | 4600 | int i, ret; |
4601 | |||
4602 | for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { | ||
4603 | p = &pci_dev_acs_ops[i]; | ||
4604 | if ((p->vendor == dev->vendor || | ||
4605 | p->vendor == (u16)PCI_ANY_ID) && | ||
4606 | (p->device == dev->device || | ||
4607 | p->device == (u16)PCI_ANY_ID) && | ||
4608 | p->enable_acs) { | ||
4609 | ret = p->enable_acs(dev); | ||
4610 | if (ret >= 0) | ||
4611 | return ret; | ||
4612 | } | ||
4613 | } | ||
4572 | 4614 | ||
4573 | for (i = pci_dev_enable_acs; i->enable_acs; i++) { | 4615 | return -ENOTTY; |
4574 | if ((i->vendor == dev->vendor || | 4616 | } |
4575 | i->vendor == (u16)PCI_ANY_ID) && | 4617 | |
4576 | (i->device == dev->device || | 4618 | int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) |
4577 | i->device == (u16)PCI_ANY_ID)) { | 4619 | { |
4578 | ret = i->enable_acs(dev); | 4620 | const struct pci_dev_acs_ops *p; |
4621 | int i, ret; | ||
4622 | |||
4623 | for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { | ||
4624 | p = &pci_dev_acs_ops[i]; | ||
4625 | if ((p->vendor == dev->vendor || | ||
4626 | p->vendor == (u16)PCI_ANY_ID) && | ||
4627 | (p->device == dev->device || | ||
4628 | p->device == (u16)PCI_ANY_ID) && | ||
4629 | p->disable_acs_redir) { | ||
4630 | ret = p->disable_acs_redir(dev); | ||
4579 | if (ret >= 0) | 4631 | if (ret >= 0) |
4580 | return ret; | 4632 | return ret; |
4581 | } | 4633 | } |
diff --git a/include/linux/pci.h b/include/linux/pci.h index b37f2734f9a2..3dd5b871b895 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h | |||
@@ -1890,20 +1890,9 @@ enum pci_fixup_pass { | |||
1890 | 1890 | ||
1891 | #ifdef CONFIG_PCI_QUIRKS | 1891 | #ifdef CONFIG_PCI_QUIRKS |
1892 | void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev); | 1892 | void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev); |
1893 | int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); | ||
1894 | int pci_dev_specific_enable_acs(struct pci_dev *dev); | ||
1895 | #else | 1893 | #else |
1896 | static inline void pci_fixup_device(enum pci_fixup_pass pass, | 1894 | static inline void pci_fixup_device(enum pci_fixup_pass pass, |
1897 | struct pci_dev *dev) { } | 1895 | struct pci_dev *dev) { } |
1898 | static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, | ||
1899 | u16 acs_flags) | ||
1900 | { | ||
1901 | return -ENOTTY; | ||
1902 | } | ||
1903 | static inline int pci_dev_specific_enable_acs(struct pci_dev *dev) | ||
1904 | { | ||
1905 | return -ENOTTY; | ||
1906 | } | ||
1907 | #endif | 1896 | #endif |
1908 | 1897 | ||
1909 | void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen); | 1898 | void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen); |