Introduce a new ACS related quirk that indicates a single function in a MFD is isolated from other functions in the MFD. In otherwords the function has no internal loopback to other functions. This is different from the existing ACS quirk since ACS only indicates if the function's egress can reach other functions in the MFD for P2P, while leaving the ingress question up to the ACS of the other functions. pci_mfd_isolation() reports both the ingress and egress behavior together, if both are blocked then it can return true. This quirk matches the historical behavior of the pci_dev_specific_acs_enabled() quirk that would allow 'adding' ACS to a single function to force that function into its own iommu_group. Many quirks were added on this assumption to give specific functions their own IOMMU groups. For example some Intel systems have a NIC in a MFD with other functions and no ACS capabilities. The NIC has been quirked, while the rest of the MFD functions are unquirked. Add an internal flag, PCI_ACS_QUIRK_ACS_ISOLATED, in the upper 16 bits of the acs_flags. If the quirk implementation sees the flag and decides the function should be isolated, then it can return success. The additional flag automatically makes the existing quirks all return failure as it is never masked off. Have pci_quirk_mf_endpoint_acs() support the flag with some expectation this will grown down the road. Apply the MFD isolation override to a general case of host bridges inside MFDs that match some AMD systems: 00:07.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx- Status: Cap- 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx- NUMA node: 3 IOMMU group: 52 00:07.1 PCI bridge: Advanced Micro Devices, Inc. [AMD] Starship/Matisse Internal PCIe GPP Bridge 0 to bus[E:B] (prog-if 00 [Normal decode]) [..] Bus: primary=00, secondary=01, subordinate=01, sec-latency=0 [..] Capabilities: [2a0 v1] Access Control Services The logic being that a host bridge with no mmio has no downstream devices and cannot source or sink any P2P MMIO operations, thus it does not contribute to the ACS isolation calculation for the MFD. Signed-off-by: Jason Gunthorpe <jgg@xxxxxxxxxx> --- drivers/pci/pci.h | 5 ++++ drivers/pci/quirks.c | 58 +++++++++++++++++++++++++++++++++++--------- drivers/pci/search.c | 30 +++++++++++++++++++++++ include/linux/pci.h | 4 +++ 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 12215ee72afb68..78651025096bcd 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -786,6 +786,7 @@ 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); int pcie_failed_link_retrain(struct pci_dev *dev); +bool pci_dev_specific_mfd_isolated(struct pci_dev *dev); #else static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags) @@ -804,6 +805,10 @@ static inline int pcie_failed_link_retrain(struct pci_dev *dev) { return -ENOTTY; } +static inline bool pci_dev_specific_mfd_isolated(struct pci_dev *dev) +{ + return false; +} #endif /* PCI error reporting and recovery */ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 71b9550e46eb06..85c786d66646a8 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4616,6 +4616,8 @@ static void quirk_chelsio_T5_disable_root_port_attributes(struct pci_dev *pdev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, PCI_ANY_ID, quirk_chelsio_T5_disable_root_port_attributes); +#define PCI_ACS_QUIRK_ACS_ISOLATED BIT(16) + /* * pci_acs_ctrl_enabled - compare desired ACS controls with those provided * by a device @@ -4948,6 +4950,13 @@ static int pci_quirk_intel_spt_pch_acs(struct pci_dev *dev, u32 acs_flags) static int pci_quirk_mf_endpoint_acs(struct pci_dev *dev, u32 acs_flags) { + /* + * The function cannot get P2P MMIO from the other functions in the MFD + * either even if the other functions do not have ACS or ACS quirks. + */ + if (acs_flags & PCI_ACS_QUIRK_ACS_ISOLATED) + return 1; + /* * SV, TB, and UF are not relevant to multifunction endpoints. * @@ -5186,18 +5195,7 @@ static const struct pci_dev_acs_enabled { { 0 } }; -/* - * pci_dev_specific_acs_enabled - check whether device provides ACS controls - * @dev: PCI device - * @acs_flags: Bitmask of desired ACS controls - * - * Returns: - * -ENOTTY: No quirk applies to this device; we can't tell whether the - * device provides the desired controls - * 0: Device does not provide all the desired controls - * >0: Device provides all the controls in @acs_flags - */ -int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags) +static int pci_dev_call_acs_enabled(struct pci_dev *dev, u32 acs_flags) { const struct pci_dev_acs_enabled *i; int ret; @@ -5222,6 +5220,42 @@ int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags) return -ENOTTY; } +/* + * pci_dev_specific_acs_enabled - check whether device provides ACS controls + * @dev: PCI device + * @acs_flags: Bitmask of desired ACS controls + * + * Returns: + * -ENOTTY: No quirk applies to this device; we can't tell whether the + * device provides the desired controls + * 0: Device does not provide all the desired controls + * >0: Device provides all the controls in @acs_flags + */ +int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags) +{ + return pci_dev_call_acs_enabled(dev, acs_flags); +} + +/* + * pci_dev_specific_mfd_isolated- check whether a MFD function is isolated + * @dev: PCI device + * + * pci_dev_specific_acs_enabled() emulates the ACS flags using a quirk however + * historically Linux has not quirked every function in a MFD. + * pci_dev_specific_mfd_isolated() overrides the other function MFD checks and + * can consider a single function fully isolated from all other functions both + * for egress and ingress directions. + * + * Returns: + * false: No override, use normal PCI defined mechanisms + * true: Function is isolated from P2P to other functions in the device + */ +bool pci_dev_specific_mfd_isolated(struct pci_dev *dev) +{ + return pci_dev_call_acs_enabled(dev, PCI_ACS_QUIRK_ACS_ISOLATED | + PCI_ACS_ISOLATED) > 0; +} + /* Config space offset of Root Complex Base Address register */ #define INTEL_LPC_RCBA_REG 0xf0 /* 31:14 RCBA address */ diff --git a/drivers/pci/search.c b/drivers/pci/search.c index dc816dc4505c6d..2be6881087b335 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -277,6 +277,36 @@ enum pci_bus_isolation pci_bus_isolated(struct pci_bus *bus) } } +/* + * pci_mfd_isolated- check whether a MFD function is isolated + * @dev: PCI device + * + * True if the dev function on a MFD should be considered isolated from all + * other functions in the MFD. This is used to override ACS checks that might + * otherwise indicate the MFD function participates in an internal loopback. + * + * Returns: + * false: No override, use normal PCI defined mechanisms + * true: Function is isolated from P2P to other functions in the device + */ +bool pci_mfd_isolated(struct pci_dev *dev) +{ + /* + * For some reason AMD likes to put "dummy functions" in their PCI + * hierarchy as part of a multi function device. These are notable + * because they can't do anything. No BARs and no downstream bus. Since + * they cannot accept P2P or initiate any MMIO we consider them to be + * isolated from the rest of MFD. Since they often accompany a real PCI + * bridge with downstream devices it is important that the MFD be + * considered isolated. Annoyingly there is no ACS capability reported + * so we assume that a host bridge in a MFD with no MMIO has the special + * property of never accepting or initiating P2P operations. + */ + if (dev->class >> 8 == PCI_CLASS_BRIDGE_HOST && !pci_has_mmio(dev)) + return true; + return pci_dev_specific_mfd_isolated(dev); +} + static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr) { struct pci_bus *child; diff --git a/include/linux/pci.h b/include/linux/pci.h index 2e629087539101..d95a983c835666 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1256,6 +1256,7 @@ void pci_reachable_set(struct pci_dev *start, struct pci_reachable_set *devfns, bool (*reachable)(struct pci_dev *deva, struct pci_dev *devb)); enum pci_bus_isolation pci_bus_isolated(struct pci_bus *bus); +bool pci_mfd_isolated(struct pci_dev *dev); int pci_dev_present(const struct pci_device_id *ids); @@ -2078,6 +2079,9 @@ pci_reachable_set(struct pci_dev *start, struct pci_reachable_set *devfns, static inline enum pci_bus_isolation pci_bus_isolated(struct pci_bus *bus) { return PCIE_NON_ISOLATED; } +bool pci_mfd_isolated(struct pci_dev *dev) +{ return false; } + static inline int pci_dev_present(const struct pci_device_id *ids) { return 0; } -- 2.43.0