Implement TSO
authorAndrew Gallatin <gallatin@google.com>
Tue, 15 Jul 2014 16:00:22 +0000 (09:00 -0700)
committerAndrew Gallatin <gallatin@google.com>
Tue, 15 Jul 2014 17:00:58 +0000 (10:00 -0700)
Implement TCP Segmentation Offload.  Part of this patch
involves adding a feat field to the output of the ether
stats so that etherbind can parse that, and set a flag
in the ipifc that can be used by TCP to check for TSO
support.  It is also nice to be able to see what offloads
a device supports:

feat: udpck tcppck padmin sg tso

I did it this way rather than just enabling TSO
everywhere & segmenting in software at the edge
for devices which don't support TSO (like I did for
checksum offload) because TSO may be inappropriate
for low bandwidth links.

This patch lets me send at line rate on my out-of-tree
10GbE NIC for message sizes larger than 8K
(an improvement from 6.9Gb/s -> 9.47Gb/s)

Signed-off-by: Andrew Gallatin <gallatin@google.com>
kern/drivers/dev/ether.c
kern/include/ip.h
kern/src/net/ethermedium.c
kern/src/net/netif.c
kern/src/net/tcp.c
kern/src/ns/qio.c

index 4319b86..b928129 100644 (file)
@@ -484,7 +484,7 @@ static long etherbwrite(struct chan *chan, struct block *bp, uint32_t unused)
                runlock(&ether->rwlock);
                nexterror();
        }
