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 @@ -171,6 +171,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,199 @@ IOMMU_DOMAIN_UNLOCK(domain); } +static struct iommu_map_entry * +iommu_gas_find_left_clip_entry(struct iommu_domain *domain, iommu_gaddr_t start) +{ + struct iommu_map_entry *entry, *nentry, fentry; + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + + fentry.start = start; + fentry.end = start; + entry = RB_NFIND(iommu_gas_entries_tree, &domain->rb_root, &fentry); + if (entry == NULL) + entry = RB_MIN(iommu_gas_entries_tree, &domain->rb_root); + for (;;) { + if (entry->end <= start) { + entry = RB_NEXT(iommu_gas_entries_tree, + &domain->rb_root, entry); + if (entry == NULL || start <= entry->start) + break; + } else if (start < entry->start) { + nentry = RB_PREV(iommu_gas_entries_tree, + &domain->rb_root, entry); + if (nentry == NULL || nentry->end <= start) + break; + entry = nentry; + } else + break; + } + return (entry); +} + +static struct iommu_map_entry * +iommu_gas_find_right_clip_entry(struct iommu_domain *domain, iommu_gaddr_t end) +{ + struct iommu_map_entry *entry, *nentry, fentry; + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + + fentry.start = end; + fentry.end = end; + entry = RB_NFIND(iommu_gas_entries_tree, &domain->rb_root, &fentry); + if (entry == NULL) + entry = RB_MAX(iommu_gas_entries_tree, &domain->rb_root); + MPASS(entry != NULL); + for (;;) { + if (end < entry->end) { + nentry = RB_PREV(iommu_gas_entries_tree, + &domain->rb_root, entry); + if (nentry == NULL || nentry->end <= end) + break; + entry = nentry; + } else if (entry->end < end) { + entry = RB_NEXT(iommu_gas_entries_tree, + &domain->rb_root, entry); + if (entry == NULL || end <= nentry->end) + break; + } else + break; + } + return (entry); +} + +static struct iommu_map_entry * +iommu_gas_clip(struct iommu_domain *domain, struct iommu_map_entry *entry, + iommu_gaddr_t clip, struct iommu_map_entry **r) +{ + struct iommu_map_entry *nentry; + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + if (clip <= entry->start) + return (entry); + nentry = *r; + *r = NULL; + iommu_gas_rb_remove(domain, entry); + *nentry = *entry; + nentry->start = entry->end = clip; + iommu_gas_rb_insert(domain, entry); + iommu_gas_rb_insert(domain, nentry); + return (nentry); +} + +static struct iommu_map_entry * +iommu_gas_left_clip(struct iommu_domain *domain, struct iommu_map_entry *entry, + iommu_gaddr_t start, struct iommu_map_entry **r) +{ + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + KASSERT(start <= entry->end, + ("iommu_gas_left_clip entry (%#jx, %#jx) start %#jx", + entry->start, entry->end, start)); +#ifdef INVARIANTS + if (start <= entry->start) { + struct iommu_map_entry *aentry; + aentry = RB_PREV(iommu_gas_entries_tree, &domain->rb_root, + entry); + if (aentry != NULL) { + KASSERT(aentry->end <= start, + ("iommu_gas_left_clip entry (%#jx, %#jx) next " + "(%#jx, %#jx) start %#jx", entry->start, entry->end, + aentry->start, aentry->end, start)); + } + } +#endif + + return (iommu_gas_clip(domain, entry, start, r)); +} + +static struct iommu_map_entry * +iommu_gas_right_clip(struct iommu_domain *domain, struct iommu_map_entry *entry, + iommu_gaddr_t end, struct iommu_map_entry **r) +{ + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + KASSERT(end <= entry->end, + ("iommu_gas_right_clip entry (%#jx, %#jx) trim %#jx", + entry->start, entry->end, end)); +#ifdef INVARIANTS + if (end <= entry->start) { + struct iommu_map_entry *aentry; + aentry = RB_PREV(iommu_gas_entries_tree, &domain->rb_root, + entry); + if (aentry != NULL) { + KASSERT(aentry->end <= end, + ("iommu_gas_left_clip entry (%#jx, %#jx) next " + "(%#jx, %#jx) start %#jx", entry->start, entry->end, + aentry->start, aentry->end, end)); + } + } +#endif + + return (iommu_gas_clip(domain, entry, end, r)); +} + +static void +iommu_gas_remove_locked(struct iommu_domain *domain, iommu_gaddr_t start, + iommu_gaddr_t end, struct iommu_map_entry **r1, struct iommu_map_entry **r2) +{ + struct iommu_map_entry *entry, *nentry, *lentry, *rentry; + + IOMMU_DOMAIN_ASSERT_LOCKED(domain); + lentry = iommu_gas_find_left_clip_entry(domain, start); + entry = iommu_gas_left_clip(domain, lentry, start, r1); + rentry = iommu_gas_find_right_clip_entry(domain, end); + (void)iommu_gas_right_clip(domain, rentry, end, r2); + for (; entry != NULL && entry->start < end; entry = nentry) { + KASSERT(start <= entry->start, + ("iommu_gas_remove entry (%#jx, %#jx) start %#jx", + entry->start, entry->end, start)); + KASSERT(entry->end <= end, + ("iommu_gas_remove entry (%#jx, %#jx) end %#jx", + entry->start, entry->end, end)); + nentry = RB_NEXT(iommu_gas_entries_tree, &domain->rb_root, + entry); + if ((entry->flags & IOMMU_MAP_ENTRY_RMRR) == 0) { + iommu_gas_free_space(entry); + domain->ops->unmap(domain, entry->start, + entry->end - entry->start, IOMMU_PGF_WAITOK); + if ((entry->flags & IOMMU_MAP_ENTRY_PLACE) == 0) + iommu_gas_free_entry(entry); + } + } +#ifdef INVARIANTS + for (entry = RB_MIN(iommu_gas_entries_tree, &domain->rb_root); + entry != NULL; entry = RB_NEXT(iommu_gas_entries_tree, + &domain->rb_root, entry)) { + 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 +} + +void +iommu_gas_remove(struct iommu_domain *domain, iommu_gaddr_t start, + iommu_gaddr_t size) +{ + struct iommu_map_entry *r1, *r2; + 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); + iommu_gas_remove_locked(domain, start, end, &r1, &r2); + 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,