summaryrefslogtreecommitdiffstats
path: root/sys/x86/iommu
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2015-04-08 01:55:22 +0000
committerkib <kib@FreeBSD.org>2015-04-08 01:55:22 +0000
commit245de71cf80d02d4ff02fcf352fb7f056c686821 (patch)
tree7a8638cf4f1ecc494c7173f4af3f2eca488c980d /sys/x86/iommu
parentba0c5cf16d2e445113872a63666ba997a9af872c (diff)
downloadFreeBSD-src-245de71cf80d02d4ff02fcf352fb7f056c686821.zip
FreeBSD-src-245de71cf80d02d4ff02fcf352fb7f056c686821.tar.gz
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
Diffstat (limited to 'sys/x86/iommu')
-rw-r--r--sys/x86/iommu/busdma_dmar.c18
-rw-r--r--sys/x86/iommu/intel_dmar.h2
-rw-r--r--sys/x86/iommu/intel_gas.c33
3 files changed, 32 insertions, 21 deletions
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);
OpenPOWER on IntegriCloud