git: 41ce5498f8e6 - main - Add OFW support to arm64's IOMMU framework. This is needed to support non-PCI devices like memory-mapped display controllers. Split-out some initialization code from iommu_ctx_alloc() into iommu_ctx_init() method so we could pass controller's MD-data obtained from DTS to the driver prior to a CTX initialization.

From: Ruslan Bukin <br_at_FreeBSD.org>
Date: Wed, 18 May 2022 13:18:11 UTC
The branch main has been updated by br:

URL: https://cgit.FreeBSD.org/src/commit/?id=41ce5498f8e69e6820962e813eb3b40c465079d0

commit 41ce5498f8e69e6820962e813eb3b40c465079d0
Author:     Ruslan Bukin <br@FreeBSD.org>
AuthorDate: 2022-05-18 13:11:23 +0000
Commit:     Ruslan Bukin <br@FreeBSD.org>
CommitDate: 2022-05-18 13:11:23 +0000

    Add OFW support to arm64's IOMMU framework.
    This is needed to support non-PCI devices like memory-mapped
    display controllers.
    Split-out some initialization code from iommu_ctx_alloc() into
    iommu_ctx_init() method so we could pass controller's MD-data
    obtained from DTS to the driver prior to a CTX initialization.
    
    Tested on Morello SoC.
    
    Sponsored by:   UKRI
---
 sys/arm64/iommu/iommu.c    | 163 ++++++++++++++++++++++++++++++++++++++++-----
 sys/arm64/iommu/iommu.h    |   1 +
 sys/arm64/iommu/iommu_if.m |  24 +++++++
 sys/arm64/iommu/smmu.c     |  92 +++++++++++++++++--------
 sys/arm64/iommu/smmu_fdt.c |   2 +
 5 files changed, 239 insertions(+), 43 deletions(-)

diff --git a/sys/arm64/iommu/iommu.c b/sys/arm64/iommu/iommu.c
index 447f3e141610..aa48dcf5ab5e 100644
--- a/sys/arm64/iommu/iommu.c
+++ b/sys/arm64/iommu/iommu.c
@@ -57,6 +57,12 @@ __FBSDID("$FreeBSD$");
 #include <dev/iommu/busdma_iommu.h>
 #include <machine/vmparam.h>
 
+#ifdef FDT
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#endif
+
 #include "iommu.h"
 #include "iommu_if.h"
 
@@ -180,23 +186,149 @@ iommu_tag_init(struct bus_dma_tag_iommu *t)
 }
 
 static struct iommu_ctx *
