Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/dev/pci Make vlan and all ip/ip6 checksum offload work f...



details:   https://anonhg.NetBSD.org/src/rev/1e50d3ed14ef
branches:  trunk
changeset: 781267:1e50d3ed14ef
user:      bouyer <bouyer%NetBSD.org@localhost>
date:      Wed Aug 29 20:39:24 2012 +0000

description:
Make vlan and all ip/ip6 checksum offload work for the I350.

On newer devices, when using the legacy TX descriptors, vlan-related flags
that were set on the last descriptor of a packet have to be set on the
first one.
For tso/checksum offloads, a new "advanced" descriptor format has to be
used.

Change wcd_txdescs to a union defining all types of descriptors (they
are all 16-bytes wide).
Define a new tx function wm_nq_start(), which handle newer devices.
There is some code duplication with wm_start(), but adding support to
the existing wm_start() would make it a if () {} else {} maze. This also
allows to get rid of some workaround for older chips that are not needed
here.
Use wm_nq_start() instead of wm_start() for the I350 (this should probably
be for all WM_F_NEWQUEUE devices, but I have no hardware but the I350 to
test). Call ifp->if_start() instead of wm_start() where is matters.

Tested on a I350, and a i80003 (which use the old format), both with and
without vlans, with and without checksum offloads.

diffstat:

 sys/dev/pci/if_wm.c    |  525 +++++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/pci/if_wmreg.h |   59 +++++-
 2 files changed, 572 insertions(+), 12 deletions(-)

diffs (truncated from 689 to 300 lines):

diff -r 202ed68e0945 -r 1e50d3ed14ef sys/dev/pci/if_wm.c
--- a/sys/dev/pci/if_wm.c       Wed Aug 29 20:37:50 2012 +0000
+++ b/sys/dev/pci/if_wm.c       Wed Aug 29 20:39:24 2012 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: if_wm.c,v 1.231 2012/08/09 07:48:39 msaitoh Exp $      */
+/*     $NetBSD: if_wm.c,v 1.232 2012/08/29 20:39:24 bouyer Exp $       */
 
 /*
  * Copyright (c) 2001, 2002, 2003, 2004 Wasabi Systems, Inc.
@@ -76,7 +76,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_wm.c,v 1.231 2012/08/09 07:48:39 msaitoh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_wm.c,v 1.232 2012/08/29 20:39:24 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -194,7 +194,10 @@
         * The transmit descriptors.  Put these at the end, because
         * we might use a smaller number of them.
         */
-       wiseman_txdesc_t wcd_txdescs[WM_NTXDESC_82544];
+       union {
+               wiseman_txdesc_t wcdu_txdescs[WM_NTXDESC_82544];
+               nq_txdesc_t      wcdu_nq_txdescs[WM_NTXDESC_82544];
+       } wdc_u;
 };
 
 struct wm_control_data_82542 {
@@ -203,7 +206,7 @@
 };
 
 #define        WM_CDOFF(x)     offsetof(struct wm_control_data_82544, x)
-#define        WM_CDTXOFF(x)   WM_CDOFF(wcd_txdescs[(x)])
+#define        WM_CDTXOFF(x)   WM_CDOFF(wdc_u.wcdu_txdescs[(x)])
 #define        WM_CDRXOFF(x)   WM_CDOFF(wcd_rxdescs[(x)])
 
 /*
@@ -294,7 +297,8 @@
        int sc_cd_rseg;                 /* real number of control segment */
        size_t sc_cd_size;              /* control data size */
 #define        sc_cddma        sc_cddmamap->dm_segs[0].ds_addr
-#define        sc_txdescs      sc_control_data->wcd_txdescs
+#define        sc_txdescs      sc_control_data->wdc_u.wcdu_txdescs
+#define        sc_nq_txdescs   sc_control_data->wdc_u.wcdu_nq_txdescs
 #define        sc_rxdescs      sc_control_data->wcd_rxdescs
 
 #ifdef WM_EVENT_COUNTERS