-       if (n > ether->maxmtu) {
+       if (n > ether->maxmtu && (bp->flag & Btso) == 0) {
                freeb(bp);
                error(Etoobig);
        }
index bb21c13..8631251 100644 (file)
@@ -187,6 +187,7 @@ struct Ipifc {
        struct medium *m;                       /* Media pointer */
        int maxtu;                                      /* Maximum transfer unit */
        int mintu;                                      /* Minumum tranfer unit */
+       unsigned int feat;                              /* Offload features */
        int mbps;                                       /* megabits per second */
        void *arg;                                      /* medium specific */
        int reassemble;                         /* reassemble IP packets before forwarding */
@@ -935,9 +936,9 @@ enum {
        NETF_IPCK = (1 << NS_IPCK_SHIFT),       /* xmit ip checksum */
        NETF_UDPCK = (1 << NS_UDPCK_SHIFT),     /* xmit udp checksum */
        NETF_TCPCK = (1 << NS_TCPCK_SHIFT),     /* xmit tcp checksum */
-       NETF_PADMIN = (1 << NETF_SG_SHIFT),     /* device pads to mintu */
+       NETF_PADMIN = (1 << NETF_PADMIN_SHIFT), /* device pads to mintu */
        NETF_SG = (1 << NETF_SG_SHIFT),         /* device can do scatter/gather */
-       NETF_TSO = (1 << NS_TSO_SHIFT), /* device can do TSO */
+       NETF_TSO = (1 << NS_TSO_SHIFT),         /* device can do TSO */
 };
 /*
  *  a network interface
index 15ae69b..5a47bae 100644 (file)
@@ -120,6 +120,25 @@ struct Etherarp {
 
 static char *nbmsg = "nonblocking";
 
+static unsigned int parsefeat(char *ptr)
+{
+       unsigned int feat = 0;
+
+       if (strstr(ptr, "ipck"))
+               feat |= NETF_IPCK;
+       if (strstr(ptr, "udpck"))
+               feat |= NETF_UDPCK;
+       if (strstr(ptr, "tcpck"))
+               feat |= NETF_TCPCK;
+       if (strstr(ptr, "padmin"))
+               feat |= NETF_PADMIN;
+       if (strstr(ptr, "sg"))
+               feat |= NETF_SG;
+       if (strstr(ptr, "tso"))
+               feat |= NETF_TSO;
+       return feat;
+}
+
 /*
  *  called to bind an IP ifc to an ethernet device
  *  called with ifc wlock'd
@@ -204,6 +223,14 @@ static void etherbind(struct Ipifc *ifc, int argc, char **argv)
        } else
                ifc->mbps = 100;
 
+
+       ptr = strstr(buf, "feat: ");
+       if (ptr) {
+               ptr += 6;
+               ifc->feat = parsefeat(ptr);
+       } else {
+               ifc->feat = 0;
+       }
        /*
         *  open arp conversation
         */
index 0eb05c5..c445b5d 100644 (file)
@@ -273,6 +273,20 @@ netifread(struct netif *nif, struct chan *c, void *a, long n, uint32_t offset)
                        j += snprintf(p + j, READSTR - j, "addr: ");
                        for (i = 0; i < nif->alen; i++)
                                j += snprintf(p + j, READSTR - j, "%02.2x", nif->addr[i]);
+                       j += snprintf(p + j, READSTR - j, "\n");
+                       j += snprintf(p + j, READSTR - j, "feat: ");
+                       if (nif->feat & NETF_IPCK)
+                               j += snprintf(p + j, READSTR - j, "ipck ");
+                       if (nif->feat & NETF_UDPCK)
+                               j += snprintf(p + j, READSTR - j, "udpck ");
+                       if (nif->feat & NETF_TCPCK)
+                               j += snprintf(p + j, READSTR - j, "tcppck ");
+                       if (nif->feat & NETF_PADMIN)
+                               j += snprintf(p + j, READSTR - j, "padmin ");
+                       if (nif->feat & NETF_SG)
+                               j += snprintf(p + j, READSTR - j, "sg ");
+                       if (nif->feat & NETF_TSO)
+                               j += snprintf(p + j, READSTR - j, "tso ");
                        snprintf(p + j, READSTR - j, "\n");
                        n = readstr(offset, a, n, p);
                        kfree(p);
index 342afa9..ec405c4 100644 (file)
@@ -80,6 +80,7 @@ enum {
        RETRAN = 4,
        ACTIVE = 8,
        SYNACK = 16,
+       TSO = 32,
 
        LOGAGAIN = 3,
        LOGDGAIN = 2,
@@ -602,7 +603,7 @@ void tcpacktimer(void *v)
 static void tcpcreate(struct conv *c)
 {
        c->rq = qopen(QMAX, Qcoalesce, tcpacktimer, c);
-       c->wq = qopen((3 * QMAX) / 2, Qkick, tcpkick, c);
+       c->wq = qopen(8 * QMAX, Qkick, tcpkick, c);
 }
 
 static void timerstate(struct tcppriv *priv, Tcptimer * t, int newstate)
@@ -748,7 +749,8 @@ void localclose(struct conv *s, char *reason)
 }
 
 /* mtu (- TCP + IP hdr len) of 1st hop */
-int tcpmtu(struct Proto *tcp, uint8_t * addr, int version, int *scale)
+int tcpmtu(struct Proto *tcp, uint8_t * addr, int version, int *scale,
+          uint8_t *flags)
 {
        struct Ipifc *ifc;
        int mtu;
@@ -767,6 +769,8 @@ int tcpmtu(struct Proto *tcp, uint8_t * addr, int version, int *scale)
                                mtu = ifc->maxtu - ifc->m->hsize - (TCP6_PKT + TCP6_HDRSIZE);
                        break;
        }
+       *flags &= ~TSO;
+
        if (ifc != NULL) {
                if (ifc->mbps > 100)
                        *scale = HaveWS | 3;
@@ -774,6 +778,8 @@ int tcpmtu(struct Proto *tcp, uint8_t * addr, int version, int *scale)
                        *scale = HaveWS | 1;
                else
                        *scale = HaveWS | 0;
+               if (ifc->feat & NETF_TSO)
+                       *flags |= TSO;
        } else
                *scale = HaveWS | 0;
 
@@ -1213,7 +1219,8 @@ void tcpsndsyn(struct conv *s, Tcpctl * tcb)
        tcb->sndsyntime = NOW;
 
        /* set desired mss and scale */
-       tcb->mss = tcpmtu(s->p, s->laddr, s->ipversion, &tcb->scale);
+       tcb->mss = tcpmtu(s->p, s->laddr, s->ipversion, &tcb->scale,
+                         &tcb->flags);
 }
 
 void
@@ -1358,6 +1365,7 @@ int sndsynack(struct Proto *tcp, Limbo * lp)
        Tcp6hdr ph6;
        Tcp seg;
        int scale;
+       uint8_t flag = 0;
 
        /* make pseudo header */
        switch (lp->version) {
@@ -1389,7 +1397,7 @@ int sndsynack(struct Proto *tcp, Limbo * lp)
        seg.ack = lp->irs + 1;
        seg.flags = SYN | ACK;
        seg.urg = 0;
-       seg.mss = tcpmtu(tcp, lp->laddr, lp->version, &scale);
+       seg.mss = tcpmtu(tcp, lp->laddr, lp->version, &scale, &flag);
        seg.wnd = QMAX;
 
        /* if the other side set scale, we should too */
@@ -2445,8 +2453,33 @@ void tcpoutput(struct conv *s)
                                   tcb->snd.wnd, tcb->cwind);
                if (usable < ssize)
                        ssize = usable;
-               if (tcb->mss < ssize)
-                       ssize = tcb->mss;
+               if (ssize > tcb->mss) {
+                       if ((tcb->flags & TSO) == 0) {
+                               ssize = tcb->mss;
+                       } else {
+                               int segs, window;
+
+                               /*  Don't send too much.  32K is arbitrary..
+                                */
+                               if (ssize > 32 * 1024)
+                                       ssize = 32 * 1024;
+
+                               /* Clamp xmit to an integral MSS to
+                                * avoid ragged tail segments causing
+                                * poor link utilization.  Also
+                                * account for each segment sent in
+                                * msg heuristic, and round up to the
+                                * next multiple of 4, to ensure we
+                                * still yeild.
+                                */
+                               segs = ssize / tcb->mss;
+                               ssize = segs * tcb->mss;
+                               msgs += segs;
+                               if (segs > 3)
+                                       msgs = (msgs + 4) & ~3;
+                       }
+               }
+
                dsize = ssize;
                seg.urg = 0;
 
@@ -2502,6 +2535,10 @@ void tcpoutput(struct conv *s)
                                seg.flags |= FIN;
                                dsize--;
                        }
