diff options
author | Stefan Sperling <stsp@cvs.openbsd.org> | 2014-07-20 11:59:13 +0000 |
---|---|---|
committer | Stefan Sperling <stsp@cvs.openbsd.org> | 2014-07-20 11:59:13 +0000 |
commit | 1e0f3ef33af1826c03834c10973d19eeeb902da8 (patch) | |
tree | 0806a8c6f76354b8fa3d6a21c9f3de76797a1db0 | |
parent | 6713489079ffe59f273b6fc68745f901a16096a1 (diff) |
Always allocate bwi(4) ring descriptors below the 1GB boundary to give 30bit
devices a chance to work. Use bounce buffers for mbufs on 30bit devices.
This fixes "intr fatal TX/RX" errors that render the internal wifi on many
macppc machines unusable. However, packet loss problems remain. In my testing
the device works fine sometimes, but experiences packet loss rates of up
to 80% at other times. Still, this is a step forward.
Helpful hints from claudio@ and dlg@
Tested on macppc by mpi@ and myself
"go ahead" kettenis@, ok mpi@
-rw-r--r-- | sys/dev/ic/bwi.c | 348 | ||||
-rw-r--r-- | sys/dev/ic/bwivar.h | 9 |
2 files changed, 300 insertions, 57 deletions
diff --git a/sys/dev/ic/bwi.c b/sys/dev/ic/bwi.c index 61fd1237585..8d170b34293 100644 --- a/sys/dev/ic/bwi.c +++ b/sys/dev/ic/bwi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: bwi.c,v 1.105 2014/07/20 11:57:49 stsp Exp $ */ +/* $OpenBSD: bwi.c,v 1.106 2014/07/20 11:59:12 stsp Exp $ */ /* * Copyright (c) 2007 The DragonFly Project. All rights reserved. @@ -73,6 +73,8 @@ #include <dev/ic/bwireg.h> #include <dev/ic/bwivar.h> +#include <uvm/uvm_extern.h> + #ifdef BWI_DEBUG int bwi_debug = 1; #define DPRINTF(l, x...) do { if ((l) <= bwi_debug) printf(x); } while (0) @@ -307,6 +309,7 @@ int bwi_dma_ring_alloc(struct bwi_softc *, int bwi_dma_txstats_alloc(struct bwi_softc *, uint32_t, bus_size_t); void bwi_dma_txstats_free(struct bwi_softc *); +int bwi_dma_mbuf_create30(struct bwi_softc *); int bwi_dma_mbuf_create(struct bwi_softc *); void bwi_dma_mbuf_destroy(struct bwi_softc *, int, int); void bwi_enable_intrs(struct bwi_softc *, uint32_t); @@ -325,6 +328,7 @@ int bwi_init_txstats64(struct bwi_softc *); void bwi_setup_rx_desc64(struct bwi_softc *, int, bus_addr_t, int); void bwi_setup_tx_desc64(struct bwi_softc *, struct bwi_ring_data *, int, bus_addr_t, int); +int bwi_newbuf30(struct bwi_softc *, int, int); int bwi_newbuf(struct bwi_softc *, int, int); void bwi_set_addr_filter(struct bwi_softc *, uint16_t, const uint8_t *); @@ -918,6 +922,7 @@ bwi_detach(void *arg) bwi_mac_detach(&sc->sc_mac[i]); bwi_dma_free(sc); + bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 1); return (0); } @@ -6222,6 +6227,9 @@ bwi_setup_desc32(struct bwi_softc *sc, struct bwi_desc32 *desc_array, struct bwi_desc32 *desc = &desc_array[desc_idx]; uint32_t ctrl, addr, addr_hi, addr_lo; + if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT && paddr >= 0x40000000) + panic("bad paddr 0x%lx\n", (long)paddr); + addr_lo = __SHIFTOUT(paddr, BWI_DESC32_A_ADDR_MASK); addr_hi = __SHIFTOUT(paddr, BWI_DESC32_A_FUNC_MASK); @@ -7515,6 +7523,13 @@ bwi_node_alloc(struct ieee80211com *ic) return ((struct ieee80211_node *)bn); } +struct uvm_constraint_range bwi_constraint = { 0x0, (0x40000000 - 1) }; +struct kmem_pa_mode bwi_pa_mode = { + .kp_align = BWI_RING_ALIGN, + .kp_constraint = &bwi_constraint, + .kp_zero = 1 +}; + int bwi_dma_alloc(struct bwi_softc *sc) { @@ -7532,6 +7547,16 @@ bwi_dma_alloc(struct bwi_softc *sc) switch (sc->sc_bus_space) { case BWI_BUS_SPACE_30BIT: + /* + * 30bit devices must use bounce buffers but + * otherwise work like 32bit devices. + */ + sc->sc_newbuf = bwi_newbuf30; + + /* XXX implement txstats for 30bit? */ + has_txstats = 0; + + /* FALLTHROUGH */ case BWI_BUS_SPACE_32BIT: desc_sz = sizeof(struct bwi_desc32); txrx_ctrl_step = 0x20; @@ -7540,6 +7565,8 @@ bwi_dma_alloc(struct bwi_softc *sc) sc->sc_free_tx_ring = bwi_free_tx_ring32; sc->sc_init_rx_ring = bwi_init_rx_ring32; sc->sc_free_rx_ring = bwi_free_rx_ring32; + if (sc->sc_newbuf == NULL) + sc->sc_newbuf = bwi_newbuf; sc->sc_setup_rxdesc = bwi_setup_rx_desc32; sc->sc_setup_txdesc = bwi_setup_tx_desc32; sc->sc_rxeof = bwi_rxeof32; @@ -7559,6 +7586,7 @@ bwi_dma_alloc(struct bwi_softc *sc) sc->sc_free_tx_ring = bwi_free_tx_ring64; sc->sc_init_rx_ring = bwi_init_rx_ring64; sc->sc_free_rx_ring = bwi_free_rx_ring64; + sc->sc_newbuf = bwi_newbuf; sc->sc_setup_rxdesc = bwi_setup_rx_desc64; sc->sc_setup_txdesc = bwi_setup_tx_desc64; sc->sc_rxeof = bwi_rxeof64; @@ -7569,6 +7597,8 @@ bwi_dma_alloc(struct bwi_softc *sc) sc->sc_txeof_status = bwi_txeof_status64; } break; + default: + panic("unsupported bus space type %d", sc->sc_bus_space); } KASSERT(desc_sz != 0); @@ -7582,19 +7612,12 @@ bwi_dma_alloc(struct bwi_softc *sc) * Create TX ring DMA stuffs */ for (i = 0; i < BWI_TX_NRING; ++i) { - error = bus_dmamap_create(sc->sc_dmat, tx_ring_sz, 1, - tx_ring_sz, 0, BUS_DMA_NOWAIT, - &sc->sc_tx_rdata[i].rdata_dmap); - if (error) { - printf("%s: %dth TX ring DMA create failed\n", - sc->sc_dev.dv_xname, i); - return (error); - } error = bwi_dma_ring_alloc(sc, &sc->sc_tx_rdata[i], tx_ring_sz, TXRX_CTRL(i)); if (error) { printf("%s: %dth TX ring DMA alloc failed\n", sc->sc_dev.dv_xname, i); + bwi_dma_free(sc); return (error); } } @@ -7602,18 +7625,11 @@ bwi_dma_alloc(struct bwi_softc *sc) /* * Create RX ring DMA stuffs */ - error = bus_dmamap_create(sc->sc_dmat, rx_ring_sz, 1, - rx_ring_sz, 0, BUS_DMA_NOWAIT, - &sc->sc_rx_rdata.rdata_dmap); - if (error) { - printf("%s: RX ring DMA create failed\n", sc->sc_dev.dv_xname); - return (error); - } - error = bwi_dma_ring_alloc(sc, &sc->sc_rx_rdata, rx_ring_sz, TXRX_CTRL(0)); if (error) { printf("%s: RX ring DMA alloc failed\n", sc->sc_dev.dv_xname); + bwi_dma_free(sc); return (error); } @@ -7622,12 +7638,20 @@ bwi_dma_alloc(struct bwi_softc *sc) if (error) { printf("%s: TX stats DMA alloc failed\n", sc->sc_dev.dv_xname); + bwi_dma_free(sc); return (error); } } #undef TXRX_CTRL - return (bwi_dma_mbuf_create(sc)); + if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT) + error = bwi_dma_mbuf_create30(sc); + else + error = bwi_dma_mbuf_create(sc); + if (error) + bwi_dma_free(sc); + + return (error); } void @@ -7640,10 +7664,10 @@ bwi_dma_free(struct bwi_softc *sc) rd = &sc->sc_tx_rdata[i]; if (rd->rdata_desc != NULL) { - bus_dmamap_unload(sc->sc_dmat, - rd->rdata_dmap); - bus_dmamem_free(sc->sc_dmat, - &rd->rdata_seg, 1); + bus_dmamap_unload(sc->sc_dmat, rd->rdata_dmap); + km_free(rd->rdata_desc, rd->rdata_ring_sz, + &kv_intrsafe, &bwi_pa_mode); + rd->rdata_desc = NULL; } } @@ -7651,30 +7675,51 @@ bwi_dma_free(struct bwi_softc *sc) if (rd->rdata_desc != NULL) { bus_dmamap_unload(sc->sc_dmat, rd->rdata_dmap); - bus_dmamem_free(sc->sc_dmat, &rd->rdata_seg, 1); + km_free(rd->rdata_desc, rd->rdata_ring_sz, + &kv_intrsafe, &bwi_pa_mode); + rd->rdata_desc = NULL; } bwi_dma_txstats_free(sc); - bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 1); + + if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT) { + for (i = 0; i < BWI_TX_NRING; ++i) { + if (sc->sc_bounce_tx_data[i] != NULL) { + km_free(sc->sc_bounce_tx_data[i], + BWI_TX_NDESC * MCLBYTES, + &kv_intrsafe, &bwi_pa_mode); + sc->sc_bounce_tx_data[i] = NULL; + } + } + + if (sc->sc_bounce_rx_data != NULL) { + km_free(sc->sc_bounce_rx_data, BWI_RX_NDESC * MCLBYTES, + &kv_intrsafe, &bwi_pa_mode); + sc->sc_bounce_rx_data = NULL; + } + } } int bwi_dma_ring_alloc(struct bwi_softc *sc, struct bwi_ring_data *rd, bus_size_t size, uint32_t txrx_ctrl) { - int error, nsegs; + int error; - error = bus_dmamem_alloc(sc->sc_dmat, size, BWI_ALIGN, 0, - &rd->rdata_seg, 1, &nsegs, BUS_DMA_NOWAIT); - if (error) { - printf("%s: can't allocate DMA mem\n", sc->sc_dev.dv_xname); - return (error); + /* Allocate rings below 1GB so 30bit devices can access them.*/ + rd->rdata_desc = (caddr_t)km_alloc(size, &kv_intrsafe, &bwi_pa_mode, + &kd_nowait); + if (rd->rdata_desc == NULL) { + printf(": could not allocate ring DMA memory\n"); + return (ENOMEM); } - error = bus_dmamem_map(sc->sc_dmat, &rd->rdata_seg, nsegs, - size, (caddr_t *)&rd->rdata_desc, BUS_DMA_NOWAIT); + error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, + BUS_DMA_NOWAIT, &rd->rdata_dmap); if (error) { - printf("%s: can't map DMA mem\n", sc->sc_dev.dv_xname); + printf(": cannot create ring DMA map (error %d)\n", error); + km_free(rd->rdata_desc, size, &kv_intrsafe, &bwi_pa_mode); + rd->rdata_desc = NULL; return (error); } @@ -7682,11 +7727,13 @@ bwi_dma_ring_alloc(struct bwi_softc *sc, size, NULL, BUS_DMA_WAITOK); if (error) { printf("%s: can't load DMA mem\n", sc->sc_dev.dv_xname); - bus_dmamem_free(sc->sc_dmat, &rd->rdata_seg, nsegs); + bus_dmamap_destroy(sc->sc_dmat, rd->rdata_dmap); + km_free(rd->rdata_desc, size, &kv_intrsafe, &bwi_pa_mode); rd->rdata_desc = NULL; return (error); } + rd->rdata_ring_sz = size; rd->rdata_paddr = rd->rdata_dmap->dm_segs[0].ds_addr; rd->rdata_txrx_ctrl = txrx_ctrl; @@ -7805,6 +7852,126 @@ bwi_dma_txstats_free(struct bwi_softc *sc) } int +bwi_dma_mbuf_create30(struct bwi_softc *sc) +{ + int i, j, k, error; + + for (i = 0; i < BWI_TX_NRING; ++i) { + struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[i]; + + sc->sc_bounce_tx_data[i] = (caddr_t)km_alloc( + BWI_TX_NDESC * MCLBYTES, &kv_intrsafe, + &bwi_pa_mode, &kd_waitok); + if (sc->sc_bounce_tx_data[i] == NULL) { + printf(": could not allocate TX mbuf bounce buffer\n"); + error = ENOMEM; + break; + } + + for (j = 0; j < BWI_TX_NDESC; ++j) { + error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, + 1, MCLBYTES, 0, BUS_DMA_NOWAIT, + &tbd->tbd_buf[j].tb_dmap); + if (error) { + printf(": cannot create TX mbuf DMA map\n"); + for (k = 0; k < j; ++k) { + bus_dmamap_destroy(sc->sc_dmat, + tbd->tbd_buf[k].tb_dmap); + } + break; + } + } + } + if (error) { + bwi_dma_mbuf_destroy(sc, i, 0); + for (j = 0; j < i; ++j) + km_free(sc->sc_bounce_tx_data[j], BWI_TX_NDESC, + &kv_intrsafe, &bwi_pa_mode); + return (error); + } + + for (i = 0; i < BWI_TX_NRING; ++i) { + struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[i]; + + for (j = 0; j < BWI_TX_NDESC; ++j) { + struct bwi_txbuf *tb = &tbd->tbd_buf[j]; + + error = bus_dmamap_load(sc->sc_dmat, tb->tb_dmap, + sc->sc_bounce_tx_data[i] + (MCLBYTES * j), + MCLBYTES, NULL, BUS_DMA_NOWAIT); + if (error) { + printf(": cannot create TX mbuf DMA map\n"); + for (k = 0; k < j; ++k) { + bus_dmamap_destroy(sc->sc_dmat, + tbd->tbd_buf[k].tb_dmap); + } + break; + } + } + } + if (error) { + bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 0); + for (i = 0; i < BWI_TX_NRING; ++i) + km_free(sc->sc_bounce_tx_data[i], BWI_TX_NDESC, + &kv_intrsafe, &bwi_pa_mode); + return (error); + } + + sc->sc_bounce_rx_data = (caddr_t)km_alloc(BWI_RX_NDESC * MCLBYTES, + &kv_intrsafe, &bwi_pa_mode, &kd_waitok); + if (sc->sc_bounce_rx_data == NULL) { + printf(": could not allocate RX mbuf bounce buffer\n"); + bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 0); + for (i = 0; i < BWI_TX_NRING; ++i) + km_free(sc->sc_bounce_tx_data[i], BWI_TX_NDESC, + &kv_intrsafe, &bwi_pa_mode); + return (ENOMEM); + } + + for (i = 0; i < BWI_RX_NDESC; ++i) { + error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, + MCLBYTES, 0, BUS_DMA_NOWAIT, + &sc->sc_rx_bdata.rbd_buf[i].rb_dmap); + if (error) { + printf(": cannot create RX mbuf DMA map\n"); + for (j = 0; j < i; ++j) { + bus_dmamap_destroy(sc->sc_dmat, + sc->sc_rx_bdata.rbd_buf[j].rb_dmap); + } + break; + } + } + if (error) { + bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 0); + for (i = 0; i < BWI_TX_NRING; ++i) + km_free(sc->sc_bounce_tx_data[i], BWI_TX_NDESC, + &kv_intrsafe, &bwi_pa_mode); + km_free(sc->sc_bounce_rx_data, BWI_RX_NDESC * MCLBYTES, + &kv_intrsafe, &bwi_pa_mode); + return (error); + } + + for (i = 0; i < BWI_RX_NDESC; ++i) { + error = bwi_newbuf30(sc, i, 1); + if (error) { + printf(": cannot create RX mbuf DMA map\n"); + break; + } + } + if (error) { + bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 1); + for (i = 0; i < BWI_TX_NRING; ++i) + km_free(sc->sc_bounce_tx_data[i], BWI_TX_NDESC, + &kv_intrsafe, &bwi_pa_mode); + km_free(sc->sc_bounce_rx_data, BWI_RX_NDESC * MCLBYTES, + &kv_intrsafe, &bwi_pa_mode); + return (error); + } + + return (0); +} + +int bwi_dma_mbuf_create(struct bwi_softc *sc) { struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; @@ -7986,9 +8153,10 @@ bwi_init_rx_ring32(struct bwi_softc *sc) int i, error; sc->sc_rx_bdata.rbd_idx = 0; + bzero(rd->rdata_desc, sizeof(struct bwi_desc32) * BWI_RX_NDESC); for (i = 0; i < BWI_RX_NDESC; ++i) { - error = bwi_newbuf(sc, i, 1); + error = sc->sc_newbuf(sc, i, 1); if (error) { printf("%s: can't allocate %dth RX buffer\n", sc->sc_dev.dv_xname, i); @@ -8087,6 +8255,58 @@ bwi_setup_tx_desc64(struct bwi_softc *sc, struct bwi_ring_data *rd, } int +bwi_newbuf30(struct bwi_softc *sc, int buf_idx, int init) +{ + struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; + struct bwi_rxbuf *rb = &rbd->rbd_buf[buf_idx]; + struct mbuf *m; + struct bwi_rxbuf_hdr *hdr; + int error; + + KASSERT(buf_idx < BWI_RX_NDESC); + + /* Create host-side mbuf. */ + MGETHDR(m, init ? M_WAITOK : M_NOWAIT, MT_DATA); + if (m == NULL) + return (ENOBUFS); + MCLGET(m, init ? M_WAITOK : M_NOWAIT); + if (m == NULL) + return (ENOBUFS); + m->m_len = m->m_pkthdr.len = MCLBYTES; + + if (init) { + /* Load device-side RX DMA buffer. */ + error = bus_dmamap_load(sc->sc_dmat, rb->rb_dmap, + sc->sc_bounce_rx_data + (MCLBYTES * buf_idx), + MCLBYTES, NULL, BUS_DMA_WAITOK); + if (error) { + m_freem(m); + return (error); + } + } + + rb->rb_mbuf = m; + rb->rb_paddr = rb->rb_dmap->dm_segs[0].ds_addr; + + /* + * Clear RX buf header + */ + hdr = (struct bwi_rxbuf_hdr *)(sc->sc_bounce_rx_data + + (MCLBYTES * buf_idx)); + bzero(hdr, sizeof(*hdr)); + bus_dmamap_sync(sc->sc_dmat, rb->rb_dmap, 0, + rb->rb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE); + + /* + * Setup RX buf descriptor + */ + sc->sc_setup_rxdesc(sc, buf_idx, rb->rb_paddr, + m->m_len - sizeof(*hdr)); + + return (0); +} + +int bwi_newbuf(struct bwi_softc *sc, int buf_idx, int init) { struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; @@ -8233,11 +8453,22 @@ bwi_rxeof(struct bwi_softc *sc, int end_idx) uint16_t flags2; int buflen, wh_ofs, hdr_extra, rssi, type, rate; - m = rb->rb_mbuf; bus_dmamap_sync(sc->sc_dmat, rb->rb_dmap, 0, rb->rb_dmap->dm_mapsize, BUS_DMASYNC_POSTREAD); - if (bwi_newbuf(sc, idx, 0)) { + if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT) { + /* Bounce for 30bit devices. */ + if (m_copyback(rb->rb_mbuf, 0, MCLBYTES, + sc->sc_bounce_rx_data + (MCLBYTES * idx), + M_NOWAIT) == ENOBUFS) { + ifp->if_ierrors++; + goto next; + } + } + + m = rb->rb_mbuf; + + if (sc->sc_newbuf(sc, idx, 0)) { ifp->if_ierrors++; goto next; } @@ -8835,30 +9066,37 @@ bwi_encap(struct bwi_softc *sc, int idx, struct mbuf *m, hdr = NULL; wh = NULL; - /* DMA load */ - error = bus_dmamap_load_mbuf(sc->sc_dmat, tb->tb_dmap, m, - BUS_DMA_NOWAIT); - if (error && error != EFBIG) { - printf("%s: can't load TX buffer (1) %d\n", - sc->sc_dev.dv_xname, error); - goto back; - } - - if (error) { /* error == EFBIG */ - if (m_defrag(m, M_DONTWAIT)) { - printf("%s: can't defrag TX buffer\n", - sc->sc_dev.dv_xname); - goto back; - } + if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT) { + /* Bounce for 30bit devices. */ + m_copydata(m, 0, m->m_pkthdr.len, + sc->sc_bounce_tx_data[BWI_TX_DATA_RING] + + (MCLBYTES * idx)); + } else { + /* DMA load */ error = bus_dmamap_load_mbuf(sc->sc_dmat, tb->tb_dmap, m, BUS_DMA_NOWAIT); - if (error) { - printf("%s: can't load TX buffer (2) %d\n", + if (error && error != EFBIG) { + printf("%s: can't load TX buffer (1) %d\n", sc->sc_dev.dv_xname, error); goto back; } + + if (error) { /* error == EFBIG */ + if (m_defrag(m, M_DONTWAIT)) { + printf("%s: can't defrag TX buffer\n", + sc->sc_dev.dv_xname); + goto back; + } + error = bus_dmamap_load_mbuf(sc->sc_dmat, tb->tb_dmap, + m, BUS_DMA_NOWAIT); + if (error) { + printf("%s: can't load TX buffer (2) %d\n", + sc->sc_dev.dv_xname, error); + goto back; + } + } + error = 0; } - error = 0; bus_dmamap_sync(sc->sc_dmat, tb->tb_dmap, 0, tb->tb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE); diff --git a/sys/dev/ic/bwivar.h b/sys/dev/ic/bwivar.h index 1489e552af6..d55a72f9140 100644 --- a/sys/dev/ic/bwivar.h +++ b/sys/dev/ic/bwivar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: bwivar.h,v 1.29 2013/12/06 21:03:02 deraadt Exp $ */ +/* $OpenBSD: bwivar.h,v 1.30 2014/07/20 11:59:12 stsp Exp $ */ /* * Copyright (c) 2007 The DragonFly Project. All rights reserved. @@ -195,7 +195,7 @@ struct bwi_txstats { struct bwi_ring_data { uint32_t rdata_txrx_ctrl; - bus_dma_segment_t rdata_seg; + bus_size_t rdata_ring_sz; bus_dmamap_t rdata_dmap; bus_addr_t rdata_paddr; void *rdata_desc; @@ -556,6 +556,10 @@ struct bwi_softc { enum bwi_bus_space sc_bus_space; + /* bounce buffers for 30 bit bus space devices */ + caddr_t sc_bounce_tx_data[BWI_TX_NRING]; + caddr_t sc_bounce_rx_data; + struct bwi_txbuf_data sc_tx_bdata[BWI_TX_NRING]; struct bwi_rxbuf_data sc_rx_bdata; @@ -575,6 +579,7 @@ struct bwi_softc { int (*sc_init_rx_ring)(struct bwi_softc *); void (*sc_free_rx_ring)(struct bwi_softc *); + int (*sc_newbuf)(struct bwi_softc *, int, int); int (*sc_init_txstats)(struct bwi_softc *); void (*sc_free_txstats)(struct bwi_softc *); |