@@ -490,6 +494,7 @@
 } while (/*CONSTCOND*/0)
 
 static void    wm_start(struct ifnet *);
+static void    wm_nq_start(struct ifnet *);
 static void    wm_watchdog(struct ifnet *);
 static int     wm_ifflags_cb(struct ethercom *);
 static int     wm_ioctl(struct ifnet *, u_long, void *);
@@ -1877,7 +1882,10 @@
        ifp->if_softc = sc;
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_ioctl = wm_ioctl;
-       ifp->if_start = wm_start;
+       if (sc->sc_type == WM_T_I350)
+               ifp->if_start = wm_nq_start;
+       else
+               ifp->if_start = wm_start;
        ifp->if_watchdog = wm_watchdog;
        ifp->if_init = wm_init;
        ifp->if_stop = wm_stop;
@@ -2761,6 +2769,480 @@
 }
 
 /*
+ * wm_nq_tx_offload:
+ *
+ *     Set up TCP/IP checksumming parameters for the
+ *     specified packet, for NEWQUEUE devices
+ */
+static int
+wm_nq_tx_offload(struct wm_softc *sc, struct wm_txsoft *txs,
+    uint32_t *cmdlenp, uint32_t *fieldsp, bool *do_csum)
+{
+       struct mbuf *m0 = txs->txs_mbuf;
+       struct m_tag *mtag;
+       uint32_t vl_len, mssidx, cmdc;
+       struct ether_header *eh;
+       int offset, iphl;
+
+       /*
+        * XXX It would be nice if the mbuf pkthdr had offset
+        * fields for the protocol headers.
+        */
+
+       eh = mtod(m0, struct ether_header *);
+       switch (htons(eh->ether_type)) {
+       case ETHERTYPE_IP:
+       case ETHERTYPE_IPV6:
+               offset = ETHER_HDR_LEN;
+               break;
+
+       case ETHERTYPE_VLAN:
+               offset = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
+               break;
+
+       default:
+               /*
+                * Don't support this protocol or encapsulation.
+                */
+               *do_csum = false;
+               return 0;
+       }
+       *do_csum = true;
+       *cmdlenp = NQTX_DTYP_D | NQTX_CMD_DEXT | NQTX_CMD_IFCS;
+       cmdc = NQTX_DTYP_C | NQTX_CMD_DEXT;
+
+       vl_len = (offset << NQTXC_VLLEN_MACLEN_SHIFT);
+       KASSERT((offset & ~NQTXC_VLLEN_MACLEN_MASK) == 0);
+
+       if ((m0->m_pkthdr.csum_flags &
+           (M_CSUM_TSOv4|M_CSUM_UDPv4|M_CSUM_TCPv4|M_CSUM_IPv4)) != 0) {
+               iphl = M_CSUM_DATA_IPv4_IPHL(m0->m_pkthdr.csum_data);
+       } else {
+               iphl = M_CSUM_DATA_IPv6_HL(m0->m_pkthdr.csum_data);
+       }
+       vl_len |= (iphl << NQTXC_VLLEN_IPLEN_SHIFT);
+       KASSERT((iphl & ~NQTXC_VLLEN_IPLEN_MASK) == 0);
+
+       if ((mtag = VLAN_OUTPUT_TAG(&sc->sc_ethercom, m0)) != NULL) {
+               vl_len |= ((VLAN_TAG_VALUE(mtag) & NQTXC_VLLEN_VLAN_MASK)
+                    << NQTXC_VLLEN_VLAN_SHIFT);
+               *cmdlenp |= NQTX_CMD_VLE;
+       }
+
+       *fieldsp = 0;
+       mssidx = 0;
+
+       if ((m0->m_pkthdr.csum_flags & (M_CSUM_TSOv4 | M_CSUM_TSOv6)) != 0) {
+               int hlen = offset + iphl;
+               int tcp_hlen;
+               bool v4 = (m0->m_pkthdr.csum_flags & M_CSUM_TSOv4) != 0;
+
+               if (__predict_false(m0->m_len <
+                                   (hlen + sizeof(struct tcphdr)))) {
+                       /*
+                        * TCP/IP headers are not in the first mbuf; we need
+                        * to do this the slow and painful way.  Let's just
+                        * hope this doesn't happen very often.
+                        */
+                       struct tcphdr th;
+
+                       WM_EVCNT_INCR(&sc->sc_ev_txtsopain);
+
+                       m_copydata(m0, hlen, sizeof(th), &th);
+                       if (v4) {
+                               struct ip ip;
+
+                               m_copydata(m0, offset, sizeof(ip), &ip);
+                               ip.ip_len = 0;
+                               m_copyback(m0,
+                                   offset + offsetof(struct ip, ip_len),
+                                   sizeof(ip.ip_len), &ip.ip_len);
+                               th.th_sum = in_cksum_phdr(ip.ip_src.s_addr,
+                                   ip.ip_dst.s_addr, htons(IPPROTO_TCP));
+                       } else {
+                               struct ip6_hdr ip6;
+
+                               m_copydata(m0, offset, sizeof(ip6), &ip6);
+                               ip6.ip6_plen = 0;
+                               m_copyback(m0,
+                                   offset + offsetof(struct ip6_hdr, ip6_plen),
+                                   sizeof(ip6.ip6_plen), &ip6.ip6_plen);
+                               th.th_sum = in6_cksum_phdr(&ip6.ip6_src,
+                                   &ip6.ip6_dst, 0, htonl(IPPROTO_TCP));
+                       }
+                       m_copyback(m0, hlen + offsetof(struct tcphdr, th_sum),
+                           sizeof(th.th_sum), &th.th_sum);
+
+                       tcp_hlen = th.th_off << 2;
+               } else {
+                       /*
+                        * TCP/IP headers are in the first mbuf; we can do
+                        * this the easy way.
+                        */
+                       struct tcphdr *th;
+
+                       if (v4) {
+                               struct ip *ip =
+                                   (void *)(mtod(m0, char *) + offset);
+                               th = (void *)(mtod(m0, char *) + hlen);
+
+                               ip->ip_len = 0;
+                               th->th_sum = in_cksum_phdr(ip->ip_src.s_addr,
+                                   ip->ip_dst.s_addr, htons(IPPROTO_TCP));
+                       } else {
+                               struct ip6_hdr *ip6 =
+                                   (void *)(mtod(m0, char *) + offset);
+                               th = (void *)(mtod(m0, char *) + hlen);
+
+                               ip6->ip6_plen = 0;
+                               th->th_sum = in6_cksum_phdr(&ip6->ip6_src,
+                                   &ip6->ip6_dst, 0, htonl(IPPROTO_TCP));
+                       }
+                       tcp_hlen = th->th_off << 2;
+               }
+               hlen += tcp_hlen;
+               *cmdlenp |= NQTX_CMD_TSE;
+
+               if (v4) {
+                       WM_EVCNT_INCR(&sc->sc_ev_txtso);
+                       *fieldsp |= NQTXD_FIELDS_IXSM | NQTXD_FIELDS_TUXSM;
+               } else {
+                       WM_EVCNT_INCR(&sc->sc_ev_txtso6);
+                       *fieldsp |= NQTXD_FIELDS_TUXSM;
+               }
+               *fieldsp |= ((m0->m_pkthdr.len - hlen) << NQTXD_FIELDS_PAYLEN_SHIFT);
+               KASSERT(((m0->m_pkthdr.len - hlen) & ~NQTXD_FIELDS_PAYLEN_MASK) == 0);
+               mssidx |= (m0->m_pkthdr.segsz << NQTXC_MSSIDX_MSS_SHIFT);
+               KASSERT((m0->m_pkthdr.segsz & ~NQTXC_MSSIDX_MSS_MASK) == 0);
+               mssidx |= (tcp_hlen << NQTXC_MSSIDX_L4LEN_SHIFT);
+               KASSERT((tcp_hlen & ~NQTXC_MSSIDX_L4LEN_MASK) == 0);
+       } else {
+               *fieldsp |= (m0->m_pkthdr.len << NQTXD_FIELDS_PAYLEN_SHIFT);
+               KASSERT((m0->m_pkthdr.len & ~NQTXD_FIELDS_PAYLEN_MASK) == 0);
+       }
+
+       if (m0->m_pkthdr.csum_flags & M_CSUM_IPv4) {
+               *fieldsp |= NQTXD_FIELDS_IXSM;
+               cmdc |= NQTXC_CMD_IP4;
+       }
+
+       if (m0->m_pkthdr.csum_flags &
+           (M_CSUM_UDPv4 | M_CSUM_TCPv4 | M_CSUM_TSOv4)) {
+               WM_EVCNT_INCR(&sc->sc_ev_txtusum);
+               if (m0->m_pkthdr.csum_flags & (M_CSUM_TCPv4 | M_CSUM_TSOv4)) {
+                       cmdc |= NQTXC_CMD_TCP;
+               } else {
+                       cmdc |= NQTXC_CMD_UDP;
+               }
+               cmdc |= NQTXC_CMD_IP4;
+               *fieldsp |= NQTXD_FIELDS_TUXSM;
+       }
+       if (m0->m_pkthdr.csum_flags &
+           (M_CSUM_UDPv6 | M_CSUM_TCPv6 | M_CSUM_TSOv6)) {
+               WM_EVCNT_INCR(&sc->sc_ev_txtusum6);
+               if (m0->m_pkthdr.csum_flags & (M_CSUM_TCPv6 | M_CSUM_TSOv6)) {
+                       cmdc |= NQTXC_CMD_TCP;
+               } else {
+                       cmdc |= NQTXC_CMD_UDP;
+               }
+               cmdc |= NQTXC_CMD_IP6;
+               *fieldsp |= NQTXD_FIELDS_TUXSM;
+       }
+
+       /* Fill in the context descriptor. */
+       sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_vl_len =
+           htole32(vl_len);
+       sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_sn = 0;
+       sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_cmd = 
+           htole32(cmdc);
+       sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_mssidx = 
+           htole32(mssidx);
+       WM_CDTXSYNC(sc, sc->sc_txnext, 1, BUS_DMASYNC_PREWRITE);
+       DPRINTF(WM_DEBUG_TX,
+           ("%s: TX: context desc %d 0x%08x%08x\n", device_xname(sc->sc_dev),
+           sc->sc_txnext, 0, vl_len));
+       DPRINTF(WM_DEBUG_TX, ("\t0x%08x%08x\n", mssidx, cmdc));
+       sc->sc_txnext = WM_NEXTTX(sc, sc->sc_txnext);
+       txs->txs_ndesc++;
+       return 0;
+}
+
+/*
+ * wm_nq_start:                [ifnet interface function]
+ *
+ *     Start packet transmission on the interface for NEWQUEUE devices
+ */
+static void
+wm_nq_start(struct ifnet *ifp)
+{
+       struct wm_softc *sc = ifp->if_softc;
+       struct mbuf *m0;
+       struct m_tag *mtag;
+       struct wm_txsoft *txs;
+       bus_dmamap_t dmamap;
+       int error, nexttx, lasttx = -1, seg, segs_needed;
+       bool do_csum, sent;
+       uint32_t cmdlen, fields, dcmdlen;
+
+       if ((ifp->if_flags & (IFF_RUNNING|IFF_OACTIVE)) != IFF_RUNNING)
+               return;
+
+       sent = false;
+
+       /*
+        * Loop through the send queue, setting up transmit descriptors
+        * until we drain the queue, or use up all available transmit
+        * descriptors.
+        */
+       for (;;) {
+               /* Grab a packet off the queue. */



Home | Main Index | Thread Index | Old Index