+                       if (BLEN(bp) > tcb->mss) {
+                               bp->flag |= Btso;
+                               bp->mss = tcb->mss;
+                       }
                }
 
                if (sent + dsize == sndcnt)
index eef8574..1f345ae 100644 (file)
@@ -101,6 +101,7 @@ struct block *padblock(struct block *bp, int size)
        uint8_t bcksum = bp->flag & BCKSUM_FLAGS;
        uint16_t checksum_start = bp->checksum_start;
        uint16_t checksum_offset = bp->checksum_offset;
+       uint16_t mss = bp->mss;
 
        QDEBUG checkb(bp, "padblock 1");
        if (size >= 0) {
@@ -144,6 +145,7 @@ struct block *padblock(struct block *bp, int size)
                nbp->flag |= bcksum;
                nbp->checksum_start = checksum_start;
                nbp->checksum_offset = checksum_offset;
+               nbp->mss = mss;
        }
        QDEBUG checkb(nbp, "padblock 1");
        return nbp;
@@ -235,6 +237,7 @@ struct block *linearizeblock(struct block *b)
                newb->flag |= (b->flag & BCKSUM_FLAGS);
                newb->checksum_start = b->checksum_start;
                newb->checksum_offset = b->checksum_offset;
+               newb->mss = b->mss;
        }
        freeb(b);
        return newb;
@@ -404,6 +407,7 @@ struct block *copyblock(struct block *bp, int count)
                nbp->flag |= (bp->flag & BCKSUM_FLAGS);
                nbp->checksum_start = bp->checksum_start;
                nbp->checksum_offset = bp->checksum_offset;
+               nbp->mss = bp->mss;
        }
        WARN_EXTRA(bp);
        for (; count > 0 && bp != 0; bp = bp->next) {