diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/sparc64/include/bus_private.h | 24 | ||||
-rw-r--r-- | sys/sparc64/include/iommuvar.h | 4 | ||||
-rw-r--r-- | sys/sparc64/pci/psycho.c | 30 | ||||
-rw-r--r-- | sys/sparc64/sbus/sbus.c | 28 | ||||
-rw-r--r-- | sys/sparc64/sparc64/bus_machdep.c | 8 | ||||
-rw-r--r-- | sys/sparc64/sparc64/iommu.c | 505 |
6 files changed, 466 insertions, 133 deletions
diff --git a/sys/sparc64/include/bus_private.h b/sys/sparc64/include/bus_private.h index 715122c..4b68ffd 100644 --- a/sys/sparc64/include/bus_private.h +++ b/sys/sparc64/include/bus_private.h @@ -35,15 +35,23 @@ #define BUS_DMAMAP_NSEGS ((BUS_SPACE_MAXSIZE / PAGE_SIZE) + 1) +struct bus_dmamap_res { + struct resource *dr_res; + bus_size_t dr_used; + SLIST_ENTRY(bus_dmamap_res) dr_link; +}; + struct bus_dmamap { - bus_dma_tag_t dmat; - void *buf; /* unmapped buffer pointer */ - bus_size_t buflen; /* unmapped buffer length */ - bus_addr_t start; /* start of mapped region */ - struct resource *res; /* associated resource */ - bus_size_t dvmaresv; /* reseved DVMA memory */ - STAILQ_ENTRY(bus_dmamap) maplruq; - int onq; + TAILQ_ENTRY(bus_dmamap) dm_maplruq; + SLIST_HEAD(, bus_dmamap_res) dm_reslist; + int dm_onq; + int dm_loaded; }; +static __inline void +sparc64_dmamap_init(struct bus_dmamap *m) +{ + SLIST_INIT(&m->dm_reslist); +} + #endif /* !_MACHINE_BUS_PRIVATE_H_ */ diff --git a/sys/sparc64/include/iommuvar.h b/sys/sparc64/include/iommuvar.h index 32eb432..1052ba3 100644 --- a/sys/sparc64/include/iommuvar.h +++ b/sys/sparc64/include/iommuvar.h @@ -87,6 +87,10 @@ int iommu_dvmamap_destroy(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, bus_dmamap_t); int iommu_dvmamap_load(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, bus_dmamap_t, void *, bus_size_t, bus_dmamap_callback_t *, void *, int); +int iommu_dvmamap_load_mbuf(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, + bus_dmamap_t, struct mbuf *, bus_dmamap_callback2_t *, void *, int); +int iommu_dvmamap_load_uio(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, + bus_dmamap_t, struct uio *, bus_dmamap_callback2_t *, void *, int); void iommu_dvmamap_unload(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, bus_dmamap_t); void iommu_dvmamap_sync(bus_dma_tag_t, bus_dma_tag_t, struct iommu_state *, diff --git a/sys/sparc64/pci/psycho.c b/sys/sparc64/pci/psycho.c index 5e454c0..30205c8 100644 --- a/sys/sparc64/pci/psycho.c +++ b/sys/sparc64/pci/psycho.c @@ -106,6 +106,10 @@ static int psycho_dmamap_create(bus_dma_tag_t, bus_dma_tag_t, int, static int psycho_dmamap_destroy(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t); static int psycho_dmamap_load(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, void *, bus_size_t, bus_dmamap_callback_t *, void *, int); +static int psycho_dmamap_load_mbuf(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, + struct mbuf *, bus_dmamap_callback2_t *, void *, int); +static int psycho_dmamap_load_uio(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, + struct uio *, bus_dmamap_callback2_t *, void *, int); static void psycho_dmamap_unload(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t); static void psycho_dmamap_sync(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, bus_dmasync_op_t); @@ -489,6 +493,8 @@ psycho_attach(device_t dev) sc->sc_dmat->dt_dmamap_create = psycho_dmamap_create; sc->sc_dmat->dt_dmamap_destroy = psycho_dmamap_destroy; sc->sc_dmat->dt_dmamap_load = psycho_dmamap_load; + sc->sc_dmat->dt_dmamap_load_mbuf = psycho_dmamap_load_mbuf; + sc->sc_dmat->dt_dmamap_load_uio = psycho_dmamap_load_uio; sc->sc_dmat->dt_dmamap_unload = psycho_dmamap_unload; sc->sc_dmat->dt_dmamap_sync = psycho_dmamap_sync; sc->sc_dmat->dt_dmamem_alloc = psycho_dmamem_alloc; @@ -1361,6 +1367,30 @@ psycho_dmamap_load(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map, callback, callback_arg, flags)); } +static int +psycho_dmamap_load_mbuf(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, + bus_dmamap_t map, struct mbuf *m, bus_dmamap_callback2_t *callback, + void *callback_arg, int flags) +{ + struct psycho_softc *sc; + + sc = (struct psycho_softc *)pdmat->dt_cookie; + return (iommu_dvmamap_load_mbuf(pdmat, ddmat, sc->sc_is, map, m, + callback, callback_arg, flags)); +} + +static int +psycho_dmamap_load_uio(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, + bus_dmamap_t map, struct uio *uio, bus_dmamap_callback2_t *callback, + void *callback_arg, int flags) +{ + struct psycho_softc *sc; + + sc = (struct psycho_softc *)pdmat->dt_cookie; + return (iommu_dvmamap_load_uio(pdmat, ddmat, sc->sc_is, map, uio, + callback, callback_arg, flags)); +} + static void psycho_dmamap_unload(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map) { diff --git a/sys/sparc64/sbus/sbus.c b/sys/sparc64/sbus/sbus.c index f050c7c..16b846b 100644 --- a/sys/sparc64/sbus/sbus.c +++ b/sys/sparc64/sbus/sbus.c @@ -237,6 +237,10 @@ static int sbus_dmamap_create(bus_dma_tag_t, bus_dma_tag_t, int, static int sbus_dmamap_destroy(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t); static int sbus_dmamap_load(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, void *, bus_size_t, bus_dmamap_callback_t *, void *, int); +static int sbus_dmamap_load_mbuf(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, + struct mbuf *, bus_dmamap_callback2_t *, void *, int); +static int sbus_dmamap_load_uio(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, + struct uio *, bus_dmamap_callback2_t *, void *, int); static void sbus_dmamap_unload(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t); static void sbus_dmamap_sync(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t, bus_dmasync_op_t); @@ -341,6 +345,8 @@ sbus_probe(device_t dev) sc->sc_cdmatag->dt_dmamap_create = sbus_dmamap_create; sc->sc_cdmatag->dt_dmamap_destroy = sbus_dmamap_destroy; sc->sc_cdmatag->dt_dmamap_load = sbus_dmamap_load; + sc->sc_cdmatag->dt_dmamap_load_mbuf = sbus_dmamap_load_mbuf; + sc->sc_cdmatag->dt_dmamap_load_uio = sbus_dmamap_load_uio; sc->sc_cdmatag->dt_dmamap_unload = sbus_dmamap_unload; sc->sc_cdmatag->dt_dmamap_sync = sbus_dmamap_sync; sc->sc_cdmatag->dt_dmamem_alloc = sbus_dmamem_alloc; @@ -942,6 +948,28 @@ sbus_dmamap_load(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map, callback, callback_arg, flags)); } +static int +sbus_dmamap_load_mbuf(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, + bus_dmamap_t map, struct mbuf *m, bus_dmamap_callback2_t *callback, + void *callback_arg, int flags) +{ + struct sbus_softc *sc = (struct sbus_softc *)pdmat->dt_cookie; + + return (iommu_dvmamap_load_mbuf(pdmat, ddmat, &sc->sc_is, map, m, + callback, callback_arg, flags)); +} + +static int +sbus_dmamap_load_uio(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, + bus_dmamap_t map, struct uio *uio, bus_dmamap_callback2_t *callback, + void *callback_arg, int flags) +{ + struct sbus_softc *sc = (struct sbus_softc *)pdmat->dt_cookie; + + return (iommu_dvmamap_load_uio(pdmat, ddmat, &sc->sc_is, map, uio, + callback, callback_arg, flags)); +} + static void sbus_dmamap_unload(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map) { diff --git a/sys/sparc64/sparc64/bus_machdep.c b/sys/sparc64/sparc64/bus_machdep.c index 672331f..5180823 100644 --- a/sys/sparc64/sparc64/bus_machdep.c +++ b/sys/sparc64/sparc64/bus_machdep.c @@ -279,10 +279,10 @@ nexus_dmamap_create(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, int flags, bus_dmamap_t *mapp) { - /* Not much to do...? */ *mapp = malloc(sizeof(**mapp), M_DEVBUF, M_NOWAIT | M_ZERO); if (*mapp != NULL) { ddmat->dt_map_count++; + sparc64_dmamap_init(*mapp); return (0); } else return (ENOMEM); @@ -415,6 +415,7 @@ nexus_dmamap_load(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map, if (error == 0) { (*callback)(callback_arg, dm_segments, nsegs + 1, 0); + map->dm_loaded = 1; } else (*callback)(callback_arg, NULL, 0, error); @@ -460,6 +461,7 @@ nexus_dmamap_load_mbuf(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, /* force "no valid mappings" in callback */ (*callback)(callback_arg, dm_segments, 0, 0, error); } else { + map->dm_loaded = 1; (*callback)(callback_arg, dm_segments, nsegs + 1, m0->m_pkthdr.len, error); } @@ -517,6 +519,7 @@ nexus_dmamap_load_uio(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, /* force "no valid mappings" in callback */ (*callback)(callback_arg, dm_segments, 0, 0, error); } else { + map->dm_loaded = 1; (*callback)(callback_arg, dm_segments, nsegs + 1, uio->uio_resid, error); } @@ -531,7 +534,7 @@ static void nexus_dmamap_unload(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map) { - /* Nothing to do...? */ + map->dm_loaded = 0; } /* @@ -584,6 +587,7 @@ sparc64_dmamem_alloc_map(bus_dma_tag_t dmat, bus_dmamap_t *mapp) return (ENOMEM); dmat->dt_map_count++; + sparc64_dmamap_init(*mapp); return (0); } diff --git a/sys/sparc64/sparc64/iommu.c b/sys/sparc64/sparc64/iommu.c index 1a4cce3..af033bf 100644 --- a/sys/sparc64/sparc64/iommu.c +++ b/sys/sparc64/sparc64/iommu.c @@ -120,11 +120,15 @@ #include <sys/param.h> #include <sys/kernel.h> -#include <sys/systm.h> #include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/systm.h> +#include <sys/uio.h> #include <vm/vm.h> #include <vm/pmap.h> +#include <vm/vm_map.h> #include <machine/bus.h> #include <machine/bus_private.h> @@ -136,6 +140,12 @@ #include <machine/iommuvar.h> +/* + * Tuning constants. + */ +#define IOMMU_MAX_PRE BUS_SPACE_MAXSIZE +#define IOMMU_MAX_PRE_SEG 3 + MALLOC_DEFINE(M_IOMMU, "dvmamem", "IOMMU DVMA Buffers"); static int iommu_strbuf_flush_sync(struct iommu_state *); @@ -148,8 +158,8 @@ static void iommu_diag(struct iommu_state *, vm_offset_t va); * the IOTSBs are divorced. * LRU queue handling for lazy resource allocation. */ -static STAILQ_HEAD(, bus_dmamap) iommu_maplruq = - STAILQ_HEAD_INITIALIZER(iommu_maplruq); +static TAILQ_HEAD(, bus_dmamap) iommu_maplruq = + TAILQ_HEAD_INITIALIZER(iommu_maplruq); /* DVMA space rman. */ static struct rman iommu_dvma_rman; @@ -183,6 +193,19 @@ static STAILQ_HEAD(, iommu_state) iommu_insts = #define IOMMU_SET_TTE(is, va, tte) \ (iommu_tsb[IOTSBSLOT(va)] = (tte)) +/* Resource helpers */ +#define IOMMU_RES_START(res) \ + ((bus_addr_t)rman_get_start(res) << IO_PAGE_SHIFT) +#define IOMMU_RES_END(res) \ + ((bus_addr_t)(rman_get_end(res) + 1) << IO_PAGE_SHIFT) +#define IOMMU_RES_SIZE(res) \ + ((bus_size_t)rman_get_size(res) << IO_PAGE_SHIFT) + +/* Helpers for struct bus_dmamap_res */ +#define BDR_START(r) IOMMU_RES_START((r)->dr_res) +#define BDR_END(r) IOMMU_RES_END((r)->dr_res) +#define BDR_SIZE(r) IOMMU_RES_SIZE((r)->dr_res) + static __inline void iommu_tlb_flush(struct iommu_state *is, bus_addr_t va) { @@ -234,9 +257,11 @@ static __inline void iommu_map_insq(bus_dmamap_t map) { - if (!map->onq && map->dvmaresv != 0) { - STAILQ_INSERT_TAIL(&iommu_maplruq, map, maplruq); - map->onq = 1; + if (!SLIST_EMPTY(&map->dm_reslist)) { + if (map->dm_onq) + TAILQ_REMOVE(&iommu_maplruq, map, dm_maplruq); + TAILQ_INSERT_TAIL(&iommu_maplruq, map, dm_maplruq); + map->dm_onq = 1; } } @@ -244,9 +269,9 @@ static __inline void iommu_map_remq(bus_dmamap_t map) { - if (map->onq) - STAILQ_REMOVE(&iommu_maplruq, map, bus_dmamap, maplruq); - map->onq = 0; + if (map->dm_onq) + TAILQ_REMOVE(&iommu_maplruq, map, dm_maplruq); + map->dm_onq = 0; } /* @@ -490,8 +515,12 @@ static int iommu_dvma_valloc(bus_dma_tag_t t, struct iommu_state *is, bus_dmamap_t map, bus_size_t size) { + struct resource *res; + struct bus_dmamap_res *bdr; bus_size_t align, bound, sgsize; + if ((bdr = malloc(sizeof(*bdr), M_IOMMU, M_NOWAIT)) == NULL) + return (EAGAIN); /* * If a boundary is specified, a map cannot be larger than it; however * we do not clip currently, as that does not play well with the lazy @@ -503,29 +532,136 @@ iommu_dvma_valloc(bus_dma_tag_t t, struct iommu_state *is, bus_dmamap_t map, if (t->dt_boundary > 0 && t->dt_boundary < IO_PAGE_SIZE) panic("iommu_dvmamap_load: illegal boundary specified"); bound = ulmax(t->dt_boundary >> IO_PAGE_SHIFT, 1); - map->dvmaresv = 0; - map->res = rman_reserve_resource_bound(&iommu_dvma_rman, 0L, + res = rman_reserve_resource_bound(&iommu_dvma_rman, 0L, t->dt_lowaddr, sgsize, bound >> IO_PAGE_SHIFT, RF_ACTIVE | rman_make_alignment_flags(align), NULL); - if (map->res == NULL) + if (res == NULL) return (ENOMEM); - map->start = rman_get_start(map->res) * IO_PAGE_SIZE; - map->dvmaresv = size; - iommu_map_insq(map); + bdr->dr_res = res; + bdr->dr_used = 0; + SLIST_INSERT_HEAD(&map->dm_reslist, bdr, dr_link); return (0); } -/* Free DVMA virtual memory for a map. */ +/* Unload the map and mark all resources as unused, but do not free them. */ static void -iommu_dvma_vfree(bus_dmamap_t map) +iommu_dvmamap_vunload(struct iommu_state *is, bus_dmamap_t map) { + struct bus_dmamap_res *r; - iommu_map_remq(map); - if (map->res != NULL && rman_release_resource(map->res) != 0) + SLIST_FOREACH(r, &map->dm_reslist, dr_link) { + iommu_remove(is, BDR_START(r), r->dr_used); + r->dr_used = 0; + } +} + +/* Free a DVMA virtual memory resource. */ +static __inline void +iommu_dvma_vfree_res(bus_dmamap_t map, struct bus_dmamap_res *r) +{ + + KASSERT(r->dr_used == 0, ("iommu_dvma_vfree_res: resource busy!")); + if (r->dr_res != NULL && rman_release_resource(r->dr_res) != 0) printf("warning: DVMA space lost\n"); - map->res = NULL; - map->dvmaresv = 0; + SLIST_REMOVE(&map->dm_reslist, r, bus_dmamap_res, dr_link); + free(r, M_IOMMU); +} + +/* Free all DVMA virtual memory for a map. */ +static void +iommu_dvma_vfree(struct iommu_state *is, bus_dmamap_t map) +{ + + iommu_map_remq(map); + iommu_dvmamap_vunload(is, map); + while (!SLIST_EMPTY(&map->dm_reslist)) + iommu_dvma_vfree_res(map, SLIST_FIRST(&map->dm_reslist)); +} + +/* Prune a map, freeing all unused DVMA resources. */ +static bus_size_t +iommu_dvma_vprune(bus_dmamap_t map) +{ + struct bus_dmamap_res *r, *n; + bus_size_t freed = 0; + + for (r = SLIST_FIRST(&map->dm_reslist); r != NULL; r = n) { + n = SLIST_NEXT(r, dr_link); + if (r->dr_used == 0) { + freed += BDR_SIZE(r); + iommu_dvma_vfree_res(map, r); + } + } + return (freed); +} + +/* + * Try to find a suitably-sized (and if requested, -aligned) slab of DVMA + * memory with IO page offset voffs. A preferred address can be specified by + * setting prefaddr to a value != 0. + */ +static bus_addr_t +iommu_dvma_vfindseg(bus_dmamap_t map, vm_offset_t voffs, bus_size_t size, + bus_addr_t amask) +{ + struct bus_dmamap_res *r; + bus_addr_t dvmaddr, dvmend; + + SLIST_FOREACH(r, &map->dm_reslist, dr_link) { + dvmaddr = round_io_page(BDR_START(r) + r->dr_used); + /* Alignment can only work with voffs == 0. */ + dvmaddr = (dvmaddr + amask) & ~amask; + dvmaddr += voffs; + dvmend = dvmaddr + size; + if (dvmend <= BDR_END(r)) { + r->dr_used = dvmend - BDR_START(r); + return (dvmaddr); + } + } + return (0); +} + +/* + * Try to find or allocate a slab of DVMA space; see above. + */ +static int +iommu_dvma_vallocseg(bus_dma_tag_t dt, struct iommu_state *is, bus_dmamap_t map, + vm_offset_t voffs, bus_size_t size, bus_addr_t amask, bus_addr_t *addr) +{ + bus_dmamap_t tm; + bus_addr_t dvmaddr, freed; + int error; + + dvmaddr = iommu_dvma_vfindseg(map, voffs, size, amask); + + /* Need to allocate. */ + if (dvmaddr == 0) { + tm = TAILQ_FIRST(&iommu_maplruq); + while ((error = iommu_dvma_valloc(dt, is, map, + voffs + size)) == ENOMEM && tm != NULL) { + /* + * Free the allocated DVMA of a few tags until + * the required size is reached. This is an + * approximation to not have to call the allocation + * function too often; most likely one free run + * will not suffice if not one map was large enough + * itself due to fragmentation. + */ + freed = 0; + do { + freed += iommu_dvma_vprune(tm); + tm = TAILQ_NEXT(tm, dm_maplruq); + } while (freed < size && tm != NULL); + } + if (error != 0) + return (error); + dvmaddr = iommu_dvma_vfindseg(map, voffs, size, amask); + KASSERT(dvmaddr != 0, + ("iommu_dvma_vallocseg: allocation failed unexpectedly!")); + } + *addr = dvmaddr; + return (0); } int @@ -543,18 +679,16 @@ iommu_dvmamem_alloc(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, if ((*vaddr = malloc(dt->dt_maxsize, M_IOMMU, (flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK)) == NULL) { error = ENOMEM; - goto failm; + sparc64_dmamem_free_map(dt, *mapp); + return (error); } + iommu_map_insq(*mapp); /* - * Try to preallocate DVMA memory. If this fails, it is retried at load + * Try to preallocate DVMA space. If this fails, it is retried at load * time. */ iommu_dvma_valloc(dt, is, *mapp, IOMMU_SIZE_ROUNDUP(dt->dt_maxsize)); return (0); - -failm: - sparc64_dmamem_free_map(dt, *mapp); - return (error); } void @@ -562,7 +696,7 @@ iommu_dvmamem_free(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, void *vaddr, bus_dmamap_t map) { - iommu_dvma_vfree(map); + iommu_dvma_vfree(is, map); sparc64_dmamem_free_map(dt, map); free(vaddr, M_IOMMU); } @@ -571,20 +705,40 @@ int iommu_dvmamap_create(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, int flags, bus_dmamap_t *mapp) { - int error; + bus_size_t totsz, presz, currsz; + int error, i, maxpre; if ((error = sparc64_dmamap_create(pt->dt_parent, dt, flags, mapp)) != 0) return (error); - KASSERT((*mapp)->res == NULL, + KASSERT(SLIST_EMPTY(&(*mapp)->dm_reslist), ("iommu_dvmamap_create: hierarchy botched")); + iommu_map_insq(*mapp); /* - * Preallocate DMVA memory; if this fails now, it is retried at load - * time. + * Preallocate DVMA space; if this fails now, it is retried at load + * time. Through bus_dmamap_load_mbuf() and bus_dmamap_load_uio(), it + * is possible to have multiple discontiguous segments in a single map, + * which is handled by allocating additional resources, instead of + * increasing the size, to avoid fragmentation. * Clamp preallocation to BUS_SPACE_MAXSIZE. In some situations we can * handle more; that case is handled by reallocating at map load time. */ - iommu_dvma_valloc(dt, is, *mapp, - ulmin(IOMMU_SIZE_ROUNDUP(dt->dt_maxsize), BUS_SPACE_MAXSIZE)); + totsz = ulmin(IOMMU_SIZE_ROUNDUP(dt->dt_maxsize), IOMMU_MAX_PRE); + error = iommu_dvma_valloc(dt, is, *mapp, totsz); + if (error != 0) + return (0); + /* + * Try to be smart about preallocating some additional segments if + * needed. + */ + maxpre = imin(dt->dt_nsegments, IOMMU_MAX_PRE_SEG); + presz = dt->dt_maxsize / maxpre; + for (i = 0; i < maxpre && totsz < IOMMU_MAX_PRE; i++) { + currsz = round_io_page(ulmin(presz, IOMMU_MAX_PRE - totsz)); + error = iommu_dvma_valloc(dt, is, *mapp, currsz); + if (error != 0) + break; + totsz += currsz; + } return (0); } @@ -593,91 +747,52 @@ iommu_dvmamap_destroy(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, bus_dmamap_t map) { - iommu_dvma_vfree(map); + iommu_dvma_vfree(is, map); return (sparc64_dmamap_destroy(pt->dt_parent, dt, map)); } -#define BUS_DMAMAP_NSEGS ((BUS_SPACE_MAXSIZE / PAGE_SIZE) + 1) - /* * IOMMU DVMA operations, common to SBUS and PCI. */ -int -iommu_dvmamap_load(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, - bus_dmamap_t map, void *buf, bus_size_t buflen, bus_dmamap_callback_t *cb, - void *cba, int flags) +static int +iommu_dvmamap_load_buffer(bus_dma_tag_t dt, struct iommu_state *is, + bus_dmamap_t map, bus_dma_segment_t sgs[], void *buf, + bus_size_t buflen, struct thread *td, int flags, int *segp, int align) { -#ifdef __GNUC__ - bus_dma_segment_t sgs[dt->dt_nsegments]; -#else - bus_dma_segment_t sgs[BUS_DMAMAP_NSEGS]; -#endif - bus_dmamap_t tm; - bus_size_t sgsize, fsize, maxsize; - vm_offset_t curaddr; - u_long dvmaddr; - vm_offset_t vaddr; - int error, sgcnt; - - if (map->buflen != 0) { - /* Already in use?? */ -#ifdef DIAGNOSTIC - printf("iommu_dvmamap_load: map still in use\n"); -#endif - bus_dmamap_unload(dt, map); - } + bus_size_t sgsize; + vm_offset_t curaddr, vaddr, voffs; + bus_addr_t amask, dvmaddr; + int error, sgcnt, firstpg; + pmap_t pmap = NULL; + + KASSERT(buflen != 0, ("iommu_dvmamap_load_buffer: buflen == 0!")); if (buflen > dt->dt_maxsize) return (EINVAL); - maxsize = IOMMU_SIZE_ROUNDUP(buflen); - if (maxsize > map->dvmaresv) { - /* - * Need to allocate resources now; free any old allocation - * first. - */ - fsize = map->dvmaresv; - iommu_dvma_vfree(map); - while (iommu_dvma_valloc(dt, is, map, maxsize) == ENOMEM && - !STAILQ_EMPTY(&iommu_maplruq)) { - /* - * Free the allocated DVMA of a few tags until - * the required size is reached. This is an - * approximation to not have to call the allocation - * function too often; most likely one free run - * will not suffice if not one map was large enough - * itself due to fragmentation. - */ - do { - tm = STAILQ_FIRST(&iommu_maplruq); - if (tm == NULL) - break; - fsize += tm->dvmaresv; - iommu_dvma_vfree(tm); - } while (fsize < maxsize); - fsize = 0; - } - if (map->dvmaresv < maxsize) { - printf("DVMA allocation failed: needed %ld, got %ld\n", - maxsize, map->dvmaresv); - return (ENOMEM); - } - } - - /* Busy the map by removing it from the LRU queue. */ - iommu_map_remq(map); + if (td != NULL) + pmap = vmspace_pmap(td->td_proc->p_vmspace); vaddr = (vm_offset_t)buf; - map->buf = buf; - map->buflen = buflen; + voffs = vaddr & IO_PAGE_MASK; + amask = align ? dt->dt_alignment - 1 : 0; - dvmaddr = map->start | (vaddr & IO_PAGE_MASK); - sgcnt = -1; + /* Try to find a slab that is large enough. */ + error = iommu_dvma_vallocseg(dt, is, map, voffs, buflen, amask, + &dvmaddr); + if (error != 0) + return (error); + + sgcnt = *segp; error = 0; + firstpg = 1; for (; buflen > 0; ) { /* * Get the physical address for this page. */ - curaddr = pmap_kextract((vm_offset_t)vaddr); + if (pmap != NULL) + curaddr = pmap_extract(pmap, vaddr); + else + curaddr = pmap_kextract(vaddr); /* * Compute the segment size, and adjust counts. @@ -689,17 +804,24 @@ iommu_dvmamap_load(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, iommu_enter(is, trunc_io_page(dvmaddr), trunc_io_page(curaddr), flags); - if (sgcnt == -1 || sgs[sgcnt].ds_len + sgsize > dt->dt_maxsegsz) { + if (firstpg || sgs[sgcnt].ds_len + sgsize > dt->dt_maxsegsz) { if (sgsize > dt->dt_maxsegsz) { /* XXX: add fixup */ - panic("iommu_dvmamap_load: magsegsz too " + panic("iommu_dvmamap_load_buffer: magsegsz too " "small\n"); } sgcnt++; - if (sgcnt > dt->dt_nsegments || sgcnt > BUS_DMAMAP_NSEGS) { - error = ENOMEM; + if (sgcnt >= dt->dt_nsegments || + sgcnt >= BUS_DMAMAP_NSEGS) { + error = EFBIG; break; } + /* + * No extra alignment here - the common practice in the + * busdma code seems to be that only the first segment + * needs to satisfy the alignment constraints (and that + * only for bus_dmamem_alloc()ed maps). + */ sgs[sgcnt].ds_addr = dvmaddr; sgs[sgcnt].ds_len = sgsize; } else @@ -707,28 +829,162 @@ iommu_dvmamap_load(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, dvmaddr += sgsize; vaddr += sgsize; buflen -= sgsize; + firstpg = 0; } - (*cb)(cba, sgs, sgcnt + 1, error); + *segp = sgcnt; return (0); + +} + +int +iommu_dvmamap_load(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, + bus_dmamap_t map, void *buf, bus_size_t buflen, bus_dmamap_callback_t *cb, + void *cba, int flags) +{ +#ifdef __GNUC__ + bus_dma_segment_t sgs[dt->dt_nsegments]; +#else + bus_dma_segment_t sgs[BUS_DMAMAP_NSEGS]; +#endif + int error, seg = -1; + + if (map->dm_loaded) { +#ifdef DIAGNOSTIC + printf("iommu_dvmamap_load: map still in use\n"); +#endif + bus_dmamap_unload(dt, map); + } + + error = iommu_dvmamap_load_buffer(dt, is, map, sgs, buf, buflen, NULL, + flags, &seg, 1); + + if (error != 0) { + iommu_dvmamap_vunload(is, map); + (*cb)(cba, NULL, 0, error); + } else { + /* Move the map to the end of the LRU queue. */ + iommu_map_insq(map); + map->dm_loaded = 1; + (*cb)(cba, sgs, seg + 1, 0); + } + + return (error); +} + +int +iommu_dvmamap_load_mbuf(bus_dma_tag_t pt, bus_dma_tag_t dt, + struct iommu_state *is, bus_dmamap_t map, struct mbuf *m0, + bus_dmamap_callback2_t *cb, void *cba, int flags) +{ +#ifdef __GNUC__ + bus_dma_segment_t sgs[dt->dt_nsegments]; +#else + bus_dma_segment_t sgs[BUS_DMAMAP_NSEGS]; +#endif + struct mbuf *m; + int error = 0, first = 1, nsegs = -1; + + KASSERT(m0->m_flags & M_PKTHDR, + ("iommu_dvmamap_load_mbuf: no packet header")); + + if (map->dm_loaded) { +#ifdef DIAGNOSTIC + printf("iommu_dvmamap_load_mbuf: map still in use\n"); +#endif + bus_dmamap_unload(dt, map); + } + + if (m0->m_pkthdr.len <= dt->dt_maxsize) { + for (m = m0; m != NULL && error == 0; m = m->m_next) { + if (m->m_len == 0) + continue; + error = iommu_dvmamap_load_buffer(dt, is, map, sgs, + m->m_data, m->m_len, NULL, flags, &nsegs, first); + first = 0; + } + } else + error = EINVAL; + + if (error != 0) { + iommu_dvmamap_vunload(is, map); + /* force "no valid mappings" in callback */ + (*cb)(cba, sgs, 0, 0, error); + } else { + iommu_map_insq(map); + map->dm_loaded = 1; + (*cb)(cba, sgs, nsegs + 1, m0->m_pkthdr.len, 0); + } + return (error); } +int +iommu_dvmamap_load_uio(bus_dma_tag_t pt, bus_dma_tag_t dt, + struct iommu_state *is, bus_dmamap_t map, struct uio *uio, + bus_dmamap_callback2_t *cb, void *cba, int flags) +{ +#ifdef __GNUC__ + bus_dma_segment_t sgs[dt->dt_nsegments]; +#else + bus_dma_segment_t sgs[BUS_DMAMAP_NSEGS]; +#endif + struct iovec *iov; + struct thread *td = NULL; + bus_size_t minlen, resid; + int nsegs = -1, error = 0, first = 1, i; + + if (map->dm_loaded) { +#ifdef DIAGNOSTIC + printf("iommu_dvmamap_load_uio: map still in use\n"); +#endif + bus_dmamap_unload(dt, map); + } + + resid = uio->uio_resid; + iov = uio->uio_iov; + + if (uio->uio_segflg == UIO_USERSPACE) { + td = uio->uio_td; + KASSERT(td != NULL, + ("%s: USERSPACE but no proc", __func__)); + } + + for (i = 0; i < uio->uio_iovcnt && resid != 0 && error == 0; i++) { + /* + * Now at the first iovec to load. Load each iovec + * until we have exhausted the residual count. + */ + minlen = resid < iov[i].iov_len ? resid : iov[i].iov_len; + if (minlen == 0) + continue; + + error = iommu_dvmamap_load_buffer(dt, is, map, sgs, + iov[i].iov_base, minlen, td, flags, &nsegs, first); + first = 0; + + resid -= minlen; + } + + if (error) { + iommu_dvmamap_vunload(is, map); + /* force "no valid mappings" in callback */ + (*cb)(cba, sgs, 0, 0, error); + } else { + iommu_map_insq(map); + map->dm_loaded = 1; + (*cb)(cba, sgs, nsegs + 1, uio->uio_resid, 0); + } + return (error); +} void iommu_dvmamap_unload(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, bus_dmamap_t map) { - /* - * If the resource is already deallocated, just pass to the parent - * tag. - */ - if (map->buflen == 0 || map->start == 0) + if (map->dm_loaded == 0) return; - - iommu_remove(is, map->start, map->buflen); - map->buflen = 0; + iommu_dvmamap_vunload(is, map); iommu_map_insq(map); - /* Flush the caches */ sparc64_dmamap_unload(pt->dt_parent, dt, map); } @@ -736,20 +992,23 @@ void iommu_dvmamap_sync(bus_dma_tag_t pt, bus_dma_tag_t dt, struct iommu_state *is, bus_dmamap_t map, bus_dmasync_op_t op) { + struct bus_dmamap_res *r; vm_offset_t va; vm_size_t len; - va = (vm_offset_t)map->start; - len = map->buflen; if ((op & BUS_DMASYNC_PREREAD) != 0) membar(Sync); if ((op & (BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE)) != 0) { - /* if we have a streaming buffer, flush it here first */ - while (len > 0) { - iommu_strbuf_flush(is, va, - len <= IO_PAGE_SIZE); - len -= ulmin(len, IO_PAGE_SIZE); - va += IO_PAGE_SIZE; + SLIST_FOREACH(r, &map->dm_reslist, dr_link) { + va = (vm_offset_t)BDR_START(r); + len = r->dr_used; + /* if we have a streaming buffer, flush it here first */ + while (len > 0) { + iommu_strbuf_flush(is, va, + len <= IO_PAGE_SIZE); + len -= ulmin(len, IO_PAGE_SIZE); + va += IO_PAGE_SIZE; + } } } if ((op & BUS_DMASYNC_PREWRITE) != 0) |