/* $NetBSD: linux_sgt.c,v 1.4 2021/12/24 15:08:31 riastradh Exp $ */ /*- * Copyright (c) 2021 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: linux_sgt.c,v 1.4 2021/12/24 15:08:31 riastradh Exp $"); #include #include #include #include #include #include #include #include int sg_alloc_table(struct sg_table *sgt, unsigned npgs, gfp_t gfp) { sgt->sgl->sg_pgs = kcalloc(npgs, sizeof(sgt->sgl->sg_pgs[0]), gfp); if (sgt->sgl->sg_pgs == NULL) return -ENOMEM; sgt->sgl->sg_npgs = sgt->nents = npgs; sgt->sgl->sg_dmamap = NULL; return 0; } int __sg_alloc_table_from_pages(struct sg_table *sgt, struct page **pgs, unsigned npgs, bus_size_t offset, bus_size_t size, unsigned maxseg, gfp_t gfp) { unsigned i; int ret; KASSERT(offset == 0); KASSERT(size == (bus_size_t)npgs << PAGE_SHIFT); ret = sg_alloc_table(sgt, npgs, gfp); if (ret) return ret; for (i = 0; i < npgs; i++) sgt->sgl->sg_pgs[i] = pgs[i]; return 0; } int sg_alloc_table_from_pages(struct sg_table *sgt, struct page **pgs, unsigned npgs, bus_size_t offset, bus_size_t size, gfp_t gfp) { return __sg_alloc_table_from_pages(sgt, pgs, npgs, offset, size, -1, gfp); } int sg_alloc_table_from_bus_dmamem(struct sg_table *sgt, bus_dma_tag_t dmat, const bus_dma_segment_t *seg, int nseg, gfp_t gfp) { int i, npgs = 0; int ret; KASSERT(nseg >= 1); /* * Count the number of pages. Some segments may span multiple * contiguous pages. */ for (i = 0; i < nseg; i++) { bus_size_t len = seg[i].ds_len; for (; len >= PAGE_SIZE; len -= PAGE_SIZE, npgs++) { if (npgs == INT_MAX) return -ENOMEM; } KASSERTMSG(len == 0, "misaligned segment length: %ju\n", (uintmax_t)seg[i].ds_len); } ret = sg_alloc_table(sgt, npgs, gfp); if (ret) return ret; /* XXX errno NetBSD->Linux */ ret = -bus_dmamem_export_pages(dmat, seg, nseg, sgt->sgl->sg_pgs, sgt->sgl->sg_npgs); if (ret) goto out; /* Success! */ ret = 0; out: if (ret) sg_free_table(sgt); return ret; } void sg_free_table(struct sg_table *sgt) { if (sgt->sgl->sg_dmamap) { KASSERT(sgt->sgl->sg_dmat); bus_dmamap_destroy(sgt->sgl->sg_dmat, sgt->sgl->sg_dmamap); } kfree(sgt->sgl->sg_pgs); sgt->sgl->sg_pgs = NULL; sgt->sgl->sg_npgs = 0; } int dma_map_sg(bus_dma_tag_t dmat, struct scatterlist *sg, int nents, int dir) { return dma_map_sg_attrs(dmat, sg, nents, dir, 0); } int dma_map_sg_attrs(bus_dma_tag_t dmat, struct scatterlist *sg, int nents, int dir, int attrs) { int flags = 0; bool loaded = false; int ret, error = 0; KASSERT(sg->sg_dmamap == NULL); KASSERT(sg->sg_npgs); KASSERT(nents >= 1); switch (dir) { case DMA_TO_DEVICE: flags |= BUS_DMA_WRITE; break; case DMA_FROM_DEVICE: flags |= BUS_DMA_READ; break; case DMA_BIDIRECTIONAL: flags |= BUS_DMA_READ|BUS_DMA_WRITE; break; case DMA_NONE: default: panic("invalid DMA direction %d", dir); } error = bus_dmamap_create(dmat, (bus_size_t)sg->sg_npgs << PAGE_SHIFT, nents, PAGE_SIZE, 0, BUS_DMA_WAITOK, &sg->sg_dmamap); if (error) goto out; KASSERT(sg->sg_dmamap); error = bus_dmamap_load_pages(dmat, sg->sg_dmamap, sg->sg_pgs, (bus_size_t)sg->sg_npgs << PAGE_SHIFT, BUS_DMA_WAITOK|flags); if (error) goto out; loaded = true; /* Success! */ KASSERT(sg->sg_dmamap->dm_nsegs > 0); KASSERT(sg->sg_dmamap->dm_nsegs <= nents); sg->sg_dmat = dmat; ret = sg->sg_dmamap->dm_nsegs; error = 0; out: if (error) { if (loaded) bus_dmamap_unload(dmat, sg->sg_dmamap); loaded = false; if (sg->sg_dmamap) bus_dmamap_destroy(dmat, sg->sg_dmamap); sg->sg_dmamap = NULL; ret = 0; } return ret; } void dma_unmap_sg(bus_dma_tag_t dmat, struct scatterlist *sg, int nents, int dir) { dma_unmap_sg_attrs(dmat, sg, nents, dir, 0); } void dma_unmap_sg_attrs(bus_dma_tag_t dmat, struct scatterlist *sg, int nents, int dir, int attrs) { KASSERT(sg->sg_dmat == dmat); bus_dmamap_unload(dmat, sg->sg_dmamap); bus_dmamap_destroy(dmat, sg->sg_dmamap); sg->sg_dmamap = NULL; } bus_addr_t sg_dma_address(const struct scatterlist *sg) { KASSERT(sg->sg_dmamap->dm_nsegs == 1); return sg->sg_dmamap->dm_segs[0].ds_addr; } bus_size_t sg_dma_len(const struct scatterlist *sg) { KASSERT(sg->sg_dmamap->dm_nsegs == 1); return sg->sg_dmamap->dm_segs[0].ds_len; }