-iommu_ctx_alloc(device_t dev, struct iommu_domain *iodom, bool disabled)
+iommu_ctx_alloc(device_t requester, struct iommu_domain *iodom, bool disabled)
 {
 	struct iommu_unit *iommu;
 	struct iommu_ctx *ioctx;
 
 	iommu = iodom->iommu;
 
-	ioctx = IOMMU_CTX_ALLOC(iommu->dev, iodom, dev, disabled);
+	ioctx = IOMMU_CTX_ALLOC(iommu->dev, iodom, requester, disabled);
 	if (ioctx == NULL)
 		return (NULL);
 
+	ioctx->domain = iodom;
+
+	return (ioctx);
+}
+
+static int
+iommu_ctx_init(device_t requester, struct iommu_ctx *ioctx)
+{
+	struct bus_dma_tag_iommu *tag;
+	struct iommu_domain *iodom;
+	struct iommu_unit *iommu;
+	int error;
+
+	iodom = ioctx->domain;
+	iommu = iodom->iommu;
+
+	error = IOMMU_CTX_INIT(iommu->dev, ioctx);
+	if (error)
+		return (error);
+
+	tag = ioctx->tag = malloc(sizeof(struct bus_dma_tag_iommu),
+	    M_IOMMU, M_WAITOK | M_ZERO);
+	tag->owner = requester;
+	tag->ctx = ioctx;
+	tag->ctx->domain = iodom;
+
+	iommu_tag_init(tag);
+
+	return (error);
+}
+
+static struct iommu_unit *
+iommu_lookup(device_t dev)
+{
+	struct iommu_entry *entry;
+	struct iommu_unit *iommu;
+
+	IOMMU_LIST_LOCK();
+	LIST_FOREACH(entry, &iommu_list, next) {
+		iommu = entry->iommu;
+		if (iommu->dev == dev) {
+			IOMMU_LIST_UNLOCK();
+			return (iommu);
+		}
+	}
+	IOMMU_LIST_UNLOCK();
+
+	return (NULL);
+}
+
+struct iommu_ctx *
+iommu_get_ctx_ofw(device_t dev, int channel)
+{
+	struct iommu_domain *iodom;
+	struct iommu_unit *iommu;
+	struct iommu_ctx *ioctx;
+	phandle_t node, parent;
+	device_t iommu_dev;
+	pcell_t *cells;
+	int niommus;
+	int ncells;
+	int error;
+
+	node = ofw_bus_get_node(dev);
+	if (node <= 0) {
+		device_printf(dev,
+		    "%s called on not ofw based device.\n", __func__);
+		return (NULL);
+	}
+
+	error = ofw_bus_parse_xref_list_get_length(node,
+	    "iommus", "#iommu-cells", &niommus);
+	if (error) {
+		device_printf(dev, "%s can't get iommu list.\n", __func__);
+		return (NULL);
+	}
+
+	if (niommus == 0) {
+		device_printf(dev, "%s iommu list is empty.\n", __func__);
+		return (NULL);
+	}
+
+	error = ofw_bus_parse_xref_list_alloc(node, "iommus", "#iommu-cells",
+	    channel, &parent, &ncells, &cells);
+	if (error != 0) {
+		device_printf(dev, "%s can't get iommu device xref.\n",
+		    __func__);
+		return (NULL);
+	}
+
+	iommu_dev = OF_device_from_xref(parent);
+	if (iommu_dev == NULL) {
+		device_printf(dev, "%s can't get iommu device.\n", __func__);
+		return (NULL);
+	}
+
+	iommu = iommu_lookup(iommu_dev);
+	if (iommu == NULL) {
+		device_printf(dev, "%s can't lookup iommu.\n", __func__);
+		return (NULL);
+	}
+
 	/*
-	 * iommu can also be used for non-PCI based devices.
-	 * This should be reimplemented as new newbus method with
-	 * pci_get_rid() as a default for PCI device class.
+	 * In our current configuration we have a domain per each ctx,
+	 * so allocate a domain first.
 	 */
-	ioctx->rid = pci_get_rid(dev);
+	iodom = iommu_domain_alloc(iommu);
+	if (iodom == NULL) {
+		device_printf(dev, "%s can't allocate domain.\n", __func__);
+		return (NULL);
+	}
+
+	ioctx = iommu_ctx_alloc(dev, iodom, false);
+	if (ioctx == NULL) {
+		iommu_domain_free(iodom);
+		return (NULL);
+	}
+
+	ioctx->domain = iodom;
+
+	error = IOMMU_OFW_MD_DATA(iommu->dev, ioctx, cells, ncells);
+	if (error) {
+		device_printf(dev, "%s can't set MD data\n", __func__);
+		return (NULL);
+	}
+
+	error = iommu_ctx_init(dev, ioctx);
+	if (error) {
+		IOMMU_CTX_FREE(iommu->dev, ioctx);
+		iommu_domain_free(iodom);
+		return (NULL);
+	}
 
 	return (ioctx);
 }
@@ -205,9 +337,9 @@ struct iommu_ctx *
 iommu_get_ctx(struct iommu_unit *iommu, device_t requester,
     uint16_t rid, bool disabled, bool rmrr)
 {
-	struct iommu_ctx *ioctx;
 	struct iommu_domain *iodom;
-	struct bus_dma_tag_iommu *tag;
+	struct iommu_ctx *ioctx;
+	int error;
 
 	IOMMU_LOCK(iommu);
 	ioctx = IOMMU_CTX_LOOKUP(iommu->dev, requester);
@@ -231,15 +363,12 @@ iommu_get_ctx(struct iommu_unit *iommu, device_t requester,
 		return (NULL);
 	}
 
-	tag = ioctx->tag = malloc(sizeof(struct bus_dma_tag_iommu),
-	    M_IOMMU, M_WAITOK | M_ZERO);
-	tag->owner = requester;
-	tag->ctx = ioctx;
-	tag->ctx->domain = iodom;
-
-	iommu_tag_init(tag);
-
-	ioctx->domain = iodom;
+	error = iommu_ctx_init(requester, ioctx);
+	if (error) {
+		IOMMU_CTX_FREE(iommu->dev, ioctx);
+		iommu_domain_free(iodom);
+		return (NULL);
+	}
 
 	return (ioctx);
 }
