bus_dma(9)-ification of vr(4): help?
Bruce M Simpson
bms at spc.org
Fri Jul 2 23:05:15 PDT 2004
Hi all,
I've begun merging NetBSD's changes to vr(4) for bus_dma(9)-ification of
the driver. However I have run into a few problems.
I have extracted diffs against HEAD (as of my last commit rev 1.89) of
if_vr.c and if_vrreg.h of the merging work I've done so far which has
been mostly by hand from NetBSD's rev 1.18, when bus_dma-ification was
first introduced to vr(4) in their tree.
The main problem here seems to be that NetBSD's version makes extensive
use of their bus_dmamap_sync() API which accepts region (offset, length)
arguments to be synchronized within the map.
I'd appreciate any help from someone more familiar with the bus_dma(9) API
than I in finishing off this work as I'm in a position to test (my local
CVS mirror / main development machine uses vr(4) at the moment).
There are some further whitespace/style changes in there which can
probably be weeded out easily before the merge proper would happen in CVS.
Faithfully,
BMS
-------------- next part --------------
Index: if_vr.c
===================================================================
RCS file: /home/ncvs/src/sys/pci/if_vr.c,v
retrieving revision 1.89
diff -u -r1.89 if_vr.c
--- if_vr.c 3 Jul 2004 02:59:02 -0000 1.89
+++ if_vr.c 3 Jul 2004 05:59:28 -0000
@@ -77,8 +77,8 @@
#include <net/bpf.h>
-#include <vm/vm.h> /* for vtophys */
-#include <vm/pmap.h> /* for vtophys */
+#include <vm/vm.h>
+#include <vm/pmap.h>
#include <machine/bus_pio.h>
#include <machine/bus_memio.h>
#include <machine/bus.h>
@@ -130,12 +130,7 @@
static int vr_attach (device_t);
static int vr_detach (device_t);
-static int vr_newbuf (struct vr_softc *,
- struct vr_chain_onefrag *,
- struct mbuf *);
-static int vr_encap (struct vr_softc *, struct vr_chain *,
- struct mbuf * );
-
+static int vr_add_rxbuf (struct vr_softc *, int);
static void vr_rxeof (struct vr_softc *);
static void vr_rxeoc (struct vr_softc *);
static void vr_txeof (struct vr_softc *);
@@ -163,8 +158,6 @@
static void vr_setcfg (struct vr_softc *, int);
static void vr_setmulti (struct vr_softc *);
static void vr_reset (struct vr_softc *);
-static int vr_list_rx_init (struct vr_softc *);
-static int vr_list_tx_init (struct vr_softc *);
#ifdef VR_USEIOSPACE
#define VR_RES SYS_RES_IOPORT
@@ -640,11 +633,11 @@
vr_attach(dev)
device_t dev;
{
- int i;
- u_char eaddr[ETHER_ADDR_LEN];
+ u_char eaddr[ETHER_ADDR_LEN];
+ bus_dma_segment_t seg;
struct vr_softc *sc;
struct ifnet *ifp;
- int unit, error = 0, rid;
+ int error = 0, i, rid, rseg, unit;
sc = device_get_softc(dev);
unit = device_get_unit(dev);
@@ -715,17 +708,6 @@
sc->vr_unit = unit;
bcopy(eaddr, (char *)&sc->arpcom.ac_enaddr, ETHER_ADDR_LEN);
- sc->vr_ldata = contigmalloc(sizeof(struct vr_list_data), M_DEVBUF,
- M_NOWAIT, 0, 0xffffffff, PAGE_SIZE, 0);
-
- if (sc->vr_ldata == NULL) {
- printf("vr%d: no memory for list buffers!\n", unit);
- error = ENXIO;
- goto fail;
- }
-
- bzero(sc->vr_ldata, sizeof(struct vr_list_data));
-
ifp = &sc->arpcom.ac_if;
ifp->if_softc = sc;
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
@@ -742,6 +724,85 @@
#endif
ifp->if_capenable = ifp->if_capabilities;
+ /* XXX needs bus_dmamap_create() */
+ sc->vr_dmat = NULL;
+ /* goto fail_0; */
+
+ /*
+ * Allocate the control data structures, and create and load
+ * the DMA map for it.
+ */
+ if ((error = bus_dmamem_alloc(sc->vr_dmat,
+ sizeof(struct vr_control_data), PAGE_SIZE, 0, &seg, 1, &rseg,
+ 0)) != 0) {
+ if_printf(ifp, "unable to allocate control data, error = %d\n",
+ error);
+ error = ENXIO;
+ goto fail_1;
+ }
+
+ if ((error = bus_dmamem_map(sc->vr_dmat, &seg, rseg,
+ sizeof(struct vr_control_data), (caddr_t *)&sc->vr_control_data,
+ BUS_DMA_COHERENT)) != 0) {
+ if_printf(ifp, "unable to map control data, error = %d\n",
+ error);
+ goto fail_2;
+ }
+
+ if ((error = bus_dmamap_create(sc->vr_dmat,
+ sizeof(struct vr_control_data), 1,
+ sizeof(struct vr_control_data), 0, 0,
+ &sc->vr_cddmamap)) != 0) {
+ if_printf(ifp,
+"unable to create control data DMA map, error = %d\n", error);
+ goto fail_3;
+ }
+
+ if ((error = bus_dmamap_load(sc->vr_dmat, sc->vr_cddmamap,
+ sc->vr_control_data, sizeof(struct vr_control_data), NULL,
+ 0)) != 0) {
+ if_printf(ifp,
+"unable to load control data DMA map, error = %d\n", error);
+ goto fail_4;
+ }
+
+ /*
+ * Create the transmit buffer DMA maps.
+ */
+ for (i = 0; i < VR_NTXDESC; i++) {
+ if ((error = bus_dmamap_create(sc->vr_dmat, MCLBYTES,
+ 1, MCLBYTES, 0, 0,
+ &VR_DSTX(sc, i)->ds_dmamap)) != 0) {
+ if_printf(ifp,
+"unable to create tx DMA map %d, error = %d\n", i, error);
+ goto fail_5;
+ }
+ }
+
+ /*
+ * Create the receive buffer DMA maps.
+ */
+ for (i = 0; i < VR_NRXDESC; i++) {
+ if ((error = bus_dmamap_create(sc->vr_dmat, MCLBYTES, 1,
+ MCLBYTES, 0, 0,
+ &VR_DSRX(sc, i)->ds_dmamap)) != 0) {
+ if_printf(ifp,
+"unable to create rx DMA map %d, error = %d\n", i, error);
+ goto fail_6;
+ }
+ }
+
+ /*
+ * Pre-allocate the receive buffers.
+ */
+ for (i = 0; i < VR_NRXDESC; i++) {
+ if ((error = vr_add_rxbuf(sc, i)) != 0) {
+ if_printf(ifp,
+"unable to allocate or map rx buffer %d, error = %d\n", i, error);
+ goto fail_7;
+ }
+ }
+
/* Do MII setup. */
if (mii_phy_probe(dev, &sc->vr_miibus,
vr_ifmedia_upd, vr_ifmedia_sts)) {
@@ -765,6 +826,39 @@
goto fail;
}
+fail_7:
+ for (i = 0; i < VR_NRXDESC; i++) {
+ if (sc->vr_rxsoft[i].ds_mbuf != NULL) {
+ bus_dmamap_unload(sc->vr_dmat,
+ sc->vr_rxsoft[i].ds_dmamap);
+ (void)m_freem(sc->vr_rxsoft[i].ds_mbuf);
+ }
+ }
+fail_6:
+ for (i = 0; i < VR_NRXDESC; i++) {
+ if (sc->vr_rxsoft[i].ds_dmamap != NULL)
+ bus_dmamap_destroy(sc->vr_dmat,
+ sc->vr_rxsoft[i].ds_dmamap);
+ }
+fail_5:
+ for (i = 0; i < VR_NTXDESC; i++) {
+ if (sc->vr_txsoft[i].ds_dmamap != NULL)
+ bus_dmamap_destroy(sc->vr_dmat,
+ sc->vr_txsoft[i].ds_dmamap);
+ }
+ bus_dmamap_unload(sc->vr_dmat, sc->vr_cddmamap);
+fail_4:
+ bus_dmamap_destroy(sc->vr_dmat, sc->vr_cddmamap);
+fail_3:
+ bus_dmamem_unmap(sc->vr_dmat, (caddr_t)sc->vr_control_data,
+ sizeof(struct vr_control_data));
+fail_2:
+ bus_dmamem_free(sc->vr_dmat, &seg, rseg);
+fail_1:
+ /* XXX */
+fail_0:
+ /* XXX */
+
fail:
if (error)
vr_detach(dev);
@@ -815,73 +909,6 @@
}
/*
- * Initialize the transmit descriptors.
- */
-static int
-vr_list_tx_init(struct vr_softc *sc)
-{
- struct vr_chain_data *cd;
- struct vr_list_data *ld;
- int i;
-
- cd = &sc->vr_cdata;
- ld = sc->vr_ldata;
- for (i = 0; i < VR_TX_LIST_CNT; i++) {
- cd->vr_tx_chain[i].vr_ptr = &ld->vr_tx_list[i];
- if (i == (VR_TX_LIST_CNT - 1))
- cd->vr_tx_chain[i].vr_nextdesc =
- &cd->vr_tx_chain[0];
- else
- cd->vr_tx_chain[i].vr_nextdesc =
- &cd->vr_tx_chain[i + 1];
- }
- cd->vr_tx_cons = cd->vr_tx_prod = &cd->vr_tx_chain[0];
-
- return (0);
-}
-
-
-/*
- * Initialize the RX descriptors and allocate mbufs for them. Note that
- * we arrange the descriptors in a closed ring, so that the last descriptor
- * points back to the first.
- */
-static int
-vr_list_rx_init(struct vr_softc *sc)
-{
- struct vr_chain_data *cd;
- struct vr_list_data *ld;
- int i;
-
- VR_LOCK_ASSERT(sc);
-
- cd = &sc->vr_cdata;
- ld = sc->vr_ldata;
-
- for (i = 0; i < VR_RX_LIST_CNT; i++) {
- cd->vr_rx_chain[i].vr_ptr =
- (struct vr_desc *)&ld->vr_rx_list[i];
- if (vr_newbuf(sc, &cd->vr_rx_chain[i], NULL) == ENOBUFS)
- return (ENOBUFS);
- if (i == (VR_RX_LIST_CNT - 1)) {
- cd->vr_rx_chain[i].vr_nextdesc =
- &cd->vr_rx_chain[0];
- ld->vr_rx_list[i].vr_next =
- vtophys(&ld->vr_rx_list[0]);
- } else {
- cd->vr_rx_chain[i].vr_nextdesc =
- &cd->vr_rx_chain[i + 1];
- ld->vr_rx_list[i].vr_next =
- vtophys(&ld->vr_rx_list[i + 1]);
- }
- }
-
- cd->vr_rx_head = &cd->vr_rx_chain[0];
-
- return (0);
-}
-
-/*
* Initialize an RX descriptor and attach an MBUF cluster.
* Note: the length fields are only 11 bits wide, which means the
* largest size we can specify is 2047. This is important because
@@ -889,33 +916,36 @@
* overflow the field and make a mess.
*/
static int
-vr_newbuf(struct vr_softc *sc, struct vr_chain_onefrag *c, struct mbuf *m)
+vr_add_rxbuf(struct vr_softc *sc, int i)
{
- struct mbuf *m_new = NULL;
+ struct vr_descsoft *ds = VR_DSRX(sc, i);
+ struct mbuf *m_new;
+ int error;
- if (m == NULL) {
- MGETHDR(m_new, M_DONTWAIT, MT_DATA);
- if (m_new == NULL)
- return (ENOBUFS);
+ MGETHDR(m_new, M_DONTWAIT, MT_DATA);
+ if (m_new == NULL)
+ return (ENOBUFS);
- MCLGET(m_new, M_DONTWAIT);
- if (!(m_new->m_flags & M_EXT)) {
- m_freem(m_new);
- return (ENOBUFS);
- }
- m_new->m_len = m_new->m_pkthdr.len = MCLBYTES;
- } else {
- m_new = m;
- m_new->m_len = m_new->m_pkthdr.len = MCLBYTES;
- m_new->m_data = m_new->m_ext.ext_buf;
+ MCLGET(m_new, M_DONTWAIT);
+ if ((m_new->m_flags & M_EXT) == 0) {
+ m_freem(m_new);
+ return (ENOBUFS);
}
- m_adj(m_new, sizeof(uint64_t));
+ if (ds->ds_mbuf != NULL)
+ bus_dmamap_unload(sc->vr_dmat, ds->ds_dmamap);
+
+ ds->ds_mbuf = m_new;
+
+ error = bus_dmamap_load(sc->vr_dmat, ds->ds_dmamap,
+ m_new->m_ext.ext_buf, m_new->m_ext.ext_size, NULL, BUS_DMA_NOWAIT);
+ if (error)
+ if_printf(&sc->arpcom.ac_if,
+"unable to load rx DMA map %d, error = %d\n", i, error);
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap, 0,
+ ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
- c->vr_mbuf = m_new;
- c->vr_ptr->vr_status = VR_RXSTAT;
- c->vr_ptr->vr_data = vtophys(mtod(m_new, caddr_t));
- c->vr_ptr->vr_ctl = VR_RXCTL | VR_RXLEN;
+ VR_INIT_RXDESC(sc, i);
return (0);
}
@@ -929,26 +959,35 @@
{
struct mbuf *m, *m0;
struct ifnet *ifp;
- struct vr_chain_onefrag *cur_rx;
- int total_len = 0;
+ struct vr_desc *d;
+ struct vr_descsoft *ds;
+ int i, total_len;
uint32_t rxstat;
VR_LOCK_ASSERT(sc);
ifp = &sc->arpcom.ac_if;
- while (!((rxstat = sc->vr_cdata.vr_rx_head->vr_ptr->vr_status) &
- VR_RXSTAT_OWN)) {
+ for (i = sc->vr_rxptr; ; i = VR_NEXTRX(i)) {
#ifdef DEVICE_POLLING
if (ifp->if_flags & IFF_POLLING) {
if (sc->rxcycles <= 0)
break;
sc->rxcycles--;
}
-#endif /* DEVICE_POLLING */
- m0 = NULL;
- cur_rx = sc->vr_cdata.vr_rx_head;
- sc->vr_cdata.vr_rx_head = cur_rx->vr_nextdesc;
- m = cur_rx->vr_mbuf;
+#endif
+ d = VR_CDRX(sc, i);
+ ds = VR_DSRX(sc, i);
+
+ VR_CDRXSYNC(sc, i, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
+
+ rxstat = d->vr_status;
+
+ if (rxstat & VR_RXSTAT_OWN) {
+ /*
+ * We have processed all of the receive buffers.
+ */
+ break;
+ }
/*
* If an error occurs, update stats, clear the
@@ -958,7 +997,7 @@
*/
if (rxstat & VR_RXSTAT_RXERR) {
ifp->if_ierrors++;
- printf("vr%d: rx error (%02x):", sc->vr_unit,
+ if_printf(ifp, "rx error (%02x):",
rxstat & 0x000000ff);
if (rxstat & VR_RXSTAT_CRCERR)
printf(" crc error");
@@ -975,12 +1014,15 @@
if (rxstat & VR_RXSTAT_BUFFERR)
printf("rx buffer error");
printf("\n");
- vr_newbuf(sc, cur_rx, m);
+ VR_INIT_RXDESC(sc, i);
continue;
}
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap, 0,
+ ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);
+
/* No errors; receive the packet. */
- total_len = VR_RXBYTES(cur_rx->vr_ptr->vr_status);
+ total_len = VR_RXBYTES(d->vr_status);
/*
* XXX The VIA Rhine chip includes the CRC with every
@@ -991,20 +1033,52 @@
*/
total_len -= ETHER_CRC_LEN;
- m0 = m_devget(mtod(m, char *), total_len, ETHER_ALIGN, ifp,
- NULL);
- vr_newbuf(sc, cur_rx, m);
- if (m0 == NULL) {
+ /*
+ * The Rhine's packet buffers must be 4-byte aligned.
+ * But this means that the data after the Ethernet header
+ * is misaligned. We must allocate a new buffer and
+ * copy the data, shifted forward 2 bytes.
+ */
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+dropit:
ifp->if_ierrors++;
+ VR_INIT_RXDESC(sc, i);
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap, 0,
+ ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
continue;
}
- m = m0;
+ if (total_len > (MHLEN - 2)) {
+ MCLGET(m, M_DONTWAIT);
+ if ((m->m_flags & M_EXT) == 0) {
+ m_freem(m);
+ goto dropit;
+ }
+ }
+ m->m_data += 2;
+
+ /*
+ * Note that we use clusters for incoming frames, so the
+ * buffer is virtually contiguous.
+ */
+ bcopy(mtod(ds->ds_mbuf, char *), mtod(m, char *), total_len);
+
+ /* Allow the receive descriptor to continue using its mbuf. */
+ VR_INIT_RXDESC(sc, i);
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap, 0,
+ ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
ifp->if_ipackets++;
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = m->m_len = total_len;
+
VR_UNLOCK(sc);
(*ifp->if_input)(ifp, m);
VR_LOCK(sc);
}
+
+ /* Update the receive pointer. */
+ sc->vr_rxptr = i;
}
static void
@@ -1035,7 +1109,7 @@
vr_rxeof(sc);
- CSR_WRITE_4(sc, VR_RXADDR, vtophys(sc->vr_cdata.vr_rx_head->vr_ptr));
+ CSR_WRITE_4(sc, VR_RXADDR, VR_CDRXADDR(sc, sc->vr_rxptr));
VR_SETBIT16(sc, VR_COMMAND, VR_CMD_RX_ON);
VR_SETBIT16(sc, VR_COMMAND, VR_CMD_RX_GO);
}
@@ -1047,8 +1121,11 @@
static void
vr_txeof(struct vr_softc *sc)
{
- struct vr_chain *cur_tx;
struct ifnet *ifp = &sc->arpcom.ac_if;
+ struct vr_desc *d;
+ struct vr_descsoft *ds;
+ u_int32_t txstat;
+ int i;
VR_LOCK_ASSERT(sc);
@@ -1056,12 +1133,14 @@
* Go through our tx list and free mbufs for those
* frames that have been transmitted.
*/
- cur_tx = sc->vr_cdata.vr_tx_cons;
- while (cur_tx->vr_mbuf != NULL) {
- uint32_t txstat;
- int i;
+ for (i = sc->vr_txdirty; sc->vr_txpending != 0;
+ i = VR_NEXTTX(i), sc->vr_txpending--) {
+ d = VR_CDTX(sc, i);
+ ds = VR_DSTX(sc, i);
+
+ VR_CDTXSYNC(sc, i, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
- txstat = cur_tx->vr_ptr->vr_status;
+ txstat = d->vr_status;
if ((txstat & VR_TXSTAT_ABRT) ||
(txstat & VR_TXSTAT_UDF)) {
@@ -1083,6 +1162,12 @@
if (txstat & VR_TXSTAT_OWN)
break;
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap,
+ 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->vr_dmat, ds->ds_dmamap);
+ m_freem(ds->ds_mbuf);
+ ds->ds_mbuf = NULL;
+
if (txstat & VR_TXSTAT_ERRSUM) {
ifp->if_oerrors++;
if (txstat & VR_TXSTAT_DEFER)
@@ -1091,17 +1176,19 @@
ifp->if_collisions++;
}
- ifp->if_collisions +=(txstat & VR_TXSTAT_COLLCNT) >> 3;
-
+ ifp->if_collisions += (txstat & VR_TXSTAT_COLLCNT) >> 3;
ifp->if_opackets++;
- m_freem(cur_tx->vr_mbuf);
- cur_tx->vr_mbuf = NULL;
ifp->if_flags &= ~IFF_OACTIVE;
-
- cur_tx = cur_tx->vr_nextdesc;
}
- sc->vr_cdata.vr_tx_cons = cur_tx;
- if (cur_tx->vr_mbuf == NULL)
+
+ /* Update the dirty transmit buffer pointer. */
+ sc->vr_txdirty = i;
+
+ /*
+ * Cancel the watchdog timer if there are no pending
+ * transmissions.
+ */
+ if (sc->vr_txpending == 0)
ifp->if_timer = 0;
}
@@ -1217,6 +1304,7 @@
struct vr_softc *sc = arg;
struct ifnet *ifp = &sc->arpcom.ac_if;
uint16_t status;
+ int handled = 0, dotx = 0;
VR_LOCK(sc);
#ifdef DEVICE_POLLING
@@ -1234,7 +1322,7 @@
}
#endif /* DEVICE_POLLING */
- /* Supress unwanted interrupts. */
+ /* Suppress unwanted interrupts. */
if (!(ifp->if_flags & IFF_UP)) {
vr_stop(sc);
VR_UNLOCK(sc);
@@ -1256,14 +1344,13 @@
vr_rxeof(sc);
if (status & VR_ISR_RX_DROPPED) {
- printf("vr%d: rx packet lost\n", sc->vr_unit);
+ if_printf(ifp, "rx packet lost\n");
ifp->if_ierrors++;
}
if ((status & VR_ISR_RX_ERR) || (status & VR_ISR_RX_NOBUF) ||
(status & VR_ISR_RX_NOBUF) || (status & VR_ISR_RX_OFLOW)) {
- printf("vr%d: receive error (%04x)",
- sc->vr_unit, status);
+ if_printf(ifp, "receive error (%04x)", status);
if (status & VR_ISR_RX_NOBUF)
printf(" no buffers");
if (status & VR_ISR_RX_OFLOW)
@@ -1274,135 +1361,197 @@
vr_rxeoc(sc);
}
- if ((status & VR_ISR_BUSERR) || (status & VR_ISR_TX_UNDERRUN)) {
+ if (status & VR_ISR_TX_OK) {
+ dotx = 1;
+ vr_txeof(sc);
+ }
+
+ if (status & (VR_ISR_TX_ABRT | VR_ISR_TX_ABRT2 | VR_ISR_UDFI)) {
+ ifp->if_oerrors++;
+ dotx = 1;
+ vr_txeof(sc);
+ if (sc->vr_txpending) {
+ VR_SETBIT16(sc, VR_COMMAND, VR_CMD_TX_ON);
+ VR_SETBIT16(sc, VR_COMMAND, VR_CMD_TX_GO);
+ }
+ }
+
+ if (status & (VR_ISR_BUSERR | VR_ISR_TX_UNDERRUN)) {
+ dotx = 0;
vr_reset(sc);
VR_UNLOCK(sc);
vr_init(sc);
VR_LOCK(sc);
break;
}
-
- if ((status & VR_ISR_TX_OK) || (status & VR_ISR_TX_ABRT) ||
- (status & VR_ISR_TX_ABRT2) || (status & VR_ISR_UDFI)) {
- vr_txeof(sc);
- if ((status & VR_ISR_UDFI) ||
- (status & VR_ISR_TX_ABRT2) ||
- (status & VR_ISR_TX_ABRT)) {
- ifp->if_oerrors++;
- if (sc->vr_cdata.vr_tx_cons->vr_mbuf != NULL) {
- VR_SETBIT16(sc, VR_COMMAND,
- VR_CMD_TX_ON);
- VR_SETBIT16(sc, VR_COMMAND,
- VR_CMD_TX_GO);
- }
- }
- }
}
/* Re-enable interrupts. */
CSR_WRITE_2(sc, VR_IMR, VR_INTRS);
VR_UNLOCK(sc);
- if (_IF_QLEN(&ifp->if_snd) != 0)
+ if (dotx || (_IF_QLEN(&ifp->if_snd) != 0))
vr_start(ifp);
}
/*
- * Encapsulate an mbuf chain in a descriptor by coupling the mbuf data
- * pointers to the fragment pointers.
- */
-static int
-vr_encap(struct vr_softc *sc, struct vr_chain *c, struct mbuf *m_head)
-{
- struct vr_desc *f = NULL;
- struct mbuf *m;
-
- VR_LOCK_ASSERT(sc);
- /*
- * The VIA Rhine wants packet buffers to be longword
- * aligned, but very often our mbufs aren't. Rather than
- * waste time trying to decide when to copy and when not
- * to copy, just do it all the time.
- */
- m = m_defrag(m_head, M_DONTWAIT);
- if (m == NULL)
- return (1);
-
- /*
- * The Rhine chip doesn't auto-pad, so we have to make
- * sure to pad short frames out to the minimum frame length
- * ourselves.
- */
- if (m->m_len < VR_MIN_FRAMELEN) {
- m->m_pkthdr.len += VR_MIN_FRAMELEN - m->m_len;
- m->m_len = m->m_pkthdr.len;
- }
-
- c->vr_mbuf = m;
- f = c->vr_ptr;
- f->vr_data = vtophys(mtod(m, caddr_t));
- f->vr_ctl = m->m_len;
- f->vr_ctl |= VR_TXCTL_TLINK|VR_TXCTL_FIRSTFRAG;
- f->vr_status = 0;
- f->vr_ctl |= VR_TXCTL_LASTFRAG|VR_TXCTL_FINT;
- f->vr_next = vtophys(c->vr_nextdesc->vr_ptr);
-
- return (0);
-}
-
-/*
* Main transmit routine. To avoid having to do mbuf copies, we put pointers
* to the mbuf data regions directly in the transmit lists. We also save a
* copy of the pointers since the transmit list fragment pointers are
* physical addresses.
*/
-
static void
vr_start(struct ifnet *ifp)
{
struct vr_softc *sc = ifp->if_softc;
- struct mbuf *m_head;
- struct vr_chain *cur_tx;
+ struct mbuf *m0, *m;
+ struct vr_desc *d;
+ struct vr_descsoft *ds;
+ int error, firsttx, nexttx, opending;
if (ifp->if_flags & IFF_OACTIVE)
return;
VR_LOCK(sc);
- cur_tx = sc->vr_cdata.vr_tx_prod;
- while (cur_tx->vr_mbuf == NULL) {
- IF_DEQUEUE(&ifp->if_snd, m_head);
- if (m_head == NULL)
- break;
+ opending = sc->vr_txpending;
+ firsttx = VR_NEXTTX(sc->vr_txlast);
- /* Pack the data into the descriptor. */
- if (vr_encap(sc, cur_tx, m_head)) {
- /* Rollback, send what we were able to encap. */
- IF_PREPEND(&ifp->if_snd, m_head);
+ /*
+ * Loop through the send queue, setting up transmit descriptors
+ * until we drain the queue, or use up all available transmit
+ * descriptors.
+ */
+ while (sc->vr_txpending < VR_NTXDESC) {
+ /*
+ * Grab a packet off the queue.
+ */
+ IF_DEQUEUE(&ifp->if_snd, m0);
+ if (m0 == NULL)
break;
+
+ /*
+ * Get the next available transmit descriptor.
+ */
+ nexttx = VR_NEXTTX(sc->vr_txlast);
+ d = VR_CDTX(sc, nexttx);
+ ds = VR_DSTX(sc, nexttx);
+
+ /*
+ * Load the DMA map. If this fails, the packet didn't
+ * fit in one DMA segment, and we need to copy. Note,
+ * the packet must also be aligned.
+ */
+ if ((mtod(m0, bus_addr_t) & 3) != 0 ||
+ bus_dmamap_load_mbuf(sc->vr_dmat, ds->ds_dmamap, m0,
+ BUS_DMA_NOWAIT) != 0) {
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ if_printf(ifp, "unable to allocate tx mbuf\n");
+ IF_PREPEND(&ifp->if_snd, m0);
+ break;
+ }
+ if (m0->m_pkthdr.len > MHLEN) {
+ MCLGET(m, M_DONTWAIT);
+ if ((m->m_flags & M_EXT) == 0) {
+ if_printf(ifp,
+"unable to allocate tx cluster\n");
+ m_freem(m);
+ IF_PREPEND(&ifp->if_snd, m0);
+ break;
+ }
+ }
+ m_copydata(m0, 0, m0->m_pkthdr.len, mtod(m, char *));
+ m->m_pkthdr.len = m->m_len = m0->m_pkthdr.len;
+ m_freem(m0);
+ m0 = m;
+ error = bus_dmamap_load_mbuf(sc->vr_dmat,
+ ds->ds_dmamap, m0, BUS_DMA_NOWAIT);
+ if (error) {
+ if_printf(ifp,
+"unable to load tx buffer, error = %d\n", error);
+ IF_PREPEND(&ifp->if_snd, m0);
+ break;
+ }
}
- VR_TXOWN(cur_tx) = VR_TXSTAT_OWN;
+ /* Sync the DMA map. */
+ bus_dmamap_sync(sc->vr_dmat, ds->ds_dmamap, 0,
+ ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+
+ /*
+ * Store a pointer to the packet so we can free it later.
+ */
+ ds->ds_mbuf = m0;
/*
* If there's a BPF listener, bounce a copy of this frame
* to him.
*/
- BPF_MTAP(ifp, cur_tx->vr_mbuf);
+ BPF_MTAP(ifp, m0);
+
+ /*
+ * Fill in the transmit descriptor. The Rhine
+ * doesn't auto-pad, so we have to do this ourselves.
+ */
+ d->vr_data = ds->ds_dmamap->dm_segs[0].ds_addr;
+ d->vr_ctl = m0->m_pkthdr.len < VR_MIN_FRAMELEN ?
+ VR_MIN_FRAMELEN : m0->m_pkthdr.len;
+ d->vr_ctl |=
+ VR_TXCTL_TLINK|VR_TXCTL_FIRSTFRAG|VR_TXCTL_LASTFRAG;
+
+ /*
+ * If this is the first descriptor we're enqueuing,
+ * don't give it to the Rhine yet. That could cause
+ * a race condition. We'll do it below.
+ */
+ if (nexttx == firsttx)
+ d->vr_status = 0;
+ else
+ d->vr_status = VR_TXSTAT_OWN;
+
+ VR_CDTXSYNC(sc, nexttx,
+ BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
- cur_tx = cur_tx->vr_nextdesc;
+ /* Advance the tx pointer. */
+ sc->vr_txpending++;
+ sc->vr_txlast = nexttx;
}
- if (cur_tx != sc->vr_cdata.vr_tx_prod || cur_tx->vr_mbuf != NULL) {
- sc->vr_cdata.vr_tx_prod = cur_tx;
- /* Tell the chip to start transmitting. */
- VR_SETBIT16(sc, VR_COMMAND, /*VR_CMD_TX_ON|*/ VR_CMD_TX_GO);
+ if (sc->vr_txpending == VR_NTXDESC) {
+ /* No more slots left; notify upper layer. */
+ ifp->if_flags |= IFF_OACTIVE;
+ }
- /* Set a timeout in case the chip goes out to lunch. */
- ifp->if_timer = 5;
+ if (sc->vr_txpending != opending) {
+ /*
+ * We enqueued packets. If the transmitter was idle,
+ * reset the txdirty pointer.
+ */
+ if (opending == 0)
+ sc->vr_txdirty = firsttx;
+
+ /*
+ * Cause a transmit interrupt to happen on the
+ * last packet we enqueued.
+ */
+ VR_CDTX(sc, sc->vr_txlast)->vr_ctl |= VR_TXCTL_FINT;
+ VR_CDTXSYNC(sc, sc->vr_txlast,
+ BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
- if (cur_tx->vr_mbuf != NULL)
- ifp->if_flags |= IFF_OACTIVE;
+ /*
+ * The entire packet chain is set up. Give the
+ * first descriptor to the Rhine now.
+ */
+ VR_CDTX(sc, firsttx)->vr_status = VR_TXSTAT_OWN;
+ VR_CDTXSYNC(sc, firsttx,
+ BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
+
+ /* Start the transmitter. */
+ VR_SETBIT16(sc, VR_COMMAND, VR_CMD_TX_ON|VR_CMD_TX_GO);
+
+ /* Set the watchdog timer in case the chip flakes out. */
+ ifp->if_timer = 5;
}
VR_UNLOCK(sc);
@@ -1414,6 +1563,7 @@
struct vr_softc *sc = xsc;
struct ifnet *ifp = &sc->arpcom.ac_if;
struct mii_data *mii;
+ struct vr_desc *d;
int i;
VR_LOCK(sc);
@@ -1448,17 +1598,28 @@
VR_CLRBIT(sc, VR_TXCFG, VR_TXCFG_TX_THRESH);
VR_SETBIT(sc, VR_TXCFG, VR_TXTHRESH_STORENFWD);
- /* Init circular RX list. */
- if (vr_list_rx_init(sc) == ENOBUFS) {
- printf(
-"vr%d: initialization failed: no memory for rx buffers\n", sc->vr_unit);
- vr_stop(sc);
- VR_UNLOCK(sc);
- return;
- }
+ /*
+ * Initialize the transmit desciptor ring. txlast is initialized
+ * to the end of the list so that it will wrap around to the first
+ * descriptor when the first packet is transmitted.
+ */
+ for (i = 0; i < VR_NTXDESC; i++) {
+ d = VR_CDTX(sc, i);
+ bzero(d, sizeof(struct vr_desc));
+ d->vr_next = VR_CDTXADDR(sc, VR_NEXTTX(i));
+ VR_CDTXSYNC(sc, i, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
+ }
+ sc->vr_txpending = 0;
+ sc->vr_txdirty = 0;
+ sc->vr_txlast = VR_NTXDESC - 1;
- /* Init tx descriptors. */
- vr_list_tx_init(sc);
+ /*
+ * Initialize the receive descriptor ring. The buffers are
+ * already allocated.
+ */
+ for (i = 0; i < VR_NRXDESC; i++)
+ VR_INIT_RXDESC(sc, i);
+ sc->vr_rxptr = 0;
/* If we want promiscuous mode, set the allframes bit. */
if (ifp->if_flags & IFF_PROMISC)
@@ -1477,18 +1638,15 @@
*/
vr_setmulti(sc);
- /*
- * Load the address of the RX list.
- */
- CSR_WRITE_4(sc, VR_RXADDR, vtophys(sc->vr_cdata.vr_rx_head->vr_ptr));
+ /* Give the transmit and recieve rings to the Rhine. */
+ CSR_WRITE_4(sc, VR_RXADDR, VR_CDRXADDR(sc, sc->vr_rxptr));
+ CSR_WRITE_4(sc, VR_TXADDR, VR_CDTXADDR(sc, VR_NEXTTX(sc->vr_txlast)));
/* Enable receiver and transmitter. */
CSR_WRITE_2(sc, VR_COMMAND, VR_CMD_TX_NOPOLL|VR_CMD_START|
VR_CMD_TX_ON|VR_CMD_RX_ON|
VR_CMD_RX_GO);
- CSR_WRITE_4(sc, VR_TXADDR, vtophys(&sc->vr_ldata->vr_tx_list[0]));
-
CSR_WRITE_2(sc, VR_ISR, 0xFFFF);
#ifdef DEVICE_POLLING
/*
@@ -1498,9 +1656,7 @@
CSR_WRITE_2(sc, VR_IMR, 0);
else
#endif /* DEVICE_POLLING */
- /*
- * Enable interrupts.
- */
+ /* Enable interrupts. */
CSR_WRITE_2(sc, VR_IMR, VR_INTRS);
mii_mediachg(mii);
@@ -1552,17 +1708,31 @@
switch (command) {
case SIOCSIFFLAGS:
- if (ifp->if_flags & IFF_UP) {
+ if ((ifp->if_flags & IFF_UP) == 0 &&
+ (ifp->if_flags & IFF_RUNNING) != 0) {
+ /*
+ * If interface is marked down and it is running, then
+ * stop it.
+ */
+ VR_LOCK(sc);
+ vr_stop(sc);
+ VR_UNLOCK(sc);
+ } else if ((ifp->if_flags & IFF_UP) != 0 &&
+ (ifp->if_flags & IFF_RUNNING) == 0) {
+ /*
+ * If interface is marked up and it is stopped, then
+ * start it.
+ */
+ vr_init(sc);
+ } else if ((ifp->if_flags & IFF_UP) != 0) {
+ /*
+ * Reset the interface to pick up changes in any other
+ * flags that affect the hardware state.
+ */
vr_init(sc);
- } else {
- if (ifp->if_flags & IFF_RUNNING) {
- VR_LOCK(sc);
- vr_stop(sc);
- VR_UNLOCK(sc);
- }
}
- error = 0;
break;
+
case SIOCADDMULTI:
case SIOCDELMULTI:
VR_LOCK(sc);
@@ -1570,14 +1740,17 @@
VR_UNLOCK(sc);
error = 0;
break;
+
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
mii = device_get_softc(sc->vr_miibus);
error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command);
break;
+
case SIOCSIFCAP:
ifp->if_capenable = ifr->ifr_reqcap;
break;
+
default:
error = ether_ioctl(ifp, command, data);
break;
@@ -1591,36 +1764,28 @@
{
struct vr_softc *sc = ifp->if_softc;
- VR_LOCK(sc);
+ if_printf(ifp, "watchdog timeout\n");
ifp->if_oerrors++;
- printf("vr%d: watchdog timeout\n", sc->vr_unit);
-
- vr_stop(sc);
- vr_reset(sc);
- VR_UNLOCK(sc);
-
vr_init(sc);
- if (ifp->if_snd.ifq_head != NULL)
- vr_start(ifp);
}
/*
- * Stop the adapter and free any mbufs allocated to the
- * RX and TX lists.
+ * Stop the adapter and free any mbufs allocated to the transmit lists.
*/
static void
vr_stop(struct vr_softc *sc)
{
- register int i;
- struct ifnet *ifp;
+ struct ifnet *ifp = &sc->arpcom.ac_if;
+ struct vr_descsoft *ds;
+ int i;
VR_LOCK_ASSERT(sc);
- ifp = &sc->arpcom.ac_if;
+ /* Mark the interface down and cancel the watchdog timer. */
ifp->if_timer = 0;
+ ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
untimeout(vr_tick, sc, sc->vr_stat_ch);
- ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
#ifdef DEVICE_POLLING
ether_poll_deregister(ifp);
#endif /* DEVICE_POLLING */
@@ -1632,28 +1797,16 @@
CSR_WRITE_4(sc, VR_RXADDR, 0x00000000);
/*
- * Free data in the RX lists.
- */
- for (i = 0; i < VR_RX_LIST_CNT; i++) {
- if (sc->vr_cdata.vr_rx_chain[i].vr_mbuf != NULL) {
- m_freem(sc->vr_cdata.vr_rx_chain[i].vr_mbuf);
- sc->vr_cdata.vr_rx_chain[i].vr_mbuf = NULL;
- }
- }
- bzero((char *)&sc->vr_ldata->vr_rx_list,
- sizeof(sc->vr_ldata->vr_rx_list));
-
- /*
- * Free the TX list buffers.
+ * Release any queued transmit buffers.
*/
- for (i = 0; i < VR_TX_LIST_CNT; i++) {
- if (sc->vr_cdata.vr_tx_chain[i].vr_mbuf != NULL) {
- m_freem(sc->vr_cdata.vr_tx_chain[i].vr_mbuf);
- sc->vr_cdata.vr_tx_chain[i].vr_mbuf = NULL;
+ for (i = 0; i < VR_NTXDESC; i++) {
+ ds = VR_DSTX(sc, i);
+ if (ds->ds_mbuf != NULL) {
+ bus_dmamap_unload(sc->vr_dmat, ds->ds_dmamap);
+ m_freem(ds->ds_mbuf);
+ ds->ds_mbuf = NULL;
}
}
- bzero((char *)&sc->vr_ldata->vr_tx_list,
- sizeof(sc->vr_ldata->vr_tx_list));
}
/*
-------------- next part --------------
Index: if_vrreg.h
===================================================================
RCS file: /home/ncvs/src/sys/pci/if_vrreg.h,v
retrieving revision 1.19
diff -u -r1.19 if_vrreg.h
--- if_vrreg.h 5 Apr 2004 17:39:57 -0000 1.19
+++ if_vrreg.h 3 Jul 2004 05:59:32 -0000
@@ -397,31 +397,42 @@
#define VR_TXOWN(x) x->vr_ptr->vr_status
-struct vr_list_data {
- struct vr_desc vr_rx_list[VR_RX_LIST_CNT];
- struct vr_desc vr_tx_list[VR_TX_LIST_CNT];
-};
+/*
+ * Transmit descriptor list size.
+ */
+#define VR_NTXDESC 64
+#define VR_NTXDESC_MASK (VR_NTXDESC - 1)
+#define VR_NEXTTX(x) (((x) + 1) & VR_NTXDESC_MASK)
-struct vr_chain {
- struct vr_desc *vr_ptr;
- struct mbuf *vr_mbuf;
- struct vr_chain *vr_nextdesc;
-};
+/*
+ * Receive descriptor list size.
+ */
+#define VR_NRXDESC 64
+#define VR_NRXDESC_MASK (VR_NRXDESC - 1)
+#define VR_NEXTRX(x) (((x) + 1) & VR_NRXDESC_MASK)
-struct vr_chain_onefrag {
- struct vr_desc *vr_ptr;
- struct mbuf *vr_mbuf;
- struct vr_chain_onefrag *vr_nextdesc;
+/*
+ * Control data structres that are DMA'd to the Rhine chip. We allocate
+ * them in a single clump that maps to a single DMA segment to make several
+ * things easier.
+ *
+ * Note that since we always copy outgoing packets to aligned transmit
+ * buffers, we can reduce the transmit descriptors to one per packet.
+ */
+struct vr_control_data {
+ struct vr_desc vr_txdescs[VR_NTXDESC];
+ struct vr_desc vr_rxdescs[VR_NRXDESC];
};
+#define VR_CDOFF(x) offsetof(struct vr_control_data, x)
+#define VR_CDTXOFF(x) VR_CDOFF(vr_txdescs[(x)])
+#define VR_CDRXOFF(x) VR_CDOFF(vr_rxdescs[(x)])
-struct vr_chain_data {
- struct vr_chain_onefrag vr_rx_chain[VR_RX_LIST_CNT];
- struct vr_chain vr_tx_chain[VR_TX_LIST_CNT];
-
- struct vr_chain_onefrag *vr_rx_head;
-
- struct vr_chain *vr_tx_cons;
- struct vr_chain *vr_tx_prod;
+/*
+ * Software state of transmit and receive descriptors.
+ */
+struct vr_descsoft {
+ struct mbuf *ds_mbuf; /* head of mbuf chain */
+ bus_dmamap_t ds_dmamap; /* our DMA map */
};
struct vr_type {
@@ -449,29 +460,46 @@
#define VR_FLAG_FORCEDELAY 1
#define VR_FLAG_SCHEDDELAY 2
-#define VR_FLAG_DELAYTIMEO 3
+#define VR_FLAG_DELAYTIMEO 3
struct vr_softc {
- struct arpcom arpcom; /* interface info */
- bus_space_handle_t vr_bhandle; /* bus space handle */
- bus_space_tag_t vr_btag; /* bus space tag */
+ struct arpcom arpcom; /* interface info */
+ bus_space_handle_t vr_bhandle; /* bus space handle */
+ bus_space_tag_t vr_btag; /* bus space tag */
+ bus_dma_tag_t vr_dmat; /* bus DMA tag */
struct resource *vr_res;
struct resource *vr_irq;
void *vr_intrhand;
- device_t vr_miibus;
+ device_t vr_miibus;
struct vr_type *vr_info; /* Rhine adapter info */
- u_int8_t vr_unit; /* interface number */
- u_int8_t vr_type;
- u_int8_t vr_revid; /* Rhine chip revision */
- u_int8_t vr_flags; /* See VR_F_* below */
- struct vr_list_data *vr_ldata;
- struct vr_chain_data vr_cdata;
- struct callout_handle vr_stat_ch;
- struct mtx vr_mtx;
+ u_int8_t vr_unit; /* interface number */
+ u_int8_t vr_type;
+ u_int8_t vr_revid; /* Rhine chip revision */
+ u_int8_t vr_flags; /* See VR_F_* below */
+ struct callout_handle vr_stat_ch;
+ struct mtx vr_mtx;
+ bus_dmamap_t vr_cddmamap; /* control data DMA map */
+
+ /*
+ * Software state for transmit and receive descriptors.
+ */
+ struct vr_descsoft vr_txsoft[VR_NTXDESC];
+ struct vr_descsoft vr_rxsoft[VR_NRXDESC];
+
+ /*
+ * Control data structures.
+ */
+ struct vr_control_data *vr_control_data;
+ int vr_txpending; /* # of TX requests pending */
+ int vr_txdirty; /* first dirty TX descriptor */
+ int vr_txlast; /* last used TX descriptor */
+ int vr_rxptr; /* next ready RX descriptor */
+
#ifdef DEVICE_POLLING
- int rxcycles;
+ int rxcycles;
#endif
};
+#define vr_cddma vr_cddmamap->dm_segs[0].ds_addr
#define VR_F_RESTART 0x01 /* Restart unit on next tick */
@@ -479,6 +507,42 @@
#define VR_UNLOCK(_sc) mtx_unlock(&(_sc)->vr_mtx)
#define VR_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->vr_mtx, MA_OWNED)
+#define VR_CDTXADDR(sc, x) ((sc)->vr_cddma + VR_CDTXOFF((x)))
+#define VR_CDRXADDR(sc, x) ((sc)->vr_cddma + VR_CDRXOFF((x)))
+
+#define VR_CDTX(sc, x) (&(sc)->vr_control_data->vr_txdescs[(x)])
+#define VR_CDRX(sc, x) (&(sc)->vr_control_data->vr_rxdescs[(x)])
+
+#define VR_DSTX(sc, x) (&(sc)->vr_txsoft[(x)])
+#define VR_DSRX(sc, x) (&(sc)->vr_rxsoft[(x)])
+
+#define VR_CDTXSYNC(sc, x, ops) \
+ bus_dmamap_sync((sc)->vr_dmat, (sc)->vr_cddmamap, \
+ VR_CDTXOFF((x)), sizeof(struct vr_desc), (ops))
+
+#define VR_CDRXSYNC(sc, x, ops) \
+ bus_dmamap_sync((sc)->vr_dmat, (sc)->vr_cddmamap, \
+ VR_CDRXOFF((x)), sizeof(struct vr_desc), (ops))
+
+/*
+ * Note we rely on MCLBYTES being a power of two below.
+ */
+#define VR_INIT_RXDESC(sc, i) \
+ do {\
+ struct vr_desc *__d = VR_CDRX((sc), (i)); \
+ struct vr_descsoft *__ds = VR_DSRX((sc), (i)); \
+ \
+ __d->vr_next = VR_CDRXADDR((sc), VR_NEXTRX((i))); \
+ __d->vr_status = VR_RXSTAT_FIRSTFRAG | VR_RXSTAT_LASTFRAG | \
+ VR_RXSTAT_OWN; \
+ __d->vr_data = __ds->ds_dmamap->dm_segs[0].ds_addr; \
+ __d->vr_ctl = VR_RXCTL_CHAIN | VR_RXCTL_RX_INTR | \
+ ((MCLBYTES - 1) & VR_RXCTL_BUFLEN); \
+ VR_CDRXSYNC((sc), (i), \
+ BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); \
+ } while (0)
+
+
/*
* register space access macros
*/
@@ -590,8 +654,4 @@
#define VR_PME_EN 0x0010
#define VR_PME_STATUS 0x8000
-
-#ifdef __alpha__
-#undef vtophys
-#define vtophys(va) alpha_XXX_dmamap((vm_offset_t)va)
-#endif
+#define AC2IFP(acp) &((acp)->ac_if)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 167 bytes
Desc: not available
Url : http://lists.freebsd.org/pipermail/freebsd-net/attachments/20040703/2a335145/attachment.bin
More information about the freebsd-net
mailing list