[PATCH V1 2/3] ACPI/IORT: Plumb Root Complex PASID descriptors into iommu_fwspec

Vidya Sagar
2026-04-23T19:15:26Z

The IORT spec, Issue E.c (ARM DEN 0049E.c, January 2022), gives
software two ways to learn about Root Complex PASID capability:

- the Flags field at byte offset 36 (RC node revision >= 4), bit 0
of which says whether the Root Complex itself supports PASID, and
- bit 2 of the long-standing ATS Attribute field at offset 24, which
says whether the Root Complex forwards PASID information on
translated transactions to the SMMU.

Neither is consumed by Linux today. Modeled after the CANWBS support
in commit 807404d66fcf ("ACPI/IORT: Support CANWBS memory access
flag"), expose both bits via the existing iommu_fwspec mechanism so
IOMMU drivers can consult them at device probe time:

- IOMMU_FWSPEC_PCI_RC_PASID (bit 2): RC declares PASID support
- IOMMU_FWSPEC_PCI_RC_PASID_FWD (bit 3): RC forwards PASID

Add three new static helpers in iort.c that read the corresponding
fields, with iort_pci_rc_supports_pasid() and the new
iort_pci_rc_pasid_max_width() helper guarded by node->revision >= 4
because their backing storage was only added in E.c. The PASID
forwarding bit lives in the older ats_attribute field and needs no
guard. Set the new fwspec flags from iort_iommu_configure_id().

In addition, expose two device-facing wrappers,
iort_pci_rc_pasid_max_width_known() and _for_dev(), so IOMMU drivers
can clamp endpoint PASID widths by what the Root Complex can actually
carry without having to walk the IORT themselves. Provide stubs in
include/linux/acpi_iort.h for !CONFIG_ACPI_IORT.

Signed-off-by: Vidya Sagar <vidyas@nvidia.com>
---
drivers/acpi/arm64/iort.c | 80 +++++++++++++++++++++++++++++++++++++++
include/linux/acpi_iort.h | 6 +++
include/linux/iommu.h | 4 ++
3 files changed, 90 insertions(+)

diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c
index af7a9b2fd5bc..40486de6bd79 100644
--- a/drivers/acpi/arm64/iort.c
+++ b/drivers/acpi/arm64/iort.c
@@ -1352,6 +1352,36 @@ static bool iort_pci_rc_supports_canwbs(struct acpi_iort_node *node)
return memory_access->memory_flags & ACPI_IORT_MF_CANWBS;
}

+static bool iort_pci_rc_supports_pasid(struct acpi_iort_node *node)
+{
+ struct acpi_iort_root_complex *pci_rc;
+
+ if (node->revision < 4)
+ return false;
+
+ pci_rc = (struct acpi_iort_root_complex *)node->node_data;
+ return pci_rc->flags & ACPI_IORT_RC_PASID_SUPPORTED;
+}
+
+static bool iort_pci_rc_supports_pasid_fwd(struct acpi_iort_node *node)
+{
+ struct acpi_iort_root_complex *pci_rc;
+
+ pci_rc = (struct acpi_iort_root_complex *)node->node_data;
+ return pci_rc->ats_attribute & ACPI_IORT_PASID_FWD_SUPPORTED;
+}
+
+static int iort_pci_rc_pasid_max_width(struct acpi_iort_node *node)
+{
+ struct acpi_iort_root_complex *pci_rc;
+
+ if (node->revision < 4)
+ return -ENODEV;
+
+ pci_rc = (struct acpi_iort_root_complex *)node->node_data;
+ return FIELD_GET(ACPI_IORT_PASID_MAX_WIDTH, pci_rc->pasid_capabilities);
+}
+
static int iort_iommu_xlate(struct device *dev, struct acpi_iort_node *node,
u32 streamid)
{
@@ -1471,6 +1501,10 @@ int iort_iommu_configure_id(struct device *dev, const u32 *id_in)
fwspec->flags |= IOMMU_FWSPEC_PCI_RC_ATS;
if (fwspec && iort_pci_rc_supports_canwbs(node))
fwspec->flags |= IOMMU_FWSPEC_PCI_RC_CANWBS;
+ if (fwspec && iort_pci_rc_supports_pasid(node))
+ fwspec->flags |= IOMMU_FWSPEC_PCI_RC_PASID;
+ if (fwspec && iort_pci_rc_supports_pasid_fwd(node))
+ fwspec->flags |= IOMMU_FWSPEC_PCI_RC_PASID_FWD;
} else {
node = iort_scan_node(ACPI_IORT_NODE_NAMED_COMPONENT,
iort_match_node_callback, dev);
@@ -1556,6 +1590,52 @@ int iort_dma_get_ranges(struct device *dev, u64 *limit)
return nc_dma_get_range(dev, limit);
}