diff --git a/sys/arm64/iommu/iommu.h b/sys/arm64/iommu/iommu.h
index 2071173070df..c221f619b5db 100644
--- a/sys/arm64/iommu/iommu.h
+++ b/sys/arm64/iommu/iommu.h
@@ -40,5 +40,6 @@
 
 int iommu_unregister(struct iommu_unit *unit);
 int iommu_register(struct iommu_unit *unit);
+struct iommu_ctx * iommu_get_ctx_ofw(device_t dev, int channel);
 
 #endif /* _ARM64_IOMMU_IOMMU_H_ */
diff --git a/sys/arm64/iommu/iommu_if.m b/sys/arm64/iommu/iommu_if.m
index ef8c3e7b57b8..6d4f963c5ff1 100644
--- a/sys/arm64/iommu/iommu_if.m
+++ b/sys/arm64/iommu/iommu_if.m
@@ -42,6 +42,12 @@
 #include <dev/pci/pcivar.h>
 #include <dev/iommu/iommu.h>
 
+#ifdef FDT
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#endif
+
 INTERFACE iommu;
 
 #
@@ -116,6 +122,14 @@ METHOD struct iommu_ctx * ctx_alloc {
 	bool			disabled;
 };
 
+#
+# Initialize the new iommu context.
+#
+METHOD int ctx_init {
+	device_t		dev;
+	struct iommu_ctx	*ioctx;
+};
+
 #
 # Free the iommu context.
 #
@@ -123,3 +137,13 @@ METHOD void ctx_free {
 	device_t		dev;
 	struct iommu_ctx	*ioctx;
 };
