git: c9e4d2505206 - main - iommu_gas: add iommu_gas_remove()

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Mon, 15 Aug 2022 14:58:59 UTC
The branch main has been updated by kib:

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

commit c9e4d25052065d1e0cbdb3a0eea3f21c9225d1d1
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2022-07-31 08:43:43 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2022-08-15 14:54:57 +0000

    iommu_gas: add iommu_gas_remove()
    
    The function removes the range of addresses from GAS.  Right now it is
    unused.
    
    The initial version of the patch was extracted from the stalled work for
    integration of DMAR and bhyve, see D25672. Having the function in the
    main tree would allow it to co-evolve with other active changes to the
    IOMMU driver.
    
    Requested by:   alc
    Reviewed by:    alc, dougm
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D36001
---
 sys/dev/iommu/iommu.h     |   2 +
 sys/dev/iommu/iommu_gas.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/iommu/iommu_gas.h |   2 +
 3 files changed, 122 insertions(+)

diff --git a/sys/dev/iommu/iommu.h b/sys/dev/iommu/iommu.h
index ae4022c5c4f7..2531ef09d9cd 100644
--- a/sys/dev/iommu/iommu.h
+++ b/sys/dev/iommu/iommu.h
@@ -171,6 +171,8 @@ struct iommu_map_entry *iommu_gas_alloc_entry(struct iommu_domain *domain,
     u_int flags);
 void iommu_gas_free_entry(struct iommu_map_entry *entry);
 void iommu_gas_free_space(struct iommu_map_entry *entry);
+void iommu_gas_remove(struct iommu_domain *domain, iommu_gaddr_t start,
+    iommu_gaddr_t size);
 int iommu_gas_map(struct iommu_domain *domain,
     const struct bus_dma_tag_common *common, iommu_gaddr_t size, int offset,
     u_int eflags, u_int flags, vm_page_t *ma, struct iommu_map_entry **res);
diff --git a/sys/dev/iommu/iommu_gas.c b/sys/dev/iommu/iommu_gas.c
index bad56ab9140e..ca5a614060fe 100644
--- a/sys/dev/iommu/iommu_gas.c
+++ b/sys/dev/iommu/iommu_gas.c
@@ -600,6 +600,124 @@ iommu_gas_free_region(struct iommu_map_entry *entry)
 	IOMMU_DOMAIN_UNLOCK(domain);
 }
 
+static struct iommu_map_entry *
+iommu_gas_remove_clip_left(struct iommu_domain *domain, iommu_gaddr_t start,
+    iommu_gaddr_t end, struct iommu_map_entry **r)
+{
+	struct iommu_map_entry *entry, *res, fentry;
+
+	IOMMU_DOMAIN_ASSERT_LOCKED(domain);
+	MPASS(start <= end);
+	MPASS(end <= domain->last_place->end);
+
+	/*
+	 * Find an entry which contains the supplied guest's address
+	 * start, or the first entry after the start.  Since we
+	 * asserted that start is below domain end, entry should
+	 * exist.  Then clip it if needed.
+	 */
+	fentry.start = start + 1;
+	fentry.end = start + 1;
+	entry = RB_NFIND(iommu_gas_entries_tree, &domain->rb_root, &fentry);
+
+	if (entry->start >= start ||
+	    (entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
+		return (entry);
+
+	res = *r;
+	*r = NULL;
+	*res = *entry;
+	res->start = entry->end = start;
+	RB_UPDATE_AUGMENT(entry, rb_entry);
+	iommu_gas_rb_insert(domain, res);
+	return (res);
+}
+
+static bool
+iommu_gas_remove_clip_right(struct iommu_domain *domain,
+    iommu_gaddr_t end, struct iommu_map_entry *entry,
+    struct iommu_map_entry *r)
+{
+	if (entry->start >= end || (entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
+		return (false);
+
+	*r = *entry;
+	r->end = entry->start = end;
+	RB_UPDATE_AUGMENT(entry, rb_entry);
+	iommu_gas_rb_insert(domain, r);
+	return (true);
+}
+
+static void
+iommu_gas_remove_unmap(struct iommu_domain *domain,
+    struct iommu_map_entry *entry, struct iommu_map_entries_tailq *gcp)
+{
+	IOMMU_DOMAIN_ASSERT_LOCKED(domain);
+
+	if ((entry->flags & (IOMMU_MAP_ENTRY_UNMAPPED |
+	    IOMMU_MAP_ENTRY_REMOVING)) != 0)
+		return;
+	MPASS((entry->flags & IOMMU_MAP_ENTRY_PLACE) == 0);
+	entry->flags |= IOMMU_MAP_ENTRY_REMOVING;
+	TAILQ_INSERT_TAIL(gcp, entry, dmamap_link);
+}
+
+/*
+ * Remove specified range from the GAS of the domain.  Note that the
+ * removal is not guaranteed to occur upon the function return, it
+ * might be finalized some time after, when hardware reports that
+ * (queued) IOTLB invalidation was performed.
+ */
+void
+iommu_gas_remove(struct iommu_domain *domain, iommu_gaddr_t start,
+    iommu_gaddr_t size)
+{
+	struct iommu_map_entry *entry, *nentry, *r1, *r2;
+	struct iommu_map_entries_tailq gc;
+	iommu_gaddr_t end;
+
+	end = start + size;
+	r1 = iommu_gas_alloc_entry(domain, IOMMU_PGF_WAITOK);
+	r2 = iommu_gas_alloc_entry(domain, IOMMU_PGF_WAITOK);
+	TAILQ_INIT(&gc);
+
+	IOMMU_DOMAIN_LOCK(domain);
+
+	nentry = iommu_gas_remove_clip_left(domain, start, end, &r1);
+	RB_FOREACH_FROM(entry, iommu_gas_entries_tree, nentry) {
+		if (entry->start >= end)
+			break;
+		KASSERT(start <= entry->start,
+		    ("iommu_gas_remove entry (%#jx, %#jx) start %#jx",
+		    entry->start, entry->end, start));
+		if ((entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
+			continue;
+		iommu_gas_remove_unmap(domain, entry, &gc);
+	}
+	if (iommu_gas_remove_clip_right(domain, end, entry, r2)) {
+		iommu_gas_remove_unmap(domain, r2, &gc);
+		r2 = NULL;
+	}
+
+#ifdef INVARIANTS
+	RB_FOREACH(entry, iommu_gas_entries_tree, &domain->rb_root) {
+		if ((entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
+			continue;
+		KASSERT(entry->end <= start || entry->start >= end,
+		    ("iommu_gas_remove leftover entry (%#jx, %#jx) range "
+		    "(%#jx, %#jx)",
+		    entry->start, entry->end, start, end));
+	}
+#endif
+
+	IOMMU_DOMAIN_UNLOCK(domain);
+	if (r1 != NULL)
+		iommu_gas_free_entry(r1);
+	if (r2 != NULL)
+		iommu_gas_free_entry(r2);
+	iommu_domain_unload(domain, &gc, true);
+}
+
 int
 iommu_gas_map(struct iommu_domain *domain,
     const struct bus_dma_tag_common *common, iommu_gaddr_t size, int offset,
diff --git a/sys/dev/iommu/iommu_gas.h b/sys/dev/iommu/iommu_gas.h
index a9d0df5f272f..75a57ec0478f 100644
--- a/sys/dev/iommu/iommu_gas.h
+++ b/sys/dev/iommu/iommu_gas.h
@@ -50,6 +50,8 @@
 #define	IOMMU_MAP_ENTRY_MAP	0x0004	/* Busdma created, linked by
 					   dmamap_link */
 #define	IOMMU_MAP_ENTRY_UNMAPPED	0x0010	/* No backing pages */
+#define	IOMMU_MAP_ENTRY_REMOVING	0x0020	/* In process of removal by
+						   iommu_gas_remove() */
 #define	IOMMU_MAP_ENTRY_READ	0x1000	/* Read permitted */
 #define	IOMMU_MAP_ENTRY_WRITE	0x2000	/* Write permitted */
 #define	IOMMU_MAP_ENTRY_SNOOP	0x4000	/* Snoop */