aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Belousov <kib@FreeBSD.org>2022-07-31 08:43:43 +0000
committerKonstantin Belousov <kib@FreeBSD.org>2022-08-22 13:37:09 +0000
commitd4f7acfad7322852a85cded19af7ebb737119993 (patch)
treeddf1b6714505ea9cd674d42f90da5e8151f3f2cc
parent55fa60782f3e0f63ea572e80b269acf6ad3dc8e3 (diff)
downloadsrc-d4f7acfad7322852a85cded19af7ebb737119993.tar.gz
src-d4f7acfad7322852a85cded19af7ebb737119993.zip
iommu_gas: add iommu_gas_remove()
(cherry picked from commit c9e4d25052065d1e0cbdb3a0eea3f21c9225d1d1)
-rw-r--r--sys/dev/iommu/iommu.h2
-rw-r--r--sys/dev/iommu/iommu_gas.c118
-rw-r--r--sys/dev/iommu/iommu_gas.h2
3 files changed, 122 insertions, 0 deletions
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 */