Commit bc8648d4 authored by Robin Murphy's avatar Robin Murphy Committed by Lorenzo Pieralisi

ACPI/IORT: Handle PCI aliases properly for IOMMUs

When a PCI device has DMA quirks, we need to ensure that an upstream
IOMMU knows about all possible aliases, since the presence of a DMA
quirk does not preclude the device still also emitting transactions
(e.g. MSIs) on its 'real' RID. Similarly, the rules for bridge aliasing
are relatively complex, and some bridges may only take ownership of
transactions under particular transient circumstances, leading again to
multiple RIDs potentially being seen at the IOMMU for the given device.

Take all this into account in iort_iommu_configure() by mapping every
RID produced by the alias walk, not just whichever one comes out last.
Since adding any more internal PTR_ERR() juggling would have confused me
no end, a bit of refactoring happens in the process - we know where to
find the ops if everything succeeded, so we're free to just pass regular
error codes around up until then.

CC: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
CC: Hanjun Guo <hanjun.guo@linaro.org>
CC: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: default avatarRobin Murphy <robin.murphy@arm.com>
[lorenzo.pieralisi@arm.com: tagged __get_pci_rid __maybe_unused]
Signed-off-by: default avatarLorenzo Pieralisi <lorenzo.pieralisi@arm.com>
parent aae4e7a8
...@@ -588,7 +588,8 @@ void acpi_configure_pmsi_domain(struct device *dev) ...@@ -588,7 +588,8 @@ void acpi_configure_pmsi_domain(struct device *dev)
dev_set_msi_domain(dev, msi_domain); dev_set_msi_domain(dev, msi_domain);
} }
static int __get_pci_rid(struct pci_dev *pdev, u16 alias, void *data) static int __maybe_unused __get_pci_rid(struct pci_dev *pdev, u16 alias,
void *data)
{ {
u32 *rid = data; u32 *rid = data;
...@@ -633,8 +634,7 @@ int iort_add_device_replay(const struct iommu_ops *ops, struct device *dev) ...@@ -633,8 +634,7 @@ int iort_add_device_replay(const struct iommu_ops *ops, struct device *dev)
{ {
int err = 0; int err = 0;
if (!IS_ERR_OR_NULL(ops) && ops->add_device && dev->bus && if (ops->add_device && dev->bus && !dev->iommu_group)
!dev->iommu_group)
err = ops->add_device(dev); err = ops->add_device(dev);
return err; return err;
...@@ -648,20 +648,19 @@ int iort_add_device_replay(const struct iommu_ops *ops, struct device *dev) ...@@ -648,20 +648,19 @@ int iort_add_device_replay(const struct iommu_ops *ops, struct device *dev)
{ return 0; } { return 0; }
#endif #endif
static const struct iommu_ops *iort_iommu_xlate(struct device *dev, static int iort_iommu_xlate(struct device *dev, struct acpi_iort_node *node,
struct acpi_iort_node *node,
u32 streamid) u32 streamid)
{ {
const struct iommu_ops *ops = NULL; const struct iommu_ops *ops;
int ret = -ENODEV;
struct fwnode_handle *iort_fwnode; struct fwnode_handle *iort_fwnode;
if (node) { if (!node)
return -ENODEV;
iort_fwnode = iort_get_fwnode(node); iort_fwnode = iort_get_fwnode(node);
if (!iort_fwnode) if (!iort_fwnode)
return NULL; return -ENODEV;
ops = iommu_ops_from_fwnode(iort_fwnode);
/* /*
* If the ops look-up fails, this means that either * If the ops look-up fails, this means that either
* the SMMU drivers have not been probed yet or that * the SMMU drivers have not been probed yet or that
...@@ -670,14 +669,28 @@ static const struct iommu_ops *iort_iommu_xlate(struct device *dev, ...@@ -670,14 +669,28 @@ static const struct iommu_ops *iort_iommu_xlate(struct device *dev,
* in the kernel or not, defer the IOMMU configuration * in the kernel or not, defer the IOMMU configuration
* or just abort it. * or just abort it.
*/ */
ops = iommu_ops_from_fwnode(iort_fwnode);
if (!ops) if (!ops)
return iort_iommu_driver_enabled(node->type) ? return iort_iommu_driver_enabled(node->type) ?
ERR_PTR(-EPROBE_DEFER) : NULL; -EPROBE_DEFER : -ENODEV;
ret = arm_smmu_iort_xlate(dev, streamid, iort_fwnode, ops); return arm_smmu_iort_xlate(dev, streamid, iort_fwnode, ops);
} }
struct iort_pci_alias_info {
struct device *dev;
struct acpi_iort_node *node;
};
static int iort_pci_iommu_init(struct pci_dev *pdev, u16 alias, void *data)
{
struct iort_pci_alias_info *info = data;
struct acpi_iort_node *parent;
u32 streamid;
return ret ? NULL : ops; parent = iort_node_map_id(info->node, alias, &streamid,
IORT_IOMMU_TYPE);
return iort_iommu_xlate(info->dev, parent, streamid);
} }
/** /**
...@@ -713,9 +726,9 @@ void iort_set_dma_mask(struct device *dev) ...@@ -713,9 +726,9 @@ void iort_set_dma_mask(struct device *dev)
const struct iommu_ops *iort_iommu_configure(struct device *dev) const struct iommu_ops *iort_iommu_configure(struct device *dev)
{ {
struct acpi_iort_node *node, *parent; struct acpi_iort_node *node, *parent;
const struct iommu_ops *ops = NULL; const struct iommu_ops *ops;
u32 streamid = 0; u32 streamid = 0;
int err; int err = -ENODEV;
/* /*
* If we already translated the fwspec there * If we already translated the fwspec there
...@@ -727,21 +740,16 @@ const struct iommu_ops *iort_iommu_configure(struct device *dev) ...@@ -727,21 +740,16 @@ const struct iommu_ops *iort_iommu_configure(struct device *dev)
if (dev_is_pci(dev)) { if (dev_is_pci(dev)) {
struct pci_bus *bus = to_pci_dev(dev)->bus; struct pci_bus *bus = to_pci_dev(dev)->bus;
u32 rid; struct iort_pci_alias_info info = { .dev = dev };
pci_for_each_dma_alias(to_pci_dev(dev), __get_pci_rid,
&rid);
node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX, node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
iort_match_node_callback, &bus->dev); iort_match_node_callback, &bus->dev);
if (!node) if (!node)
return NULL; return NULL;
parent = iort_node_map_id(node, rid, &streamid, info.node = node;
IORT_IOMMU_TYPE); err = pci_for_each_dma_alias(to_pci_dev(dev),
iort_pci_iommu_init, &info);
ops = iort_iommu_xlate(dev, parent, streamid);
} else { } else {
int i = 0; int i = 0;
...@@ -750,31 +758,30 @@ const struct iommu_ops *iort_iommu_configure(struct device *dev) ...@@ -750,31 +758,30 @@ const struct iommu_ops *iort_iommu_configure(struct device *dev)
if (!node) if (!node)
return NULL; return NULL;
parent = iort_node_map_platform_id(node, &streamid, do {
IORT_IOMMU_TYPE, i++);
while (parent) {
ops = iort_iommu_xlate(dev, parent, streamid);
if (IS_ERR_OR_NULL(ops))
return ops;
parent = iort_node_map_platform_id(node, &streamid, parent = iort_node_map_platform_id(node, &streamid,
IORT_IOMMU_TYPE, IORT_IOMMU_TYPE,
i++); i++);
}
if (parent)
err = iort_iommu_xlate(dev, parent, streamid);
} while (parent && !err);
} }
/* /*
* If we have reason to believe the IOMMU driver missed the initial * If we have reason to believe the IOMMU driver missed the initial
* add_device callback for dev, replay it to get things in order. * add_device callback for dev, replay it to get things in order.
*/ */
if (!err) {
ops = dev->iommu_fwspec->ops;
err = iort_add_device_replay(ops, dev); err = iort_add_device_replay(ops, dev);
if (err) }
ops = ERR_PTR(err);
/* Ignore all other errors apart from EPROBE_DEFER */ /* Ignore all other errors apart from EPROBE_DEFER */
if (IS_ERR(ops) && (PTR_ERR(ops) != -EPROBE_DEFER)) { if (err == -EPROBE_DEFER) {
dev_dbg(dev, "Adding to IOMMU failed: %ld\n", PTR_ERR(ops)); ops = ERR_PTR(err);
} else if (err) {
dev_dbg(dev, "Adding to IOMMU failed: %d\n", err);
ops = NULL; ops = NULL;
} }
......
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