Have the MAC strip the ethernet FCS on rx so it is not included in the hardware

checksum and add code to support IPv4 TCP/UDP hardware checksums.
This commit is contained in:
heas 2005-02-20 18:29:00 +00:00
parent d39b670320
commit 9bcc8b955c
1 changed files with 155 additions and 12 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: gem.c,v 1.34 2005/02/04 02:10:36 perry Exp $ */
/* $NetBSD: gem.c,v 1.35 2005/02/20 18:29:00 heas Exp $ */
/*
*
@ -34,8 +34,9 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: gem.c,v 1.34 2005/02/04 02:10:36 perry Exp $");
__KERNEL_RCSID(0, "$NetBSD: gem.c,v 1.35 2005/02/20 18:29:00 heas Exp $");
#include "opt_inet.h"
#include "bpfilter.h"
#include <sys/param.h>
@ -59,6 +60,15 @@ __KERNEL_RCSID(0, "$NetBSD: gem.c,v 1.34 2005/02/04 02:10:36 perry Exp $");
#include <net/if_media.h>
#include <net/if_ether.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#endif
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
@ -241,6 +251,8 @@ gem_attach(sc, enaddr)
ifp->if_softc = sc;
ifp->if_flags =
IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
ifp->if_capabilities |= IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_UDPv4_Rx
| IFCAP_CSUM_TCPv4 | IFCAP_CSUM_UDPv4;
ifp->if_start = gem_start;
ifp->if_ioctl = gem_ioctl;
ifp->if_watchdog = gem_watchdog;
@ -831,11 +843,18 @@ gem_init(struct ifnet *ifp)
/* Encode Receive Descriptor ring size: four possible values */
v = gem_ringsize(GEM_NRXDESC /*XXX*/);
/* Set receive h/w checksum offset */
#ifdef INET
v |= (ETHER_HDR_LEN + sizeof(struct ip) +
((sc->sc_ethercom.ec_capenable & ETHERCAP_VLAN_MTU) ?
ETHER_VLAN_ENCAP_LEN : 0)) << GEM_RX_CONFIG_CXM_START_SHFT;
#endif
/* Enable DMA */
bus_space_write_4(t, h, GEM_RX_CONFIG,
v|(GEM_THRSH_1024<<GEM_RX_CONFIG_FIFO_THRS_SHIFT)|
(2<<GEM_RX_CONFIG_FBOFF_SHFT)|GEM_RX_CONFIG_RXDMA_EN|
(0<<GEM_RX_CONFIG_CXM_START_SHFT));
(2<<GEM_RX_CONFIG_FBOFF_SHFT)|GEM_RX_CONFIG_RXDMA_EN);
/*
* The following value is for an OFF Threshold of about 3/4 full
* and an ON Threshold of 1/4 full.
@ -853,7 +872,7 @@ gem_init(struct ifnet *ifp)
/* step 12. RX_MAC Configuration Register */
v = bus_space_read_4(t, h, GEM_MAC_RX_CONFIG);
v |= GEM_MAC_RX_ENABLE;
v |= GEM_MAC_RX_ENABLE | GEM_MAC_RX_STRIP_CRC;
bus_space_write_4(t, h, GEM_MAC_RX_CONFIG, v);
/* step 14. Issue Transmit Pending command */
@ -1114,6 +1133,39 @@ gem_start(ifp)
sc->sc_txwin = 0;
flags |= GEM_TD_INTERRUPT_ME;
}
#ifdef INET
/* h/w checksum */
if (ifp->if_csum_flags_tx & (M_CSUM_TCPv4 |
M_CSUM_UDPv4) && m0->m_pkthdr.csum_flags &
(M_CSUM_TCPv4|M_CSUM_UDPv4)) {
struct ether_header *eh;
uint16_t offset, start;
eh = mtod(m0, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
start = ETHER_HDR_LEN;
break;
case ETHERTYPE_VLAN:
start = ETHER_HDR_LEN +
ETHER_VLAN_ENCAP_LEN;
break;
default:
/* unsupported, drop it */
m_free(m0);
continue;
}
start += m0->m_pkthdr.csum_data >> 16;
offset = (m0->m_pkthdr.csum_data &
0xffff) + start;
flags |= (start <<
GEM_TD_CXSUM_STARTSHFT) |
(offset <<
GEM_TD_CXSUM_STUFFSHFT) |
GEM_TD_CXSUM_ENABLE;
}
#endif
}
if (seg == dmamap->dm_nsegs - 1) {
flags |= GEM_TD_END_OF_PACKET;
@ -1339,7 +1391,6 @@ gem_rint(sc)
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
bus_space_tag_t t = sc->sc_bustag;
bus_space_handle_t h = sc->sc_h;
struct ether_header *eh;
struct gem_rxsoft *rxs;
struct mbuf *m;
u_int64_t rxstat;
@ -1402,11 +1453,8 @@ gem_rint(sc)
}
#endif
/*
* No errors; receive the packet. Note the Gem
* includes the CRC with every packet.
*/
len = GEM_RD_BUFLEN(rxstat) - ETHER_CRC_LEN;
/* No errors; receive the packet. */
len = GEM_RD_BUFLEN(rxstat);
/*
* Allocate a new mbuf cluster. If that fails, we are
@ -1424,7 +1472,6 @@ gem_rint(sc)
}
m->m_data += 2; /* We're already off by two */
eh = mtod(m, struct ether_header *);
m->m_pkthdr.rcvif = ifp;
m->m_pkthdr.len = m->m_len = len;
@ -1437,6 +1484,102 @@ gem_rint(sc)
bpf_mtap(ifp->if_bpf, m);
#endif /* NPBFILTER > 0 */
#ifdef INET
/* hardware checksum */
if (ifp->if_csum_flags_rx & (M_CSUM_UDPv4 | M_CSUM_TCPv4)) {
struct ether_header *eh;
struct ip *ip;
struct udphdr *uh;
int32_t hlen, pktlen;
if (sc->sc_ethercom.ec_capenable & ETHERCAP_VLAN_MTU) {
pktlen = m->m_pkthdr.len - ETHER_HDR_LEN -
ETHER_VLAN_ENCAP_LEN;
eh = (struct ether_header *) mtod(m, caddr_t) +
ETHER_VLAN_ENCAP_LEN;
} else {
pktlen = m->m_pkthdr.len - ETHER_HDR_LEN;
eh = mtod(m, struct ether_header *);
}
if (ntohs(eh->ether_type) != ETHERTYPE_IP)
goto swcsum;
ip = (struct ip *) ((caddr_t)eh + ETHER_HDR_LEN);
/* IPv4 only */
if (ip->ip_v != IPVERSION)
goto swcsum;
hlen = ip->ip_hl << 2;
if (hlen < sizeof(struct ip))
goto swcsum;
/* too short, truncated, fragment */
if ((ntohs(ip->ip_len) < hlen) ||
(ntohs(ip->ip_len) > pktlen) ||
(ntohs(ip->ip_off) & (IP_MF | IP_OFFMASK)))
goto swcsum;
switch (ip->ip_p) {
case IPPROTO_TCP:
if (! (ifp->if_csum_flags_rx & M_CSUM_TCPv4))
goto swcsum;
if (pktlen < (hlen + sizeof(struct tcphdr)))
goto swcsum;
m->m_pkthdr.csum_flags = M_CSUM_TCPv4;
break;
case IPPROTO_UDP:
if (! (ifp->if_csum_flags_rx & M_CSUM_UDPv4))
goto swcsum;
if (pktlen < (hlen + sizeof(struct udphdr)))
goto swcsum;
uh = (struct udphdr *)((caddr_t)ip + hlen);
/* no checksum */
if (uh->uh_sum == 0)
goto swcsum;
m->m_pkthdr.csum_flags = M_CSUM_UDPv4;
break;
default:
goto swcsum;
}
/* the uncomplemented sum is expected */
m->m_pkthdr.csum_data = (~rxstat) & GEM_RD_CHECKSUM;
/* if the pkt had ip options, we have to deduct them */
if (hlen > sizeof(struct ip)) {
uint16_t *opts;
uint32_t optsum, temp;
optsum = 0;
temp = hlen - sizeof(struct ip);
opts = (uint16_t *) ((caddr_t) ip +
sizeof(struct ip));
while (temp > 1) {
optsum += ntohs(*opts++);
temp -= 2;
}
while (optsum >> 16)
optsum = (optsum >> 16) +
(optsum & 0xffff);
/* Deduct ip opts sum from hwsum (rfc 1624). */
m->m_pkthdr.csum_data =
~((~m->m_pkthdr.csum_data) - ~optsum);
while (m->m_pkthdr.csum_data >> 16)
m->m_pkthdr.csum_data =
(m->m_pkthdr.csum_data >> 16) +
(m->m_pkthdr.csum_data &
0xffff);
}
m->m_pkthdr.csum_flags |= M_CSUM_DATA |
M_CSUM_NO_PSEUDOHDR;
} else
swcsum:
m->m_pkthdr.csum_flags = 0;
#endif
/* Pass it on. */
(*ifp->if_input)(ifp, m);
}