/* * Copyright (c) 2001 Wind River Systems * Copyright (c) 1997, 1998, 1999, 2000, 2001 * Bill Paul . 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include struct bus_dma_map_arg { int curseg; int maxseg; bus_dma_segment_t *segs; }; static void bus_dmamap_list_put __P((bus_dma_tag_t, bus_dmahead_t, bus_dmalist_t)); static bus_dmalist_t bus_dmamap_list_get __P((bus_dma_tag_t, bus_dmahead_t)); static void bus_dmamap_load_mbuf_cb __P((void *, bus_dma_segment_t *, int, int)); int bus_dmamap_list_init(dmat, listhead, cnt, flag) bus_dma_tag_t dmat; bus_dmahead_t listhead; int cnt, flag; { bus_dmalist_t entry; int i, error; SLIST_INIT(listhead); for (i = 0; i < cnt; i++) { entry = malloc(sizeof(struct bus_dmamap_list_entry), M_DEVBUF, M_NOWAIT); if (entry == NULL) { bus_dmamap_list_destroy(dmat, listhead); return(ENOBUFS); } entry->map = NULL; if ((error = bus_dmamap_create(dmat, 0, &entry->map))) { bus_dmamap_list_destroy(dmat, listhead); return(error); } SLIST_INSERT_HEAD(listhead, entry, link); } return(0); } void bus_dmamap_list_destroy(dmat, listhead) bus_dma_tag_t dmat; bus_dmahead_t listhead; { bus_dmalist_t entry; while (!SLIST_EMPTY(listhead)) { entry = SLIST_FIRST(listhead); SLIST_REMOVE_HEAD(listhead, link); bus_dmamap_destroy(dmat, entry->map); free(entry, M_DEVBUF); } return; } static bus_dmalist_t bus_dmamap_list_get(dmat, listhead) bus_dma_tag_t dmat; bus_dmahead_t listhead; { bus_dmalist_t entry; if (SLIST_EMPTY(listhead)) return(NULL); entry = SLIST_FIRST(listhead); SLIST_REMOVE_HEAD(listhead, link); return(entry); } static void bus_dmamap_list_put(dmat, listhead, entry) bus_dma_tag_t dmat; bus_dmahead_t listhead; bus_dmalist_t entry; { SLIST_INSERT_HEAD(listhead, entry, link); return; } static void bus_dmamap_load_mbuf_cb(arg, segs, nseg, error) void *arg; bus_dma_segment_t *segs; int nseg, error; { struct bus_dma_map_arg *a; bus_dma_segment_t *s; int i; a = arg; if (error) { a->curseg = -1; return; } /* * Make sure there's still room left * in this segment array. */ if ((a->curseg + nseg) > a->maxseg) { a->curseg = -1; return; } s = a->segs; for (i = 0; i < nseg; i++) { s[i + a->curseg].ds_len = segs[i].ds_len; s[i + a->curseg].ds_addr = segs[i].ds_addr; } a->curseg += i; return; } void bus_dmamap_unload_mbuf(dmat, m0) bus_dma_tag_t dmat; struct mbuf *m0; { struct mbuf *m; bus_dmalist_t entry; for (m = m0; m != NULL; m = m->m_next) { if (m->m_len != 0) { entry = (bus_dmalist_t)m->m_nextpkt; bus_dmamap_unload(dmat, entry->map); } } return; } void bus_dmamap_destroy_mbuf(dmat, listhead, m0) bus_dma_tag_t dmat; struct bus_dmamap_list_head *listhead; struct mbuf *m0; { struct mbuf *m; bus_dmalist_t entry; for (m = m0; m != NULL; m = m->m_next) { if (m->m_len != 0) { entry = (bus_dmalist_t)m->m_nextpkt; m->m_nextpkt = NULL; bus_dmamap_list_put(dmat, listhead, entry); } } return; } void bus_dmamap_sync_mbuf(dmat, m0, op) bus_dma_tag_t dmat; struct mbuf *m0; bus_dmasync_op_t op; { struct mbuf *m; bus_dmalist_t entry; for (m = m0; m != NULL; m = m->m_next) { if (m->m_len != 0) { entry = (bus_dmalist_t)m->m_nextpkt; bus_dmamap_sync(dmat, entry->map, op); } } return; } /* * Use the busdma API to DMA map the buffers in an mbuf list. * Note that this is meant to be used at the device driver level. * We abuse the mbuf header a little bit in order to stash the * DMA map pointer without forcing device drivers to allocate * extra space. The m_nextpkt pointer in the mbuf header will * always be available for us to use in a network driver context: * from the RX side, we allocate the mbufs ourselves, so we know * the m_nextpkt pointer will be unused, and from the TX side, we * know we will always be passed an mbuf list, and never an mbuf * chain. * * Note that we deal with the case where the mbuf's data region * spans discontiguous pages. The caller has to make sure to pass * us a large enough segment array to handle all the possible * segment mappings that the busdma code might cough up. */ int bus_dmamap_load_mbuf(dmat, listhead, m0, segs, nseg) bus_dma_tag_t dmat; struct bus_dmamap_list_head *listhead; struct mbuf *m0; bus_dma_segment_t *segs; int *nseg; { struct mbuf *m; bus_dmalist_t entry; struct bus_dma_map_arg arg; int error; arg.curseg = 0; arg.maxseg = *nseg; arg.segs = segs; for (m = m0; m != NULL; m = m->m_next) { if (m->m_len != 0) { entry = bus_dmamap_list_get(dmat, listhead); if (entry == NULL) { bus_dmamap_unload_mbuf(dmat, m0); bus_dmamap_destroy_mbuf(dmat, listhead, m0); return(ENOBUFS); } m->m_nextpkt = (struct mbuf *)entry; if ((error = bus_dmamap_load(dmat, entry->map, mtod(m, void *), m->m_len, bus_dmamap_load_mbuf_cb, &arg, 0))) { bus_dmamap_unload_mbuf(dmat, m0); bus_dmamap_destroy_mbuf(dmat, listhead, m0); return(error); } if (arg.curseg == -1) { bus_dmamap_unload_mbuf(dmat, m0); bus_dmamap_destroy_mbuf(dmat, listhead, m0); return(EINVAL); } } } *nseg = arg.curseg; return(0); }