Add a workaround for a hardware ip4csum-tx bug which would sometimes put

wrong IPv4 checksum on sending 21 or 22 byte IP packets.

See discussion on tech-kern and tech-net for details:
http://mail-index.netbsd.org/tech-kern/2006/11/04/0004.html
This commit is contained in:
tsutsui 2006-11-12 02:49:46 +00:00
parent 1bb72362cf
commit cad156cdf4
2 changed files with 46 additions and 14 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: elinkxl.c,v 1.92 2006/11/05 07:59:21 itohy Exp $ */
/* $NetBSD: elinkxl.c,v 1.93 2006/11/12 02:49:46 tsutsui Exp $ */
/*-
* Copyright (c) 1998 The NetBSD Foundation, Inc.
@ -37,7 +37,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: elinkxl.c,v 1.92 2006/11/05 07:59:21 itohy Exp $");
__KERNEL_RCSID(0, "$NetBSD: elinkxl.c,v 1.93 2006/11/12 02:49:46 tsutsui Exp $");
#include "bpfilter.h"
#include "rnd.h"
@ -279,7 +279,7 @@ ex_config(sc)
* map for them.
*/
if ((error = bus_dmamem_alloc(sc->sc_dmat,
EX_NDPD * sizeof (struct ex_dpd), PAGE_SIZE, 0, &sc->sc_dseg, 1,
DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN, PAGE_SIZE, 0, &sc->sc_dseg, 1,
&sc->sc_drseg, BUS_DMA_NOWAIT)) != 0) {
aprint_error(
"%s: can't allocate download descriptors, error = %d\n",
@ -290,19 +290,19 @@ ex_config(sc)
attach_stage = 5;
if ((error = bus_dmamem_map(sc->sc_dmat, &sc->sc_dseg, sc->sc_drseg,
EX_NDPD * sizeof (struct ex_dpd), (caddr_t *)&sc->sc_dpd,
DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN, (caddr_t *)&sc->sc_dpd,
BUS_DMA_NOWAIT|BUS_DMA_COHERENT)) != 0) {
aprint_error("%s: can't map download descriptors, error = %d\n",
sc->sc_dev.dv_xname, error);
goto fail;
}
memset(sc->sc_dpd, 0, EX_NDPD * sizeof (struct ex_dpd));
memset(sc->sc_dpd, 0, DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN);
attach_stage = 6;
if ((error = bus_dmamap_create(sc->sc_dmat,
EX_NDPD * sizeof (struct ex_dpd), 1,
EX_NDPD * sizeof (struct ex_dpd), 0, BUS_DMA_NOWAIT,
DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN, 1,
DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN, 0, BUS_DMA_NOWAIT,
&sc->sc_dpd_dmamap)) != 0) {
aprint_error(
"%s: can't create download desc. DMA map, error = %d\n",
@ -313,13 +313,15 @@ ex_config(sc)
attach_stage = 7;
if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_dpd_dmamap,
sc->sc_dpd, EX_NDPD * sizeof (struct ex_dpd), NULL,
sc->sc_dpd, DPDMEM_SIZE + EX_IP4CSUMTX_PADLEN, NULL,
BUS_DMA_NOWAIT)) != 0) {
aprint_error(
"%s: can't load download desc. DMA map, error = %d\n",
sc->sc_dev.dv_xname, error);
goto fail;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_dpd_dmamap,
DPDMEMPAD_OFF, EX_IP4CSUMTX_PADLEN, BUS_DMASYNC_PREWRITE);
attach_stage = 8;
@ -1053,7 +1055,7 @@ ex_start(ifp)
struct ex_txdesc *txp;
struct mbuf *mb_head;
bus_dmamap_t dmamap;
int m_csumflags, offset, totlen, segment, error;
int m_csumflags, offset, seglen, totlen, segment, error;
u_int32_t csum_flags;
if (sc->tx_head || sc->tx_free == NULL)
@ -1153,11 +1155,28 @@ ex_start(ifp)
totlen = 0;
for (segment = 0; segment < dmamap->dm_nsegs; segment++, fr++) {
fr->fr_addr = htole32(dmamap->dm_segs[segment].ds_addr);
fr->fr_len = htole32(dmamap->dm_segs[segment].ds_len);
totlen += dmamap->dm_segs[segment].ds_len;
seglen = dmamap->dm_segs[segment].ds_len;
fr->fr_len = htole32(seglen);
totlen += seglen;
}
if (__predict_false(totlen <= EX_IP4CSUMTX_PADLEN &&
(m_csumflags & M_CSUM_IPv4) != 0)) {
/*
* Pad short packets to avoid ip4csum-tx bug.
*
* XXX Should we still consider if such short
* (36 bytes or less) packets might already
* occupy EX_NTFRAG (== 32) fragements here?
*/
KASSERT(segment < EX_NTFRAGS);
fr->fr_addr = htole32(DPDMEMPAD_DMADDR(sc));
seglen = EX_IP4CSUMTX_PADLEN + 1 - totlen;
fr->fr_len = htole32(EX_FR_LAST | seglen);
totlen += seglen;
} else {
fr--;
fr->fr_len |= htole32(EX_FR_LAST);
}
fr--;
fr->fr_len |= htole32(EX_FR_LAST);
txp->tx_mbhead = mb_head;
bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,

View File

@ -1,4 +1,4 @@
/* $NetBSD: elinkxlreg.h,v 1.11 2002/11/09 11:45:19 enami Exp $ */
/* $NetBSD: elinkxlreg.h,v 1.12 2006/11/12 02:49:46 tsutsui Exp $ */
/*-
* Copyright (c) 1998 The NetBSD Foundation, Inc.
@ -268,6 +268,19 @@ struct ex_txdesc {
struct ex_dpd *tx_dpd;
};
/*
* hardware ip4csum-tx on ex(4) sometimes seems to set wrong IP checksums
* if the TX IP packet length is 21 or 22 bytes which requires autopadding.
* To avoid this bug, we have to pad such very short packets manually.
*/
#define EX_IP4CSUMTX_MINLEN 22
#define EX_IP4CSUMTX_PADLEN \
(sizeof(struct ether_header) + EX_IP4CSUMTX_MINLEN)
#define DPDMEM_SIZE (sizeof(struct ex_dpd) * EX_NDPD)
#define DPDMEMPAD_OFF DPDMEM_SIZE
#define DPDMEMPAD_DMADDR(sc) ((sc)->sc_dpddma + DPDMEMPAD_OFF)
#define DPD_DMADDR(s,t) \
((s)->sc_dpddma + ((caddr_t)((t)->tx_dpd) - (caddr_t)((s)->sc_dpd)))