aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/iommu
diff options
context:
space:
mode:
authorRyan Libby <rlibby@FreeBSD.org>2020-12-09 18:43:58 +0000
committerRyan Libby <rlibby@FreeBSD.org>2020-12-09 18:43:58 +0000
commitee47a12a490f89226ae736f92d8578ec1ca3b157 (patch)
treeadb0781217a3a99c4be3504b2864d75660f92e2f /sys/dev/iommu
parent6813f2420b236f852d18f1a582c77d17ed15d45f (diff)
downloadsrc-ee47a12a490f89226ae736f92d8578ec1ca3b157.tar.gz
src-ee47a12a490f89226ae736f92d8578ec1ca3b157.zip
dmar: reserve memory windows of PCIe root port
PCI memory address space is shared between memory-mapped devices (MMIO) and host memory (which may be remapped by an IOMMU). Device accesses to an address within a memory aperture in a PCIe root port will be treated as peer-to-peer and not forwarded to an IOMMU. To avoid this, reserve the address space of the root port's memory apertures in the address space used by the IOMMU for remapping. Reviewed by: kib, tychon Discussed with: Anton Rang <rang@acm.org> Tested by: tychon Sponsored by: Dell EMC Isilon Differential Revision: https://reviews.freebsd.org/D27503
Notes
Notes: svn path=/head/; revision=368490
Diffstat (limited to 'sys/dev/iommu')
-rw-r--r--sys/dev/iommu/iommu.h2
-rw-r--r--sys/dev/iommu/iommu_gas.c72
2 files changed, 69 insertions, 5 deletions
diff --git a/sys/dev/iommu/iommu.h b/sys/dev/iommu/iommu.h
index 388a8cb3b2ce..ba592ea08ff1 100644
--- a/sys/dev/iommu/iommu.h
+++ b/sys/dev/iommu/iommu.h
@@ -199,6 +199,8 @@ int iommu_gas_map_region(struct iommu_domain *domain,
struct iommu_map_entry *entry, u_int eflags, u_int flags, vm_page_t *ma);
int iommu_gas_reserve_region(struct iommu_domain *domain, iommu_gaddr_t start,
iommu_gaddr_t end, struct iommu_map_entry **entry0);
+int iommu_gas_reserve_region_extend(struct iommu_domain *domain,
+ iommu_gaddr_t start, iommu_gaddr_t end);
void iommu_set_buswide_ctx(struct iommu_unit *unit, u_int busno);
bool iommu_is_buswide_ctx(struct iommu_unit *unit, u_int busno);
diff --git a/sys/dev/iommu/iommu_gas.c b/sys/dev/iommu/iommu_gas.c
index 841bb3c83da5..431c0c195c35 100644
--- a/sys/dev/iommu/iommu_gas.c
+++ b/sys/dev/iommu/iommu_gas.c
@@ -677,6 +677,22 @@ iommu_gas_map_region(struct iommu_domain *domain, struct iommu_map_entry *entry,
return (0);
}
+static int
+iommu_gas_reserve_region_locked(struct iommu_domain *domain,
+ iommu_gaddr_t start, iommu_gaddr_t end, struct iommu_map_entry *entry)
+{
+ int error;
+
+ IOMMU_DOMAIN_ASSERT_LOCKED(domain);
+
+ entry->start = start;
+ entry->end = end;
+ error = iommu_gas_alloc_region(domain, entry, IOMMU_MF_CANWAIT);
+ if (error == 0)
+ entry->flags |= IOMMU_MAP_ENTRY_UNMAPPED;
+ return (error);
+}
+
int
iommu_gas_reserve_region(struct iommu_domain *domain, iommu_gaddr_t start,
iommu_gaddr_t end, struct iommu_map_entry **entry0)
@@ -685,12 +701,8 @@ iommu_gas_reserve_region(struct iommu_domain *domain, iommu_gaddr_t start,
int error;
entry = iommu_gas_alloc_entry(domain, IOMMU_PGF_WAITOK);
- entry->start = start;
- entry->end = end;
IOMMU_DOMAIN_LOCK(domain);
- error = iommu_gas_alloc_region(domain, entry, IOMMU_MF_CANWAIT);
- if (error == 0)
- entry->flags |= IOMMU_MAP_ENTRY_UNMAPPED;
+ error = iommu_gas_reserve_region_locked(domain, start, end, entry);
IOMMU_DOMAIN_UNLOCK(domain);
if (error != 0)
iommu_gas_free_entry(domain, entry);
@@ -699,6 +711,56 @@ iommu_gas_reserve_region(struct iommu_domain *domain, iommu_gaddr_t start,
return (error);
}
+/*
+ * As in iommu_gas_reserve_region, reserve [start, end), but allow for existing
+ * entries.
+ */
+int
+iommu_gas_reserve_region_extend(struct iommu_domain *domain,
+ iommu_gaddr_t start, iommu_gaddr_t end)
+{
+ struct iommu_map_entry *entry, *next, *prev, key = {};
+ iommu_gaddr_t entry_start, entry_end;
+ int error;
+
+ error = 0;
+ entry = NULL;
+ end = ummin(end, domain->end);
+ while (start < end) {
+ /* Preallocate an entry. */
+ if (entry == NULL)
+ entry = iommu_gas_alloc_entry(domain,
+ IOMMU_PGF_WAITOK);
+ /* Calculate the free region from here to the next entry. */
+ key.start = key.end = start;
+ IOMMU_DOMAIN_LOCK(domain);
+ next = RB_NFIND(iommu_gas_entries_tree, &domain->rb_root, &key);
+ KASSERT(next != NULL, ("domain %p with end %#jx has no entry "
+ "after %#jx", domain, (uintmax_t)domain->end,
+ (uintmax_t)start));
+ entry_end = ummin(end, next->start);
+ prev = RB_PREV(iommu_gas_entries_tree, &domain->rb_root, next);
+ if (prev != NULL)
+ entry_start = ummax(start, prev->end);
+ else
+ entry_start = start;
+ start = next->end;
+ /* Reserve the region if non-empty. */
+ if (entry_start != entry_end) {
+ error = iommu_gas_reserve_region_locked(domain,
+ entry_start, entry_end, entry);
+ if (error != 0)
+ break;
+ entry = NULL;
+ }
+ IOMMU_DOMAIN_UNLOCK(domain);
+ }
+ /* Release a preallocated entry if it was not used. */
+ if (entry != NULL)
+ iommu_gas_free_entry(domain, entry);
+ return (error);
+}
+
struct iommu_map_entry *
iommu_map_alloc_entry(struct iommu_domain *domain, u_int flags)
{