Commit efcbd9d3 authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

Merge branch 'pci/thunderbolt'

- Detect some Thunderbolt chips that are built-in and hence 'trustworthy'
  by a heuristic since the 'ExternalFacingPort' and 'usb4-host-interface'
  ACPI properties are not quite enough (Esther Shimanovich)

* pci/thunderbolt:
  PCI: Detect and trust built-in Thunderbolt chips
parents c03d361c 3b96b895
Loading
Loading
Loading
Loading
+119 −0
Original line number Diff line number Diff line
@@ -250,6 +250,125 @@ void __init pci_acpi_crs_quirks(void)
		pr_info("Please notify linux-pci@vger.kernel.org so future kernels can do this automatically\n");
}

/*
 * Check if pdev is part of a PCIe switch that is directly below the
 * specified bridge.
 */
static bool pcie_switch_directly_under(struct pci_dev *bridge,
				       struct pci_dev *pdev)
{
	struct pci_dev *parent = pci_upstream_bridge(pdev);

	/* If the device doesn't have a parent, it's not under anything */
	if (!parent)
		return false;

	/*
	 * If the device has a PCIe type, check if it is below the
	 * corresponding PCIe switch components (if applicable). Then check
	 * if its upstream port is directly beneath the specified bridge.
	 */
	switch (pci_pcie_type(pdev)) {
	case PCI_EXP_TYPE_UPSTREAM:
		return parent == bridge;

	case PCI_EXP_TYPE_DOWNSTREAM:
		if (pci_pcie_type(parent) != PCI_EXP_TYPE_UPSTREAM)
			return false;
		parent = pci_upstream_bridge(parent);
		return parent == bridge;

	case PCI_EXP_TYPE_ENDPOINT:
		if (pci_pcie_type(parent) != PCI_EXP_TYPE_DOWNSTREAM)
			return false;
		parent = pci_upstream_bridge(parent);
		if (!parent || pci_pcie_type(parent) != PCI_EXP_TYPE_UPSTREAM)
			return false;
		parent = pci_upstream_bridge(parent);
		return parent == bridge;
	}

	return false;
}

static bool pcie_has_usb4_host_interface(struct pci_dev *pdev)
{
	struct fwnode_handle *fwnode;

	/*
	 * For USB4, the tunneled PCIe Root or Downstream Ports are marked
	 * with the "usb4-host-interface" ACPI property, so we look for
	 * that first. This should cover most cases.
	 */
	fwnode = fwnode_find_reference(dev_fwnode(&pdev->dev),
				       "usb4-host-interface", 0);
	if (!IS_ERR(fwnode)) {
		fwnode_handle_put(fwnode);
		return true;
	}

	/*
	 * Any integrated Thunderbolt 3/4 PCIe Root Ports from Intel
	 * before Alder Lake do not have the "usb4-host-interface"
	 * property so we use their PCI IDs instead. All these are
	 * tunneled. This list is not expected to grow.
	 */
	if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
		switch (pdev->device) {
		/* Ice Lake Thunderbolt 3 PCIe Root Ports */
		case 0x8a1d:
		case 0x8a1f:
		case 0x8a21:
		case 0x8a23:
		/* Tiger Lake-LP Thunderbolt 4 PCIe Root Ports */
		case 0x9a23:
		case 0x9a25:
		case 0x9a27:
		case 0x9a29:
		/* Tiger Lake-H Thunderbolt 4 PCIe Root Ports */
		case 0x9a2b:
		case 0x9a2d:
		case 0x9a2f:
		case 0x9a31:
			return true;
		}
	}

	return false;
}

bool arch_pci_dev_is_removable(struct pci_dev *pdev)
{
	struct pci_dev *parent, *root;

	/* pdev without a parent or Root Port is never tunneled */
	parent = pci_upstream_bridge(pdev);
	if (!parent)
		return false;
	root = pcie_find_root_port(pdev);
	if (!root)
		return false;

	/* Internal PCIe devices are not tunneled */
	if (!root->external_facing)
		return false;

	/* Anything directly behind a "usb4-host-interface" is tunneled */
	if (pcie_has_usb4_host_interface(parent))
		return true;

	/*
	 * Check if this is a discrete Thunderbolt/USB4 controller that is
	 * directly behind the non-USB4 PCIe Root Port marked as
	 * "ExternalFacingPort". Those are not behind a PCIe tunnel.
	 */
	if (pcie_switch_directly_under(root, pdev))
		return false;

	/* PCIe devices after the discrete chip are tunneled */
	return true;
}

#ifdef	CONFIG_PCI_MMCONFIG
static int check_segment(u16 seg, struct device *dev, char *estr)
{
+23 −7
Original line number Diff line number Diff line
@@ -1635,23 +1635,33 @@ static void set_pcie_thunderbolt(struct pci_dev *dev)

static void set_pcie_untrusted(struct pci_dev *dev)
{
	struct pci_dev *parent;
	struct pci_dev *parent = pci_upstream_bridge(dev);

	if (!parent)
		return;
	/*
	 * If the upstream bridge is untrusted we treat this device
	 * If the upstream bridge is untrusted we treat this device as
	 * untrusted as well.
	 */
	parent = pci_upstream_bridge(dev);
	if (parent && (parent->untrusted || parent->external_facing))
	if (parent->untrusted) {
		dev->untrusted = true;
		return;
	}

	if (arch_pci_dev_is_removable(dev)) {
		pci_dbg(dev, "marking as untrusted\n");
		dev->untrusted = true;
	}
}

static void pci_set_removable(struct pci_dev *dev)
{
	struct pci_dev *parent = pci_upstream_bridge(dev);

	if (!parent)
		return;
	/*
	 * We (only) consider everything downstream from an external_facing
	 * We (only) consider everything tunneled below an external_facing
	 * device to be removable by the user. We're mainly concerned with
	 * consumer platforms with user accessible thunderbolt ports that are
	 * vulnerable to DMA attacks, and we expect those ports to be marked by
@@ -1661,10 +1671,16 @@ static void pci_set_removable(struct pci_dev *dev)
	 * accessible to user / may not be removed by end user, and thus not
	 * exposed as "removable" to userspace.
	 */
	if (parent &&
	    (parent->external_facing || dev_is_removable(&parent->dev)))
	if (dev_is_removable(&parent->dev)) {
		dev_set_removable(&dev->dev, DEVICE_REMOVABLE);
		return;
	}

	if (arch_pci_dev_is_removable(dev)) {
		pci_dbg(dev, "marking as removable\n");
		dev_set_removable(&dev->dev, DEVICE_REMOVABLE);
	}
}

/**
 * pci_ext_cfg_is_aliased - Is ext config space just an alias of std config?
+6 −0
Original line number Diff line number Diff line
@@ -2606,6 +2606,12 @@ pci_host_bridge_acpi_msi_domain(struct pci_bus *bus) { return NULL; }
static inline bool pci_pr3_present(struct pci_dev *pdev) { return false; }
#endif

#if defined(CONFIG_X86) && defined(CONFIG_ACPI)
bool arch_pci_dev_is_removable(struct pci_dev *pdev);
#else
static inline bool arch_pci_dev_is_removable(struct pci_dev *pdev) { return false; }
#endif

#ifdef CONFIG_EEH
static inline struct eeh_dev *pci_dev_to_eeh_dev(struct pci_dev *pdev)
{