Page MenuHomeFreeBSD

D35440.diff
No OneTemporary

D35440.diff

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
@@ -292,90 +292,109 @@
/*
* The interval [beg, end) is a free interval between two iommu_map_entries.
- * maxaddr is an upper bound on addresses that can be allocated. Try to
- * allocate space in the free interval, subject to the conditions expressed
- * by a, and return 'true' if and only if the allocation attempt succeeds.
+ * Addresses can be allocated only in the range [lbound, ubound). Try to
+ * allocate space in the free interval, subject to the conditions expressed by
+ * a, and return 'true' if and only if the allocation attempt succeeds.
*/
static bool
iommu_gas_match_one(struct iommu_gas_match_args *a, iommu_gaddr_t beg,
- iommu_gaddr_t end, iommu_gaddr_t maxaddr)
+ iommu_gaddr_t end, iommu_gaddr_t lbound, iommu_gaddr_t ubound)
{
- iommu_gaddr_t bs, start;
+ struct iommu_map_entry *entry;
+ iommu_gaddr_t first, size, start;
+ bool found __diagused;
+ int offset;
/*
* The prev->end is always aligned on the page size, which
* causes page alignment for the entry->start too.
*
- * A page sized gap is created between consecutive
- * allocations to ensure that out-of-bounds accesses fault.
+ * Create IOMMU_PAGE_SIZE gaps before, after new entry
+ * to ensure that out-of-bounds accesses fault.
*/
- a->entry->start = roundup2(beg + IOMMU_PAGE_SIZE,
- a->common->alignment);
- if (a->entry->start + a->offset + a->size > maxaddr)
+ beg = MAX(beg + IOMMU_PAGE_SIZE, lbound);
+ start = roundup2(beg, a->common->alignment);
+ if (start < beg)
return (false);
-
- /* IOMMU_PAGE_SIZE to create gap after new entry. */
- if (a->entry->start < beg + IOMMU_PAGE_SIZE ||
- a->entry->start + a->size + a->offset + IOMMU_PAGE_SIZE > end)
+ end = MIN(end - IOMMU_PAGE_SIZE, ubound);
+ offset = a->offset;
+ size = a->size;
+ if (start + offset + size > end)
return (false);
- /* No boundary crossing. */
- if (vm_addr_bound_ok(a->entry->start + a->offset, a->size,
- a->common->boundary))
- return (true);
-
- /*
- * The start + offset to start + offset + size region crosses
- * the boundary. Check if there is enough space after the
- * next boundary after the beg.
- */
- bs = rounddown2(a->entry->start + a->offset + a->common->boundary,
- a->common->boundary);
- start = roundup2(bs, a->common->alignment);
- /* IOMMU_PAGE_SIZE to create gap after new entry. */
- if (start + a->offset + a->size + IOMMU_PAGE_SIZE <= end &&
- start + a->offset + a->size <= maxaddr &&
- vm_addr_bound_ok(start + a->offset, a->size,
- a->common->boundary)) {
- a->entry->start = start;
- return (true);
- }
-
- /*
- * Not enough space to align at the requested boundary, or
- * boundary is smaller than the size, but allowed to split.
- * We already checked that start + size does not overlap maxaddr.
- *
- * XXXKIB. It is possible that bs is exactly at the start of
- * the next entry, then we do not have gap. Ignore for now.
- */
- if ((a->gas_flags & IOMMU_MF_CANSPLIT) != 0) {
- a->size = bs - a->entry->start - a->offset;
- return (true);
+ /* Check for and try to skip past boundary crossing. */
+ if (!vm_addr_bound_ok(start + offset, size, a->common->boundary)) {
+ /*
+ * The start + offset to start + offset + size region crosses
+ * the boundary. Check if there is enough space after the next
+ * boundary after the beg.
+ */
+ first = start;
+ beg = roundup2(start + offset + 1, a->common->boundary);
+ start = roundup2(beg, a->common->alignment);
+
+ if (start + offset + size > end ||
+ !vm_addr_bound_ok(start + offset, size,
+ a->common->boundary)) {
+ /*
+ * Not enough space to align at the requested boundary,
+ * or boundary is smaller than the size, but allowed to
+ * split. We already checked that start + size does not
+ * overlap ubound.
+ *
+ * XXXKIB. It is possible that beg is exactly at the
+ * start of the next entry, then we do not have gap.
+ * Ignore for now.
+ */
+ if ((a->gas_flags & IOMMU_MF_CANSPLIT) == 0)
+ return (false);
+ size = beg - first - offset;
+ start = first;
+ }
}
-
- return (false);
+ entry = a->entry;
+ entry->start = start;
+ entry->end = start + roundup2(size + offset, IOMMU_PAGE_SIZE);
+ entry->flags = IOMMU_MAP_ENTRY_MAP;
+ found = iommu_gas_rb_insert(a->domain, entry);
+ KASSERT(found, ("found dup %p start %jx size %jx",
+ a->domain, (uintmax_t)start, (uintmax_t)size));
+ return (true);
}
-static void
-iommu_gas_match_insert(struct iommu_gas_match_args *a)
+/* Find the next entry that might abut a big-enough range. */
+static struct iommu_map_entry *
+iommu_gas_next(struct iommu_map_entry *curr, iommu_gaddr_t min_free)
{
- bool found __diagused;
-
- a->entry->end = a->entry->start +
- roundup2(a->size + a->offset, IOMMU_PAGE_SIZE);
-
- found = iommu_gas_rb_insert(a->domain, a->entry);
- KASSERT(found, ("found dup %p start %jx size %jx",
- a->domain, (uintmax_t)a->entry->start, (uintmax_t)a->size));
- a->entry->flags = IOMMU_MAP_ENTRY_MAP;
+ struct iommu_map_entry *next;
+
+ if ((next = RB_RIGHT(curr, rb_entry)) != NULL &&
+ next->free_down >= min_free) {
+ /* Find next entry in right subtree. */
+ do
+ curr = next;
+ while ((next = RB_LEFT(curr, rb_entry)) != NULL &&
+ next->free_down >= min_free);
+ } else {
+ /* Find next entry in a left-parent ancestor. */
+ while ((next = RB_PARENT(curr, rb_entry)) != NULL &&
+ curr == RB_RIGHT(next, rb_entry))
+ curr = next;
+ curr = next;
+ }
+ return (curr);
}
static int
-iommu_gas_lowermatch(struct iommu_gas_match_args *a, struct iommu_map_entry *entry)
+iommu_gas_find_space(struct iommu_gas_match_args *a)
{
- struct iommu_map_entry *first;
- iommu_gaddr_t min_free;
+ struct iommu_domain *domain;
+ struct iommu_map_entry *curr, *first;
+ iommu_gaddr_t addr, min_free;
+
+ IOMMU_DOMAIN_ASSERT_LOCKED(a->domain);
+ KASSERT(a->entry->flags == 0,
+ ("dirty entry %p %p", a->domain, a->entry));
/*
* If the subtree doesn't have free space for the requested allocation
@@ -384,121 +403,74 @@
min_free = 2 * IOMMU_PAGE_SIZE +
roundup2(a->size + a->offset, IOMMU_PAGE_SIZE);
- /* Find the first entry that could abut a big-enough range. */
+ /*
+ * Find the first entry in the lower region that could abut a big-enough
+ * range.
+ */
+ curr = RB_ROOT(&a->domain->rb_root);
first = NULL;
- while (entry != NULL && entry->free_down >= min_free) {
- first = entry;
- entry = RB_LEFT(entry, rb_entry);
+ while (curr != NULL && curr->free_down >= min_free) {
+ first = curr;
+ curr = RB_LEFT(curr, rb_entry);
}
/*
* Walk the big-enough ranges until one satisfies alignment
* requirements, or violates lowaddr address requirement.
*/
- entry = first;
- while (entry != NULL) {
- if ((first = RB_LEFT(entry, rb_entry)) != NULL &&
- iommu_gas_match_one(a, first->last, entry->start,
- a->common->lowaddr)) {
- iommu_gas_match_insert(a);
+ addr = a->common->lowaddr + 1;
+ for (curr = first; curr != NULL;
+ curr = iommu_gas_next(curr, min_free)) {
+ if ((first = RB_LEFT(curr, rb_entry)) != NULL &&
+ iommu_gas_match_one(a, first->last, curr->start,
+ 0, addr))
return (0);
- }
- if (entry->end >= a->common->lowaddr) {
- /* All remaining ranges >= lowaddr */
+ if (curr->end >= addr) {
+ /* All remaining ranges >= addr */
break;
}
- if ((first = RB_RIGHT(entry, rb_entry)) != NULL &&
- iommu_gas_match_one(a, entry->end, first->first,
- a->common->lowaddr)) {
- iommu_gas_match_insert(a);
+ if ((first = RB_RIGHT(curr, rb_entry)) != NULL &&
+ iommu_gas_match_one(a, curr->end, first->first,
+ 0, addr))
return (0);
- }
- /* Find the next entry that might abut a big-enough range. */
- if (first != NULL && first->free_down >= min_free) {
- /* Find next entry in right subtree. */
- do
- entry = first;
- while ((first = RB_LEFT(entry, rb_entry)) != NULL &&
- first->free_down >= min_free);
- } else {
- /* Find next entry in a left-parent ancestor. */
- while ((first = RB_PARENT(entry, rb_entry)) != NULL &&
- entry == RB_RIGHT(first, rb_entry))
- entry = first;
- entry = first;
- }
}
- return (ENOMEM);
-}
-
-static int
-iommu_gas_uppermatch(struct iommu_gas_match_args *a, struct iommu_map_entry *entry)
-{
- struct iommu_map_entry *child;
/*
- * If the subtree doesn't have free space for the requested allocation
- * plus two guard pages, give up.
+ * To resume the search at the start of the upper region, first climb to
+ * the nearest ancestor that spans highaddr. Then find the last entry
+ * before highaddr that could abut a big-enough range.
*/
- if (entry->free_down < 2 * IOMMU_PAGE_SIZE +
- roundup2(a->size + a->offset, IOMMU_PAGE_SIZE))
- return (ENOMEM);
- if (entry->last < a->common->highaddr)
- return (ENOMEM);
- child = RB_LEFT(entry, rb_entry);
- if (child != NULL && 0 == iommu_gas_uppermatch(a, child))
- return (0);
- if (child != NULL && child->last >= a->common->highaddr &&
- iommu_gas_match_one(a, child->last, entry->start,
- a->domain->end)) {
- iommu_gas_match_insert(a);
- return (0);
- }
- child = RB_RIGHT(entry, rb_entry);
- if (child != NULL && entry->end >= a->common->highaddr &&
- iommu_gas_match_one(a, entry->end, child->first,
- a->domain->end)) {
- iommu_gas_match_insert(a);
- return (0);
+ addr = a->common->highaddr;
+ while (curr != NULL && curr->last < addr)
+ curr = RB_PARENT(curr, rb_entry);
+ first = NULL;
+ while (curr != NULL && curr->free_down >= min_free) {
+ if (addr < curr->end)
+ curr = RB_LEFT(curr, rb_entry);
+ else {
+ first = curr;
+ curr = RB_RIGHT(curr, rb_entry);
+ }
}
- if (child != NULL && 0 == iommu_gas_uppermatch(a, child))
- return (0);
- return (ENOMEM);
-}
-
-static int
-iommu_gas_find_space(struct iommu_domain *domain,
- const struct bus_dma_tag_common *common, iommu_gaddr_t size,
- int offset, u_int flags, struct iommu_map_entry *entry)
-{
- struct iommu_gas_match_args a;
- int error;
-
- IOMMU_DOMAIN_ASSERT_LOCKED(domain);
- KASSERT(entry->flags == 0, ("dirty entry %p %p", domain, entry));
- a.domain = domain;
- a.size = size;
- a.offset = offset;
- a.common = common;
- a.gas_flags = flags;
- a.entry = entry;
-
- /* Handle lower region. */
- if (common->lowaddr > 0) {
- error = iommu_gas_lowermatch(&a, RB_ROOT(&domain->rb_root));
- if (error == 0)
+ /*
+ * Walk the remaining big-enough ranges until one satisfies alignment
+ * requirements.
+ */
+ domain = a->domain;
+ for (curr = first; curr != NULL;
+ curr = iommu_gas_next(curr, min_free)) {
+ if ((first = RB_LEFT(curr, rb_entry)) != NULL &&
+ iommu_gas_match_one(a, first->last, curr->start,
+ addr + 1, domain->end))
+ return (0);
+ if ((first = RB_RIGHT(curr, rb_entry)) != NULL &&
+ iommu_gas_match_one(a, curr->end, first->first,
+ addr + 1, domain->end))
return (0);
- KASSERT(error == ENOMEM,
- ("error %d from iommu_gas_lowermatch", error));
}
- /* Handle upper region. */
- if (common->highaddr >= domain->end)
- return (ENOMEM);
- error = iommu_gas_uppermatch(&a, RB_ROOT(&domain->rb_root));
- KASSERT(error == 0 || error == ENOMEM,
- ("error %d from iommu_gas_uppermatch", error));
- return (error);
+
+ return (ENOMEM);
}
static int
@@ -627,19 +599,25 @@
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)
{
+ struct iommu_gas_match_args a;
struct iommu_map_entry *entry;
int error;
KASSERT((flags & ~(IOMMU_MF_CANWAIT | IOMMU_MF_CANSPLIT)) == 0,
("invalid flags 0x%x", flags));
+ a.domain = domain;
+ a.size = size;
+ a.offset = offset;
+ a.common = common;
+ a.gas_flags = flags;
entry = iommu_gas_alloc_entry(domain,
(flags & IOMMU_MF_CANWAIT) != 0 ? IOMMU_PGF_WAITOK : 0);
if (entry == NULL)
return (ENOMEM);
+ a.entry = entry;
IOMMU_DOMAIN_LOCK(domain);
- error = iommu_gas_find_space(domain, common, size, offset, flags,
- entry);
+ error = iommu_gas_find_space(&a);
if (error == ENOMEM) {
IOMMU_DOMAIN_UNLOCK(domain);
iommu_gas_free_entry(domain, entry);

File Metadata

Mime Type
text/plain
Expires
Tue, Sep 24, 10:33 AM (22 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
12656132
Default Alt Text
D35440.diff (12 KB)

Event Timeline