From 245de71cf80d02d4ff02fcf352fb7f056c686821 Mon Sep 17 00:00:00 2001 From: kib Date: Wed, 8 Apr 2015 01:55:22 +0000 Subject: Account for the offset of the page run when allocating the dmar_map_entry. Non-zero offset both increases the required mapping size, which is handled in dmar_bus_dmamap_load_something1(), and makes it possible that allocated range crosses boundary, which needs a check in dmar_gas_match_one(). Reported and tested by: jimharris Sponsored by: The FreeBSD Foundation MFC after: 1 week --- sys/x86/iommu/busdma_dmar.c | 18 ++++++++++++------ sys/x86/iommu/intel_dmar.h | 2 +- sys/x86/iommu/intel_gas.c | 33 +++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 21 deletions(-) (limited to 'sys/x86/iommu') diff --git a/sys/x86/iommu/busdma_dmar.c b/sys/x86/iommu/busdma_dmar.c index 3f86c78..96b0bce 100644 --- a/sys/x86/iommu/busdma_dmar.c +++ b/sys/x86/iommu/busdma_dmar.c @@ -462,6 +462,7 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, bus_size_t buflen1; int error, idx, gas_flags, seg; + KASSERT(offset < DMAR_PAGE_SIZE, ("offset %d", offset)); if (segs == NULL) segs = tag->segments; ctx = tag->ctx; @@ -476,7 +477,6 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, } buflen1 = buflen > tag->common.maxsegsz ? tag->common.maxsegsz : buflen; - buflen -= buflen1; size = round_page(offset + buflen1); /* @@ -487,7 +487,7 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, if (seg + 1 < tag->common.nsegments) gas_flags |= DMAR_GM_CANSPLIT; - error = dmar_gas_map(ctx, &tag->common, size, + error = dmar_gas_map(ctx, &tag->common, size, offset, DMAR_MAP_ENTRY_READ | DMAR_MAP_ENTRY_WRITE, gas_flags, ma + idx, &entry); if (error != 0) @@ -506,6 +506,10 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, (uintmax_t)size, (uintmax_t)entry->start, (uintmax_t)entry->end)); } + if (offset + buflen1 > size) + buflen1 = size - offset; + if (buflen1 > tag->common.maxsegsz) + buflen1 = tag->common.maxsegsz; KASSERT(((entry->start + offset) & (tag->common.alignment - 1)) == 0, @@ -519,15 +523,16 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, (uintmax_t)entry->start, (uintmax_t)entry->end, (uintmax_t)tag->common.lowaddr, (uintmax_t)tag->common.highaddr)); - KASSERT(dmar_test_boundary(entry->start, entry->end - - entry->start, tag->common.boundary), + KASSERT(dmar_test_boundary(entry->start + offset, buflen1, + tag->common.boundary), ("boundary failed: ctx %p start 0x%jx end 0x%jx " "boundary 0x%jx", ctx, (uintmax_t)entry->start, (uintmax_t)entry->end, (uintmax_t)tag->common.boundary)); KASSERT(buflen1 <= tag->common.maxsegsz, ("segment too large: ctx %p start 0x%jx end 0x%jx " - "maxsegsz 0x%jx", ctx, (uintmax_t)entry->start, - (uintmax_t)entry->end, (uintmax_t)tag->common.maxsegsz)); + "buflen1 0x%jx maxsegsz 0x%jx", ctx, + (uintmax_t)entry->start, (uintmax_t)entry->end, + (uintmax_t)buflen1, (uintmax_t)tag->common.maxsegsz)); DMAR_CTX_LOCK(ctx); TAILQ_INSERT_TAIL(&map->map_entries, entry, dmamap_link); @@ -541,6 +546,7 @@ dmar_bus_dmamap_load_something1(struct bus_dma_tag_dmar *tag, idx += OFF_TO_IDX(trunc_page(offset + buflen1)); offset += buflen1; offset &= DMAR_PAGE_MASK; + buflen -= buflen1; } if (error == 0) *segp = seg; diff --git a/sys/x86/iommu/intel_dmar.h b/sys/x86/iommu/intel_dmar.h index 2865ab5..401ff2f 100644 --- a/sys/x86/iommu/intel_dmar.h +++ b/sys/x86/iommu/intel_dmar.h @@ -308,7 +308,7 @@ struct dmar_map_entry *dmar_gas_alloc_entry(struct dmar_ctx *ctx, u_int flags); void dmar_gas_free_entry(struct dmar_ctx *ctx, struct dmar_map_entry *entry); void dmar_gas_free_space(struct dmar_ctx *ctx, struct dmar_map_entry *entry); int dmar_gas_map(struct dmar_ctx *ctx, const struct bus_dma_tag_common *common, - dmar_gaddr_t size, u_int eflags, u_int flags, vm_page_t *ma, + dmar_gaddr_t size, int offset, u_int eflags, u_int flags, vm_page_t *ma, struct dmar_map_entry **res); void dmar_gas_free_region(struct dmar_ctx *ctx, struct dmar_map_entry *entry); int dmar_gas_map_region(struct dmar_ctx *ctx, struct dmar_map_entry *entry, diff --git a/sys/x86/iommu/intel_gas.c b/sys/x86/iommu/intel_gas.c index d91da97..5a7a730 100644 --- a/sys/x86/iommu/intel_gas.c +++ b/sys/x86/iommu/intel_gas.c @@ -294,6 +294,7 @@ dmar_gas_fini_ctx(struct dmar_ctx *ctx) struct dmar_gas_match_args { struct dmar_ctx *ctx; dmar_gaddr_t size; + int offset; const struct bus_dma_tag_common *common; u_int gas_flags; struct dmar_map_entry *entry; @@ -310,25 +311,28 @@ dmar_gas_match_one(struct dmar_gas_match_args *a, struct dmar_map_entry *prev, /* DMAR_PAGE_SIZE to create gap after new entry. */ if (a->entry->start < prev->end + DMAR_PAGE_SIZE || - a->entry->start + a->size + DMAR_PAGE_SIZE > prev->end + - prev->free_after) + a->entry->start + a->size + a->offset + DMAR_PAGE_SIZE > + prev->end + prev->free_after) return (false); /* No boundary crossing. */ - if (dmar_test_boundary(a->entry->start, a->size, a->common->boundary)) + if (dmar_test_boundary(a->entry->start + a->offset, a->size, + a->common->boundary)) return (true); /* - * The start to start + size region crosses the boundary. - * Check if there is enough space after the next boundary - * after the prev->end. + * The start + offset to start + offset + size region crosses + * the boundary. Check if there is enough space after the + * next boundary after the prev->end. */ - bs = (a->entry->start + a->common->boundary) & ~(a->common->boundary - - 1); + bs = (a->entry->start + a->offset + a->common->boundary) & + ~(a->common->boundary - 1); start = roundup2(bs, a->common->alignment); /* DMAR_PAGE_SIZE to create gap after new entry. */ - if (start + a->size + DMAR_PAGE_SIZE <= prev->end + prev->free_after && - start + a->size <= end && dmar_test_boundary(start, a->size, + if (start + a->offset + a->size + DMAR_PAGE_SIZE <= + prev->end + prev->free_after && + start + a->offset + a->size <= end && + dmar_test_boundary(start + a->offset, a->size, a->common->boundary)) { a->entry->start = start; return (true); @@ -410,7 +414,7 @@ dmar_gas_lowermatch(struct dmar_gas_match_args *a, struct dmar_map_entry *prev) return (0); } } - if (prev->free_down < a->size + DMAR_PAGE_SIZE) + if (prev->free_down < a->size + a->offset + DMAR_PAGE_SIZE) return (ENOMEM); l = RB_LEFT(prev, rb_entry); if (l != NULL) { @@ -466,7 +470,7 @@ dmar_gas_uppermatch(struct dmar_gas_match_args *a) static int dmar_gas_find_space(struct dmar_ctx *ctx, const struct bus_dma_tag_common *common, dmar_gaddr_t size, - u_int flags, struct dmar_map_entry *entry) + int offset, u_int flags, struct dmar_map_entry *entry) { struct dmar_gas_match_args a; int error; @@ -477,6 +481,7 @@ dmar_gas_find_space(struct dmar_ctx *ctx, a.ctx = ctx; a.size = size; + a.offset = offset; a.common = common; a.gas_flags = flags; a.entry = entry; @@ -618,7 +623,7 @@ dmar_gas_free_region(struct dmar_ctx *ctx, struct dmar_map_entry *entry) int dmar_gas_map(struct dmar_ctx *ctx, const struct bus_dma_tag_common *common, - dmar_gaddr_t size, u_int eflags, u_int flags, vm_page_t *ma, + dmar_gaddr_t size, int offset, u_int eflags, u_int flags, vm_page_t *ma, struct dmar_map_entry **res) { struct dmar_map_entry *entry; @@ -632,7 +637,7 @@ dmar_gas_map(struct dmar_ctx *ctx, const struct bus_dma_tag_common *common, if (entry == NULL) return (ENOMEM); DMAR_CTX_LOCK(ctx); - error = dmar_gas_find_space(ctx, common, size, flags, entry); + error = dmar_gas_find_space(ctx, common, size, offset, flags, entry); if (error == ENOMEM) { DMAR_CTX_UNLOCK(ctx); dmar_gas_free_entry(ctx, entry); -- cgit v1.1