From 9bcc8b955c59e8046fd84686a07d7b7ac0304a0a Mon Sep 17 00:00:00 2001 From: heas Date: Sun, 20 Feb 2005 18:29:00 +0000 Subject: [PATCH] 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. --- sys/dev/ic/gem.c | 167 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 12 deletions(-) diff --git a/sys/dev/ic/gem.c b/sys/dev/ic/gem.c index 8a19f784797f..d1d92b4664e4 100644 --- a/sys/dev/ic/gem.c +++ b/sys/dev/ic/gem.c @@ -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 -__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 @@ -59,6 +60,15 @@ __KERNEL_RCSID(0, "$NetBSD: gem.c,v 1.34 2005/02/04 02:10:36 perry Exp $"); #include #include +#ifdef INET +#include +#include +#include +#include +#include +#include +#endif + #if NBPFILTER > 0 #include #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<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); }