+static struct acpi_iort_node *iort_pci_rc_node_for_dev(struct device *dev)
+{
+ struct pci_bus *pbus;
+
+ if (!dev_is_pci(dev))
+ return NULL;
+
+ pbus = to_pci_dev(dev)->bus;
+ return iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
+ iort_match_node_callback, &pbus->dev);
+}
+
+/**
+ * iort_pci_rc_pasid_max_width_known() - Whether IORT firmware describes the
+ * Root Complex PASID width for the given PCI device.
+ * @dev: PCI device to lookup
+ *
+ * Returns true iff a matching IORT Root Complex node exists and has revision
+ * >= 4 (IORT spec E.c), i.e. the PASID Capabilities descriptor is present.
+ */
+bool iort_pci_rc_pasid_max_width_known(struct device *dev)
+{
+ struct acpi_iort_node *node = iort_pci_rc_node_for_dev(dev);
+
+ return node && node->revision >= 4;
+}
+
+/**
+ * iort_pci_rc_pasid_max_width_for_dev() - Look up the Root Complex Max PASID
+ * Width for the given PCI device.
+ * @dev: PCI device to lookup
+ *
+ * Returns the Max PASID Width (bits[4:0] of PASID Capabilities) declared by
+ * the Root Complex node in IORT firmware, or a negative errno when the field
+ * is not present (RC node revision < 4) or the device is not PCI.
+ */
+int iort_pci_rc_pasid_max_width_for_dev(struct device *dev)
+{
+ struct acpi_iort_node *node = iort_pci_rc_node_for_dev(dev);
+
+ if (!node)
+ return -ENODEV;
+
+ return iort_pci_rc_pasid_max_width(node);
+}
+
static void __init acpi_iort_register_irq(int hwirq, const char *name,
int trigger,
struct resource *res)
diff --git a/include/linux/acpi_iort.h b/include/linux/acpi_iort.h
index 17bb3374f4ca..befe19d87c2c 100644
--- a/include/linux/acpi_iort.h
+++ b/include/linux/acpi_iort.h
@@ -45,6 +45,8 @@ void iort_put_rmr_sids(struct fwnode_handle *iommu_fwnode,
int iort_dma_get_ranges(struct device *dev, u64 *limit);
int iort_iommu_configure_id(struct device *dev, const u32 *id_in);
void iort_iommu_get_resv_regions(struct device *dev, struct list_head *head);
+bool iort_pci_rc_pasid_max_width_known(struct device *dev);
+int iort_pci_rc_pasid_max_width_for_dev(struct device *dev);
phys_addr_t acpi_iort_dma_get_max_cpu_address(void);
#else
static inline u32 iort_msi_map_id(struct device *dev, u32 id)
@@ -71,6 +73,10 @@ static inline int iort_iommu_configure_id(struct device *dev, const u32 *id_in)
static inline
void iort_iommu_get_resv_regions(struct device *dev, struct list_head *head)
{ }
+static inline bool iort_pci_rc_pasid_max_width_known(struct device *dev)
+{ return false; }
+static inline int iort_pci_rc_pasid_max_width_for_dev(struct device *dev)
+{ return -ENODEV; }

static inline phys_addr_t acpi_iort_dma_get_max_cpu_address(void)
{ return PHYS_ADDR_MAX; }
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e587d4ac4d33..e78d7f56d603 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -1118,6 +1118,10 @@ struct iommu_fwspec {
#define IOMMU_FWSPEC_PCI_RC_ATS (1 << 0)
/* CANWBS is supported */
#define IOMMU_FWSPEC_PCI_RC_CANWBS (1 << 1)
+/* Root complex declares PASID support (IORT E.c Flags bit 0) */
+#define IOMMU_FWSPEC_PCI_RC_PASID (1 << 2)
+/* Root complex forwards PASID on translated transactions (IORT ATS bit 2) */
+#define IOMMU_FWSPEC_PCI_RC_PASID_FWD (1 << 3)

/*
* An iommu attach handle represents a relationship between an iommu domain
--
2.25.1