diff --git a/sys/dev/iommu/iommu.h b/sys/dev/iommu/iommu.h --- a/sys/dev/iommu/iommu.h +++ b/sys/dev/iommu/iommu.h @@ -140,6 +140,7 @@ page table */ #define IOMMU_DOMAIN_RMRR 0x0020 /* Domain contains RMRR entry, cannot be turned off */ +#define IOMMU_DOMAIN_BUSY 0x0040 #define IOMMU_LOCK(unit) mtx_lock(&(unit)->lock) #define IOMMU_UNLOCK(unit) mtx_unlock(&(unit)->lock) @@ -171,6 +172,8 @@ 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 --- a/sys/dev/iommu/iommu_gas.c +++ b/sys/dev/iommu/iommu_gas.c @@ -600,6 +600,131 @@ IOMMU_DOMAIN_UNLOCK(domain); } +static void +iommu_gas_remove_clip_left(struct iommu_domain *domain, iommu_gaddr_t start, + iommu_gaddr_t end, struct iommu_map_entry **first, + struct iommu_map_entry **r) +{ + struct iommu_map_entry *entry, *nentry, 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); + MPASS(start <= entry->start || (entry->start <= start && + start <= entry->end)); + + if (entry->start < start && + (entry->flags & IOMMU_MAP_ENTRY_RMRR) == 0) { + nentry = *r; + *r = NULL; + *nentry = *entry; + nentry->start = entry->end = start; + RB_UPDATE_AUGMENT(entry, rb_entry); + iommu_gas_rb_insert(domain, nentry); + *first = nentry; + } else { + *first = entry; + } +} + +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) +{ + int error __diagused; + + entry->flags &= ~IOMMU_MAP_ENTRY_MAP; + if ((entry->flags & IOMMU_MAP_ENTRY_UNMAPPED) == 0) { + MPASS((entry->flags & IOMMU_MAP_ENTRY_PLACE) == 0); + error = domain->ops->unmap(domain, entry->start, + entry->end - entry->start, 0); + MPASS(error == 0); + + IOMMU_DOMAIN_UNLOCK(domain); + iommu_domain_unload_entry(entry, true, true); + IOMMU_DOMAIN_LOCK(domain); + } +} + +void +iommu_gas_remove(struct iommu_domain *domain, iommu_gaddr_t start, + iommu_gaddr_t size) +{ + struct iommu_map_entry *r1, *r2, *first, *nentry; + struct iommu_map_entry *entry __diagused; + 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); + + IOMMU_DOMAIN_LOCK(domain); + while ((domain->flags & IOMMU_DOMAIN_BUSY) != 0) + msleep(&domain->flags, &domain->lock, 0, "ioubsy", 0); + domain->flags |= IOMMU_DOMAIN_BUSY; + + iommu_gas_remove_clip_left(domain, start, end, &first, &r1); + nentry = first; + 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); + } + if (iommu_gas_remove_clip_right(domain, end, entry, r2)) { + iommu_gas_remove_unmap(domain, r2); + 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 + + domain->flags &= ~IOMMU_DOMAIN_BUSY; + wakeup(&domain->flags); + IOMMU_DOMAIN_UNLOCK(domain); + if (r1 != NULL) + iommu_gas_free_entry(r1); + if (r2 != NULL) + iommu_gas_free_entry(r2); +} + int iommu_gas_map(struct iommu_domain *domain, const struct bus_dma_tag_common *common, iommu_gaddr_t size, int offset, @@ -623,6 +748,8 @@ return (ENOMEM); a.entry = entry; IOMMU_DOMAIN_LOCK(domain); + while ((domain->flags & IOMMU_DOMAIN_BUSY) != 0) + msleep(&domain->flags, &domain->lock, 0, "iombsy", 0); error = iommu_gas_find_space(&a); if (error == ENOMEM) { IOMMU_DOMAIN_UNLOCK(domain);