+
+#
+# Notify controller we have machine-dependent data.
+#
+METHOD int ofw_md_data {
+	device_t dev;
+	struct iommu_ctx *ioctx;
+	pcell_t *cells;
+	int ncells;
+};
diff --git a/sys/arm64/iommu/smmu.c b/sys/arm64/iommu/smmu.c
index 5d4401c5cee9..57f6826faa5e 100644
--- a/sys/arm64/iommu/smmu.c
+++ b/sys/arm64/iommu/smmu.c
@@ -1844,42 +1844,63 @@ smmu_ctx_alloc(device_t dev, struct iommu_domain *iodom, device_t child,
     bool disabled)
 {
 	struct smmu_domain *domain;
+	struct smmu_ctx *ctx;
+
+	domain = (struct smmu_domain *)iodom;
+
+	ctx = malloc(sizeof(struct smmu_ctx), M_SMMU, M_WAITOK | M_ZERO);
+	ctx->dev = child;
+	ctx->domain = domain;
+	if (disabled)
+		ctx->bypass = true;
+
+	IOMMU_DOMAIN_LOCK(iodom);
+	LIST_INSERT_HEAD(&domain->ctx_list, ctx, next);
+	IOMMU_DOMAIN_UNLOCK(iodom);
+
+	return (&ctx->ioctx);
+}
+
+static int
+smmu_ctx_init(device_t dev, struct iommu_ctx *ioctx)
+{
+	struct smmu_domain *domain;
+	struct iommu_domain *iodom;
 	struct smmu_softc *sc;
 	struct smmu_ctx *ctx;
 	devclass_t pci_class;
 	u_int sid;
 	int err;
 
+	ctx = (struct smmu_ctx *)ioctx;
+
 	sc = device_get_softc(dev);
-	domain = (struct smmu_domain *)iodom;
 
-	pci_class = devclass_find("pci");
-	if (device_get_devclass(device_get_parent(child)) != pci_class)
-		return (NULL);
+	domain = ctx->domain;
+	iodom = (struct iommu_domain *)domain;
 
+	pci_class = devclass_find("pci");
+	if (device_get_devclass(device_get_parent(ctx->dev)) == pci_class) {
 #ifdef DEV_ACPI
-	err = smmu_pci_get_sid_acpi(child, NULL, &sid);
+		err = smmu_pci_get_sid_acpi(ctx->dev, NULL, &sid);
 #else
-	err = smmu_pci_get_sid_fdt(child, NULL, &sid);
+		err = smmu_pci_get_sid_fdt(ctx->dev, NULL, &sid);
 #endif
-	if (err)
-		return (NULL);
+		if (err)
+			return (err);
+
+		ioctx->rid = pci_get_rid(dev);
+		ctx->sid = sid;
+		ctx->vendor = pci_get_vendor(ctx->dev);
+		ctx->device = pci_get_device(ctx->dev);
+	}
 
 	if (sc->features & SMMU_FEATURE_2_LVL_STREAM_TABLE) {
-		err = smmu_init_l1_entry(sc, sid);
+		err = smmu_init_l1_entry(sc, ctx->sid);
 		if (err)
-			return (NULL);
+			return (err);
 	}
 
-	ctx = malloc(sizeof(struct smmu_ctx), M_SMMU, M_WAITOK | M_ZERO);
-	ctx->vendor = pci_get_vendor(child);
-	ctx->device = pci_get_device(child);
-	ctx->dev = child;
-	ctx->sid = sid;
-	ctx->domain = domain;
-	if (disabled)
-		ctx->bypass = true;
-
 	/*
 	 * Neoverse N1 SDP:
 	 * 0x800 xhci
@@ -1889,14 +1910,11 @@ smmu_ctx_alloc(device_t dev, struct iommu_domain *iodom, device_t child,
 
 	smmu_init_ste(sc, domain->cd, ctx->sid, ctx->bypass);
 
-	if (iommu_is_buswide_ctx(iodom->iommu, pci_get_bus(ctx->dev)))
-		smmu_set_buswide(dev, domain, ctx);
+	if (device_get_devclass(device_get_parent(ctx->dev)) == pci_class)
+		if (iommu_is_buswide_ctx(iodom->iommu, pci_get_bus(ctx->dev)))
+			smmu_set_buswide(dev, domain, ctx);
 
-	IOMMU_DOMAIN_LOCK(iodom);
-	LIST_INSERT_HEAD(&domain->ctx_list, ctx, next);
-	IOMMU_DOMAIN_UNLOCK(iodom);
-
-	return (&ctx->ioctx);
+	return (0);
 }
 
 static void
@@ -1993,6 +2011,24 @@ smmu_find(device_t dev, device_t child)
 	return (0);
 }
 
+#ifdef FDT
+static int
+smmu_ofw_md_data(device_t dev, struct iommu_ctx *ioctx, pcell_t *cells,
+    int ncells)
+{
+	struct smmu_ctx *ctx;
+
+	ctx = (struct smmu_ctx *)ioctx;
+
+	if (ncells != 1)
+		return (-1);
+
+	ctx->sid = cells[0];
+
+	return (0);
+}
+#endif
+
 static device_method_t smmu_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_detach,	smmu_detach),
@@ -2004,8 +2040,12 @@ static device_method_t smmu_methods[] = {
 	DEVMETHOD(iommu_domain_alloc,	smmu_domain_alloc),
 	DEVMETHOD(iommu_domain_free,	smmu_domain_free),
 	DEVMETHOD(iommu_ctx_alloc,	smmu_ctx_alloc),
+	DEVMETHOD(iommu_ctx_init,	smmu_ctx_init),
 	DEVMETHOD(iommu_ctx_free,	smmu_ctx_free),
 	DEVMETHOD(iommu_ctx_lookup,	smmu_ctx_lookup),
+#ifdef FDT
+	DEVMETHOD(iommu_ofw_md_data,	smmu_ofw_md_data),
+#endif
 
 	/* Bus interface */
 	DEVMETHOD(bus_read_ivar,	smmu_read_ivar),
diff --git a/sys/arm64/iommu/smmu_fdt.c b/sys/arm64/iommu/smmu_fdt.c
index f2d441fe8340..e5541b50058f 100644
--- a/sys/arm64/iommu/smmu_fdt.c
+++ b/sys/arm64/iommu/smmu_fdt.c
@@ -176,6 +176,8 @@ smmu_fdt_attach(device_t dev)
 		return (ENXIO);
 	}
 
+	OF_device_register_xref(OF_xref_from_node(node), dev);
+
 	return (0);
 
 error: