|
@@ -19,9 +19,11 @@
|
|
|
#define pr_fmt(fmt) "ACPI: IORT: " fmt
|
|
|
|
|
|
#include <linux/acpi_iort.h>
|
|
|
+#include <linux/iommu.h>
|
|
|
#include <linux/kernel.h>
|
|
|
#include <linux/list.h>
|
|
|
#include <linux/pci.h>
|
|
|
+#include <linux/platform_device.h>
|
|
|
#include <linux/slab.h>
|
|
|
|
|
|
struct iort_its_msi_chip {
|
|
@@ -457,6 +459,153 @@ struct irq_domain *iort_get_device_domain(struct device *dev, u32 req_id)
|
|
|
return irq_find_matching_fwnode(handle, DOMAIN_BUS_PCI_MSI);
|
|
|
}
|
|
|
|
|
|
+struct iort_iommu_config {
|
|
|
+ const char *name;
|
|
|
+ int (*iommu_init)(struct acpi_iort_node *node);
|
|
|
+ bool (*iommu_is_coherent)(struct acpi_iort_node *node);
|
|
|
+ int (*iommu_count_resources)(struct acpi_iort_node *node);
|
|
|
+ void (*iommu_init_resources)(struct resource *res,
|
|
|
+ struct acpi_iort_node *node);
|
|
|
+};
|
|
|
+
|
|
|
+static __init
|
|
|
+const struct iort_iommu_config *iort_get_iommu_cfg(struct acpi_iort_node *node)
|
|
|
+{
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * iort_add_smmu_platform_device() - Allocate a platform device for SMMU
|
|
|
+ * @node: Pointer to SMMU ACPI IORT node
|
|
|
+ *
|
|
|
+ * Returns: 0 on success, <0 failure
|
|
|
+ */
|
|
|
+static int __init iort_add_smmu_platform_device(struct acpi_iort_node *node)
|
|
|
+{
|
|
|
+ struct fwnode_handle *fwnode;
|
|
|
+ struct platform_device *pdev;
|
|
|
+ struct resource *r;
|
|
|
+ enum dev_dma_attr attr;
|
|
|
+ int ret, count;
|
|
|
+ const struct iort_iommu_config *ops = iort_get_iommu_cfg(node);
|
|
|
+
|
|
|
+ if (!ops)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ pdev = platform_device_alloc(ops->name, PLATFORM_DEVID_AUTO);
|
|
|
+ if (!pdev)
|
|
|
+ return PTR_ERR(pdev);
|
|
|
+
|
|
|
+ count = ops->iommu_count_resources(node);
|
|
|
+
|
|
|
+ r = kcalloc(count, sizeof(*r), GFP_KERNEL);
|
|
|
+ if (!r) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto dev_put;
|
|
|
+ }
|
|
|
+
|
|
|
+ ops->iommu_init_resources(r, node);
|
|
|
+
|
|
|
+ ret = platform_device_add_resources(pdev, r, count);
|
|
|
+ /*
|
|
|
+ * Resources are duplicated in platform_device_add_resources,
|
|
|
+ * free their allocated memory
|
|
|
+ */
|
|
|
+ kfree(r);
|
|
|
+
|
|
|
+ if (ret)
|
|
|
+ goto dev_put;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Add a copy of IORT node pointer to platform_data to
|
|
|
+ * be used to retrieve IORT data information.
|
|
|
+ */
|
|
|
+ ret = platform_device_add_data(pdev, &node, sizeof(node));
|
|
|
+ if (ret)
|
|
|
+ goto dev_put;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We expect the dma masks to be equivalent for
|
|
|
+ * all SMMUs set-ups
|
|
|
+ */
|
|
|
+ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
|
|
+
|
|
|
+ fwnode = iort_get_fwnode(node);
|
|
|
+
|
|
|
+ if (!fwnode) {
|
|
|
+ ret = -ENODEV;
|
|
|
+ goto dev_put;
|
|
|
+ }
|
|
|
+
|
|
|
+ pdev->dev.fwnode = fwnode;
|
|
|
+
|
|
|
+ attr = ops->iommu_is_coherent(node) ?
|
|
|
+ DEV_DMA_COHERENT : DEV_DMA_NON_COHERENT;
|
|
|
+
|
|
|
+ /* Configure DMA for the page table walker */
|
|
|
+ acpi_dma_configure(&pdev->dev, attr);
|
|
|
+
|
|
|
+ ret = platform_device_add(pdev);
|
|
|
+ if (ret)
|
|
|
+ goto dma_deconfigure;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+dma_deconfigure:
|
|
|
+ acpi_dma_deconfigure(&pdev->dev);
|
|
|
+dev_put:
|
|
|
+ platform_device_put(pdev);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void __init iort_init_platform_devices(void)
|
|
|
+{
|
|
|
+ struct acpi_iort_node *iort_node, *iort_end;
|
|
|
+ struct acpi_table_iort *iort;
|
|
|
+ struct fwnode_handle *fwnode;
|
|
|
+ int i, ret;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * iort_table and iort both point to the start of IORT table, but
|
|
|
+ * have different struct types
|
|
|
+ */
|
|
|
+ iort = (struct acpi_table_iort *)iort_table;
|
|
|
+
|
|
|
+ /* Get the first IORT node */
|
|
|
+ iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
|
|
|
+ iort->node_offset);
|
|
|
+ iort_end = ACPI_ADD_PTR(struct acpi_iort_node, iort,
|
|
|
+ iort_table->length);
|
|
|
+
|
|
|
+ for (i = 0; i < iort->node_count; i++) {
|
|
|
+ if (iort_node >= iort_end) {
|
|
|
+ pr_err("iort node pointer overflows, bad table\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((iort_node->type == ACPI_IORT_NODE_SMMU) ||
|
|
|
+ (iort_node->type == ACPI_IORT_NODE_SMMU_V3)) {
|
|
|
+
|
|
|
+ fwnode = acpi_alloc_fwnode_static();
|
|
|
+ if (!fwnode)
|
|
|
+ return;
|
|
|
+
|
|
|
+ iort_set_fwnode(iort_node, fwnode);
|
|
|
+
|
|
|
+ ret = iort_add_smmu_platform_device(iort_node);
|
|
|
+ if (ret) {
|
|
|
+ iort_delete_fwnode(iort_node);
|
|
|
+ acpi_free_fwnode_static(fwnode);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort_node,
|
|
|
+ iort_node->length);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
void __init acpi_iort_init(void)
|
|
|
{
|
|
|
acpi_status status;
|
|
@@ -472,5 +621,7 @@ void __init acpi_iort_init(void)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ iort_init_platform_devices();
|
|
|
+
|
|
|
acpi_probe_device_table(iort);
|
|
|
}
|