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