Block extra_data
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 3 Jul 2014 04:17:35 +0000 (21:17 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 3 Jul 2014 04:17:35 +0000 (21:17 -0700)
Extends blocks to have an array of pointers to extra data.  The old
block buffer is now the 'main body'.  A block's data consists of the
main body, followed by the extra_data buffers, in order.

The extra data are currently kmalloc refcnt'd and are kfreed when the
block is freed.  Later, we'll want some way to handle other, probably
refcnt'd buffers.

Blocks are ideally kept in their scattered state, but most of our NIC
drivers do not know how to handle this yet.  For those, we linearize the
block before pushing it out devether.

Checksums, qdiscards, copies, clones, etc, all needed to be changed to
know about the new packet type.  There are also a lot of places where we
don't handle the new block types.  If you see any of the warnings, then
let me know.

Likewise, if you have weird networking failures, turn off
CONFIG_BLOCK_EXTRAS and see if that fixes it.  Regardless, let me know
the outcome.

Kconfig
kern/drivers/dev/ether.c
kern/include/ns.h
kern/src/net/icmp.c
kern/src/net/ip.c
kern/src/net/ipaux.c
kern/src/ns/allocb.c
kern/src/ns/qio.c

diff --git a/Kconfig b/Kconfig
index 8fa0228..ecb9ff1 100644 (file)
--- a/Kconfig
+++ b/Kconfig
@@ -198,6 +198,15 @@ config SYSCALL_STRING_SAVING
                kthread, which can be viewed when debugging semaphores.  Individual
                syscalls can save info in this buffer.
 
+config BLOCK_EXTRAS
+       bool "Block Extra Data"
+       default y
+       help
+               Extends blocks to have a list of other memory blocks.  Useful for
+               networking performance.  This is only an option while we debug the
+               implementation.  Say y.  If you have networking bugs, try turning this
+               off, and if that helps, tell someone.
+
 endmenu
 
 config VM
index dbdc8e0..4319b86 100644 (file)
@@ -360,6 +360,8 @@ static int etheroq(struct ether *ether, struct block *bp)
 
        ether->netif.outpackets++;
 
+       if (!(ether->netif.feat & NETF_SG))
+               bp = linearizeblock(bp);
        /*
         * Check if the packet has to be placed back onto the input queue,
         * i.e. if it's a loopback or broadcast packet or the interface is
index ca358a7..667971b 100644 (file)
@@ -332,6 +332,13 @@ enum {
 };
 #define BCKSUM_FLAGS (Bipck|Budpck|Btcpck|Bpktck|Btso)
 
+struct extra_bdata {
+       uintptr_t base;
+       /* using u32s for packing reasons.  this means no extras > 4GB */
+       uint32_t off;
+       uint32_t len;
+};
+
 struct block {
        struct block *next;
        struct block *list;
@@ -343,11 +350,16 @@ struct block {
        uint16_t flag;
        uint16_t checksum;                      /* IP checksum of complete packet (minus media header) */
        uint16_t checksum_start;                /* off from start of block to start csum */
-       uint16_t checksum_offset;               /* off from checksum_offset to store csum */
+       uint16_t checksum_offset;               /* off from checksum_start to store csum */
        uint16_t mss;               /* TCP MSS for TSO */
+       /* might want something to track the next free extra_data slot */
+       size_t extra_len;
+       unsigned int nr_extra_bufs;
+       struct extra_bdata *extra_data;
 };
-#define BLEN(s)        ((s)->wp - (s)->rp)
-#define BALLOC(s) ((s)->lim - (s)->base)
+#define BLEN(s)        ((s)->wp - (s)->rp + (s)->extra_len)
+#define BHLEN(s) ((s)->wp - (s)->rp)
+#define BALLOC(s) ((s)->lim - (s)->base + (s)->extra_len)
 
 struct chan {
        spinlock_t lock;
@@ -605,6 +617,7 @@ void addprog(struct proc *);
 void addrootfile(char *unused_char_p_t, uint8_t * unused_uint8_p_t, uint32_t);
 struct block *adjustblock(struct block *, int);
 struct block *allocb(int);
+void block_add_extd(struct block *b, unsigned int nr_bufs, int mem_flags);
 int anyhigher(void);
 int anyready(void);
 void _assert(char *unused_char_p_t);
@@ -634,6 +647,7 @@ struct mhead *newmhead(struct chan *from);
 int cmount(struct chan *, struct chan *, int unused_int, char *unused_char_p_t);
 void cnameclose(struct cname *);
 struct block *concatblock(struct block *);
+struct block *linearizeblock(struct block *b);
 void confinit(void);
 void copen(struct chan *);
 struct block *copyblock(struct block *, int);
@@ -708,6 +722,7 @@ void hnputl(void *, uint32_t);
 void hnputs(void *, uint16_t);
 struct block *iallocb(int);
 void iallocsummary(void);
+void printblock(struct block *b);
 void ilock(spinlock_t *);
 int iprint(char *unused_char_p_t, ...);
 void isdir(struct chan *);
@@ -779,6 +794,10 @@ int qcanread(struct queue *);
 void qclose(struct queue *);
 int qconsume(struct queue *, void *, int);
 struct block *qcopy(struct queue *, int unused_int, uint32_t);
+struct block *qclone(struct queue *q, int header_len, int len,
+                     uint32_t offset);
+struct block *blist_clone(struct block *blist, int header_len, int len,
+                          uint32_t offset);
 int qdiscard(struct queue *, int);
 void qflush(struct queue *);
 void qfree(struct queue *);
index 740993c..67f7781 100644 (file)
@@ -176,10 +176,9 @@ static void icmpkick(void *x, struct block *bp)
        if (bp == NULL)
                return;
 
-       if (blocklen(bp) < ICMP_IPSIZE + ICMP_HDRSIZE) {
-               freeblist(bp);
+       bp = pullupblock(bp, ICMP_IPSIZE + ICMP_HDRSIZE);
+       if (bp == 0)
                return;
-       }
        p = (Icmp *) (bp->rp);
        p->vihl = IP_VER4;
        ipriv = c->p->priv;
index c8f644a..bde05d4 100644 (file)
@@ -296,7 +296,7 @@ ipoput4(struct Fs *f,
 
        /* If we dont need to fragment just send it */
        medialen = ifc->maxtu - ifc->m->hsize;
-       if (len <= medialen) {
+       if (bp->flag & Btso || len <= medialen) {
                if (!gating)
                        hnputs(eh->id, NEXT_ID(ip->id4));
                hnputs(eh->length, len);
@@ -342,6 +342,8 @@ ipoput4(struct Fs *f,
        else
                lid = NEXT_ID(ip->id4);
 
+       /* advance through the blist enough to drop IP4HDR size.  this should
+        * usually just be the first block. */
        offset = IP4HDR;
        while (xp != NULL && offset && offset >= BLEN(xp)) {
                offset -= BLEN(xp);
@@ -355,7 +357,7 @@ ipoput4(struct Fs *f,
                fragoff = 0;
        dlen += fragoff;
        for (; fragoff < dlen; fragoff += seglen) {
-               nb = allocb(IP4HDR + seglen);
+               nb = blist_clone(xp, IP4HDR, seglen, fragoff);
                feh = (struct Ip4hdr *)(nb->rp);
 
                memmove(nb->wp, eh, IP4HDR);
@@ -370,27 +372,6 @@ ipoput4(struct Fs *f,
                hnputs(feh->length, seglen + IP4HDR);
                hnputs(feh->id, lid);
 
-               /* Copy up the data area */
-               chunk = seglen;
-               while (chunk) {
-                       if (!xp) {
-                               ip->stats[OutDiscards]++;
-                               ip->stats[FragFails]++;
-                               freeblist(nb);
-                               netlog(f, Logip, "!xp: chunk %d\n", chunk);
-                               goto raise;
-                       }
-                       blklen = chunk;
-                       if (BLEN(xp) < chunk)
-                               blklen = BLEN(xp);
-                       memmove(nb->wp, xp->rp, blklen);
-                       nb->wp += blklen;
-                       xp->rp += blklen;
-                       chunk -= blklen;
-                       if (xp->rp == xp->wp)
-                               xp = xp->next;
-               }
-
                feh->cksum[0] = 0;
                feh->cksum[1] = 0;
                hnputs(feh->cksum, ipcsum(&feh->vihl));
index 5ac4872..5c070b8 100644 (file)
@@ -216,6 +216,53 @@ uint8_t v6solicitednodemask[IPaddrlen] = {
 
 int v6snpreflen = 13;
 
+uint16_t ptclcsum_one(struct block *bp, int offset, int len)
+{
+       uint8_t *addr;
+       uint32_t losum, hisum;
+       uint16_t csum;
+       int odd, blocklen, x, i, boff;
+       struct extra_bdata *ebd;
+
+       hisum = 0;
+       losum = 0;
+       odd = 0;
+
+       if (offset < BHLEN(bp)) {
+               x = MIN(len, BHLEN(bp) - offset);
+               odd = (odd + x) & 1;
+               addr = bp->rp + offset;
+               losum = ptclbsum(addr, x);
+               len -= x;
+               offset = 0;
+       } else {
+               offset -= BHLEN(bp);
+       }
+       for (int i = 0; (i < bp->nr_extra_bufs) && len; i++) {
+               ebd = &bp->extra_data[i];
+               boff = MIN(offset, ebd->len);
+               if (offset) {
+                       offset -= boff;
+                       if (offset)
+                               continue;
+               }
+               x = MIN(len, ebd->len - boff);
+               addr = (void *)(ebd->base + ebd->off);
+               if (odd)
+                       hisum += ptclbsum(addr, x);
+               else
+                       losum += ptclbsum(addr, x);
+               len -= x;
+
+       }
+       losum += hisum >> 8;
+       losum += (hisum & 0xff) << 8;
+       while ((csum = losum >> 16) != 0)
+               losum = csum + (losum & 0xffff);
+
+       return losum & 0xffff;
+}
+
 uint16_t ptclcsum(struct block *bp, int offset, int len)
 {
        uint8_t *addr;
@@ -234,7 +281,7 @@ uint16_t ptclcsum(struct block *bp, int offset, int len)
        addr = bp->rp + offset;
        blocklen = BLEN(bp) - offset;
 
-       if (bp->next == NULL) {
+       if (bp->next == NULL && bp->extra_len == 0) {
                if (blocklen < len)
                        len = blocklen;
                return ~ptclbsum(addr, len) & 0xffff;
@@ -245,11 +292,8 @@ uint16_t ptclcsum(struct block *bp, int offset, int len)
 
        odd = 0;
        while (len) {
-               x = blocklen;
-               if (len < x)
-                       x = len;
-
-               csum = ptclbsum(addr, x);
+               x = MIN(blocklen, len);
+               csum = ptclcsum_one(bp, offset, x);
                if (odd)
                        hisum += csum;
                else
@@ -261,7 +305,7 @@ uint16_t ptclcsum(struct block *bp, int offset, int len)
                if (bp == NULL)
                        break;
                blocklen = BLEN(bp);
-               addr = bp->rp;
+               offset = 0;
        }
 
        losum += hisum >> 8;
index 982de7a..e6125f2 100644 (file)
@@ -14,8 +14,9 @@
 #include <ip.h>
 #include <process.h>
 
+/* Note that Hdrspc is only available via padblock (to the 'left' of the rp). */
 enum {
-       Hdrspc = 64,                            /* leave room for high-level headers */
+       Hdrspc = 128,           /* leave room for high-level headers */
        Bdead = 0x51494F42,     /* "QIOB" */
        BLOCKALIGN = 32,        /* was the old BY2V in inferno, which was 8 */
 };
@@ -42,6 +43,9 @@ static struct block *_allocb(int size, int mem_flags)
        b->list = NULL;
        b->free = NULL;
        b->flag = 0;
+       b->extra_len = 0;
+       b->nr_extra_bufs = 0;
+       b->extra_data = 0;
 
        addr = (uintptr_t) b;
        addr = ROUNDUP(addr + sizeof(struct block), BLOCKALIGN);
@@ -71,6 +75,33 @@ struct block *allocb(int size)
        return _allocb(size, KMALLOC_WAIT);
 }
 
+/* Makes sure b has nr_bufs extra_data.  Will grow, but not shrink, an existing
+ * extra_data array.  When growing, it'll copy over the old entries.  All new
+ * entries will be zeroed.  mem_flags determines if we'll block on kmallocs.
+ *
+ * Caller is responsible for concurrent access to the block's metadata. */
+void block_add_extd(struct block *b, unsigned int nr_bufs, int mem_flags)
+{
+       unsigned int old_nr_bufs = b->nr_extra_bufs;
+       size_t old_amt = sizeof(struct extra_bdata) * old_nr_bufs;
+       size_t new_amt = sizeof(struct extra_bdata) * nr_bufs;
+       void *new_bdata;
+
+       if (old_nr_bufs >= nr_bufs)
+               return;
+       if (b->extra_data) {
+               new_bdata = krealloc(b->extra_data, new_amt, mem_flags);
+               if (!new_bdata)
+                       return;
+               memset(new_bdata + old_amt, 0, new_amt - old_amt);
+       } else {
+               new_bdata = kzmalloc(new_amt, mem_flags);
+               if (!new_bdata)
+                       return;
+       }
+       b->extra_data = new_bdata;
+       b->nr_extra_bufs = nr_bufs;
+}
 
 /*
  *  interrupt time allocation
@@ -103,10 +134,20 @@ struct block *iallocb(int size)
 void freeb(struct block *b)
 {
        void *dead = (void *)Bdead;
+       struct extra_bdata *ebd;
 
        if (b == NULL)
                return;
 
+       /* assuming our release method is kfree, which will change when we support
+        * user buffers */
+       for (int i = 0; i < b->nr_extra_bufs; i++) {
+               ebd = &b->extra_data[i];
+               if (ebd->base)
+                       kfree((void*)ebd->base);
+       }
+       kfree(b->extra_data);   /* harmless if it is 0 */
+       b->extra_data = 0;              /* in case the block is reused by a free override */
        /*
         * drivers which perform non cache coherent DMA manage their own buffer
         * pool of uncached buffers and provide their own free routine.
@@ -133,6 +174,7 @@ void freeb(struct block *b)
 void checkb(struct block *b, char *msg)
 {
        void *dead = (void *)Bdead;
+       struct extra_bdata *ebd;
 
        if (b == dead)
                panic("checkb b %s 0x%lx", msg, b);
@@ -154,6 +196,15 @@ void checkb(struct block *b, char *msg)
                panic("checkb 3 %s 0x%lx 0x%lx", msg, b->rp, b->lim);
        if (b->wp > b->lim)
                panic("checkb 4 %s 0x%lx 0x%lx", msg, b->wp, b->lim);
+       if (b->nr_extra_bufs && !b->extra_data)
+               panic("checkb 5 %s missing extra_data", msg);
+
+       for (int i = 0; i < b->nr_extra_bufs; i++) {
+               ebd = &b->extra_data[i];
+               if (ebd->base) {
+                       assert(kmalloc_refcnt((void*)ebd->base));
+               }
+       }
 
 }
 
@@ -161,3 +212,44 @@ void iallocsummary(void)
 {
        printd("ialloc %lu/%lu\n", atomic_read(&ialloc_bytes), 0 /*conf.ialloc */ );
 }
+
+void printblock(struct block *b)
+{
+       unsigned char *c;
+       unsigned int off, elen;
+       struct extra_bdata *e;
+
+       printk("block of BLEN = %d, with %d header and %d data in %d extras\n",
+              BLEN(b), BHLEN(b), b->extra_len, b->nr_extra_bufs);
+
+       printk("header:\n");
+       printk("%2x:\t", 0);
+       off = 0;
+       for (c = b->rp; c < b->wp; c++) {
+               printk("  %02x", *c & 0xff);
+               off++;
+               if (off % 8 == 0) {
+                       printk("\n");
+                       printk("%2x:\t", off);
+               }
+       }
+       printk("\n");
+       elen = b->extra_len;
+       for (int i = 0; (i < b->nr_extra_bufs) && elen; i++) {
+               e = &b->extra_data[i];
+               if (e->len == 0)
+                       continue;
+               elen -= e->len;
+               printk("data %d:\n", i);
+               printk("%2x:\t", 0);
+               for (off = 0; off < e->len; off++) {
+                       c = (unsigned char *)e->base + e->off + off;
+                       printk("  %02x", *c & 0xff);
+                       if ((off + 1) % 8 == 0 && off +1 < e->len) {
+                               printk("\n");
+                               printk("%2x:\t", off + 1);
+                       }
+               }
+       }
+       printk("\n");
+}
index a3687fa..2403bd6 100644 (file)
 #include <smp.h>
 #include <ip.h>
 
+#define WARN_EXTRA(b)                                                          \
+{                                                                              \
+       if ((b)->extra_len)                                                        \
+               warn_once("%s doesn't handle extra_data", __FUNCTION__);               \
+}
+
 static uint32_t padblockcnt;
 static uint32_t concatblockcnt;
 static uint32_t pullupblockcnt;
@@ -104,6 +110,7 @@ struct block *padblock(struct block *bp, int size)
                        return bp;
                }
 
+               WARN_EXTRA(bp);
                if (bp->next)
                        panic("padblock %p", getcallerpc(&bp));
                n = BLEN(bp);
@@ -118,6 +125,8 @@ struct block *padblock(struct block *bp, int size)
        } else {
                size = -size;
 
+               WARN_EXTRA(bp);
+
                if (bp->next)
                        panic("padblock %p", getcallerpc(&bp));
 
@@ -182,6 +191,8 @@ struct block *concatblock(struct block *bp)
        if (bp->next == 0)
                return bp;
 
+       /* probably use parts of qclone */
+       WARN_EXTRA(bp);
        nb = allocb(blocklen(bp));
        for (f = bp; f; f = f->next) {
                len = BLEN(f);
@@ -194,21 +205,92 @@ struct block *concatblock(struct block *bp)
        return nb;
 }
 
+/* Returns a block with the remaining contents of b all in the main body of the
+ * returned block.  Replace old references to b with the returned value (which
+ * may still be 'b', if no change was needed. */
+struct block *linearizeblock(struct block *b)
+{
+       struct block *newb;
+       size_t len;
+       struct extra_bdata *ebd;
+
+       if (!b->extra_len)
+               return b;
+
+       newb = allocb(BLEN(b));
+       len = BHLEN(b);
+       memcpy(newb->wp, b->rp, len);
+       newb->wp += len;
+       len = b->extra_len;
+       for (int i = 0; (i < b->nr_extra_bufs) && len; i++) {
+               ebd = &b->extra_data[i];
+               if (!ebd->base || !ebd->len)
+                       continue;
+               memcpy(newb->wp, (void*)(ebd->base + ebd->off), ebd->len);
+               newb->wp += ebd->len;
+               len -= ebd->len;
+       }
+       /* TODO: any other flags that need copied over? */
+       if (b->flag & BCKSUM_FLAGS) {
+               newb->flag |= (b->flag & BCKSUM_FLAGS);
+               newb->checksum_start = b->checksum_start;
+               newb->checksum_offset = b->checksum_offset;
+       }
+       freeb(b);
+       return newb;
+}
+
 /*
- *  make sure the first block has at least n bytes
+ *  make sure the first block has at least n bytes in its main body
  */
 struct block *pullupblock(struct block *bp, int n)
 {
-       int i;
+       int i, len, seglen;
        struct block *nbp;
+       struct extra_bdata *ebd;
 
        /*
         *  this should almost always be true, it's
         *  just to avoid every caller checking.
         */
-       if (BLEN(bp) >= n)
+       if (BHLEN(bp) >= n)
                return bp;
 
+        /* a start at explicit main-body / header management */
+       if (bp->extra_len) {
+               if (n > bp->lim - bp->rp) {
+                       /* would need to realloc a new block and copy everything over. */
+                       panic("can't pullup, no place to put it\n");
+               }
+               len = n - BHLEN(bp);
+               if (len > bp->extra_len)
+                       panic("pullup more than extra (%d, %d, %d)\n",
+                             n, BHLEN(bp), bp->extra_len);
+               checkb(bp, "before pullup");
+               for (int i = 0; (i < bp->nr_extra_bufs) && len; i++) {
+                       ebd = &bp->extra_data[i];
+                       if (!ebd->base || !ebd->len)
+                               continue;
+                       seglen = MIN(ebd->len, len);
+                       memcpy(bp->wp, (void*)(ebd->base + ebd->off), seglen);
+                       bp->wp += seglen;
+                       len -= seglen;
+                       ebd->len -= seglen;
+                       ebd->off += seglen;
+                       bp->extra_len -= seglen;
+                       if (ebd->len == 0) {
+                               kfree((void *)ebd->base);
+                               ebd->len = 0;
+                               ebd->off = 0;
+                       }
+               }
+               /* maybe just call pullupblock recursively here */
+               if (len)
+                       panic("pullup %d bytes overdrawn\n", len);
+               checkb(bp, "after pullup");
+               return bp;
+       }
+
        /*
         *  if not enough room in the first block,
         *  add another to the front of the list.
@@ -257,6 +339,7 @@ struct block *pullupqueue(struct queue *q, int n)
 {
        struct block *b;
 
+       /* TODO: lock to protect the queue links? */
        if ((BLEN(q->bfirst) >= n))
                return q->bfirst;
        q->bfirst = pullupblock(q->bfirst, n);
@@ -287,6 +370,7 @@ struct block *trimblock(struct block *bp, int offset, int len)
                bp = nb;
        }
 
+       WARN_EXTRA(bp);
        startb = bp;
        bp->rp += offset;
 
@@ -320,6 +404,7 @@ struct block *copyblock(struct block *bp, int count)
                nbp->checksum_start = bp->checksum_start;
                nbp->checksum_offset = bp->checksum_offset;
        }
+       WARN_EXTRA(bp);
        for (; count > 0 && bp != 0; bp = bp->next) {
                l = BLEN(bp);
                if (l > count)
@@ -348,6 +433,7 @@ struct block *adjustblock(struct block *bp, int len)
                return NULL;
        }
 
+       WARN_EXTRA(bp);
        if (bp->rp + len > bp->lim) {
                nbp = copyblock(bp, len);
                freeblist(bp);
@@ -381,6 +467,8 @@ int pullblock(struct block **bph, int count)
 
        while (*bph != NULL && count != 0) {
                bp = *bph;
+       WARN_EXTRA(bp);
+
                n = BLEN(bp);
                if (count < n)
                        n = count;
@@ -442,7 +530,8 @@ struct block *qget(struct queue *q)
 int qdiscard(struct queue *q, int len)
 {
        struct block *b;
-       int dowakeup, n, sofar;
+       int dowakeup, n, sofar, body_amt, extra_amt;
+       struct extra_bdata *ebd;
 
        spin_lock_irqsave(&q->lock);
        for (sofar = 0; sofar < len; sofar += n) {
@@ -459,8 +548,32 @@ int qdiscard(struct queue *q, int len)
                        freeb(b);
                } else {
                        n = len - sofar;
-                       b->rp += n;
                        q->dlen -= n;
+                       /* partial block removal */
+                       body_amt = MIN(BHLEN(b), n);
+                       b->rp += body_amt;
+                       extra_amt = n - body_amt;
+                       /* reduce q->len by the amount we remove from the extras.  The
+                        * header will always be accounted for above, during block removal.
+                        * */
+                       q->len -= extra_amt;
+                       for (int i = 0; (i < b->nr_extra_bufs) && extra_amt; i++) {
+                               ebd = &b->extra_data[i];
+                               if (!ebd->base || !ebd->len)
+                                       continue;
+                               if (extra_amt >= ebd->len) {
+                                       /* remove the entire entry, note the kfree release */
+                                       b->extra_len -= ebd->len;
+                                       extra_amt -= ebd->len;
+                                       kfree((void*)ebd->base);
+                                       ebd->base = ebd->off = ebd->len = 0;
+                                       continue;
+                               }
+                               ebd->off += extra_amt;
+                               ebd->len -= extra_amt;
+                               b->extra_len -= extra_amt;
+                               extra_amt = 0;
+                       }
                }
        }
 
@@ -521,6 +634,7 @@ int qconsume(struct queue *q, void *vp, int len)
                tofree = b;
        };
 
+       WARN_EXTRA(b);
        if (n < len)
                len = n;
        memmove(p, b->rp, len);
@@ -666,6 +780,7 @@ struct block *packblock(struct block *bp)
        struct block **l, *nbp;
        int n;
 
+       WARN_EXTRA(bp);
        for (l = &bp; *l; l = &(*l)->next) {
                nbp = *l;
                n = BLEN(nbp);
@@ -719,6 +834,7 @@ int qproduce(struct queue *q, void *vp, int len)
                /* b->next = 0; done by iallocb() */
                q->len += BALLOC(b);
        }
+       WARN_EXTRA(b);
        memmove(b->wp, p, len);
        producecnt += len;
        b->wp += len;
@@ -740,10 +856,166 @@ int qproduce(struct queue *q, void *vp, int len)
        return len;
 }
 
+/* Add an extra_data entry to newb at newb_idx pointing to b's body, starting at
+ * body_rp, for up to len.  Returns the len consumed. 
+ *
+ * The base is 'b', so that we can kfree it later.  This currently ties us to
+ * using kfree for the release method for all extra_data.
+ *
+ * It is possible to have a body size that is 0, if there is no offset, and
+ * b->wp == b->rp.  This will have an extra data entry of 0 length. */
+static size_t point_to_body(struct block *b, uint8_t *body_rp,
+                            struct block *newb, unsigned int newb_idx,
+                            size_t len)
+{
+       struct extra_bdata *ebd = &newb->extra_data[newb_idx];
+
+       assert(newb_idx < newb->nr_extra_bufs);
+
+       kmalloc_incref(b);
+       ebd->base = (uintptr_t)b;
+       ebd->off = (uint32_t)(body_rp - (uint8_t*)b);
+       ebd->len = MIN(b->wp - body_rp, len);   /* think of body_rp as b->rp */
+       assert((int)ebd->len >= 0);
+       newb->extra_len += ebd->len;
+       return ebd->len;
+}
+
+/* Add an extra_data entry to newb at newb_idx pointing to b's b_idx'th
+ * extra_data buf, at b_off within that buffer, for up to len.  Returns the len
+ * consumed.
+ *
+ * We can have blocks with 0 length, but they are still refcnt'd.  See above. */
+static size_t point_to_buf(struct block *b, unsigned int b_idx, uint32_t b_off,
+                           struct block *newb, unsigned int newb_idx,
+                           size_t len)
+{
+       struct extra_bdata *n_ebd = &newb->extra_data[newb_idx];
+       struct extra_bdata *b_ebd = &b->extra_data[b_idx];
+
+       assert(b_idx < b->nr_extra_bufs);
+       assert(newb_idx < newb->nr_extra_bufs);
+
+       kmalloc_incref((void*)b_ebd->base);
+       n_ebd->base = b_ebd->base;
+       n_ebd->off = b_ebd->off + b_off;
+       n_ebd->len = MIN(b_ebd->len - b_off, len);
+       newb->extra_len += n_ebd->len;
+       return n_ebd->len;
+}
+
+/* given a string of blocks, fills the new block's extra_data  with the contents
+ * of the blist [offset, len + offset)
+ *
+ * returns 0 on success.  the only failure is if the extra_data array was too
+ * small, so this returns a positive integer saying how big the extra_data needs
+ * to be.
+ *
+ * callers are responsible for protecting the list structure. */
+static int __blist_clone_to(struct block *blist, struct block *newb, int len,
+                            uint32_t offset)
+{
+       struct block *b, *first;
+       unsigned int nr_bufs = 0;
+       unsigned int b_idx, newb_idx = 0;
+       uint8_t *first_main_body = 0;
+
+       /* find the first block; keep offset relative to the latest b in the list */
+       for (b = blist; b; b = b->next) {
+               if (BLEN(b) > offset)
+                       break;
+               offset -= BLEN(b);
+       }
+       /* qcopy semantics: if you asked for an offset outside the block list, you
+        * get an empty block back */
+       if (!b)
+               return 0;
+       first = b;
+       /* upper bound for how many buffers we'll need in newb */
+       for (/* b is set*/; b; b = b->next) {
+               nr_bufs += 1 + b->nr_extra_bufs;        /* 1 for the main body */
+       }
+       /* we might be holding a spinlock here, so we won't wait for kmalloc */
+       block_add_extd(newb, nr_bufs, 0);
+       if (newb->nr_extra_bufs < nr_bufs) {
+               /* caller will need to alloc these, then re-call us */
+               return nr_bufs;
+       }
+       for (b = first; b && len; b = b->next) {
+               b_idx = 0;
+               if (offset) {
+                       if (offset < BHLEN(b)) {
+                               /* off is in the main body */
+                               len -= point_to_body(b, b->rp + offset, newb, newb_idx, len);
+                               newb_idx++;
+                       } else {
+                               /* off is in one of the buffers (or just past the last one).
+                                * we're not going to point to b's main body at all. */
+                               offset -= BHLEN(b);
+                               assert(b->extra_data);
+                               /* assuming these extrabufs are packed, or at least that len
+                                * isn't gibberish */
+                               while (b->extra_data[b_idx].len <= offset) {
+                                       offset -= b->extra_data[b_idx].len;
+                                       b_idx++;
+                               }
+                               /* now offset is set to our offset in the b_idx'th buf */
+                               len -= point_to_buf(b, b_idx, offset, newb, newb_idx, len);
+                               newb_idx++;
+                               b_idx++;
+                       }
+                       offset = 0;
+               } else {
+                       len -= point_to_body(b, b->rp, newb, newb_idx, len);
+                       newb_idx++;
+               }
+               /* knock out all remaining bufs.  we only did one point_to_ op by now,
+                * and any point_to_ could be our last if it consumed all of len. */
+               for (int i = b_idx; (i < b->nr_extra_bufs) && len; i++) {
+                       len -= point_to_buf(b, i, 0, newb, newb_idx, len);
+                       newb_idx++;
+               }
+       }
+       return 0;
+}
+
+struct block *blist_clone(struct block *blist, int header_len, int len,
+                          uint32_t offset)
+{
+       int ret;
+       struct block *newb = allocb(header_len);
+       do {
+               ret = __blist_clone_to(blist, newb, len, offset);
+               if (ret)
+                       block_add_extd(newb, ret, KMALLOC_WAIT);
+       } while (ret);
+       return newb;
+}
+
+/* given a queue, makes a single block with header_len reserved space in the
+ * block main body, and the contents of [offset, len + offset) pointed to in the
+ * new blocks ext_data. */
+struct block *qclone(struct queue *q, int header_len, int len, uint32_t offset)
+{
+       int ret;
+       struct block *newb = allocb(header_len);
+       /* the while loop should rarely be used: it would require someone
+        * concurrently adding to the queue. */
+       do {
+               /* TODO: RCU: protecting the q list (b->next) (need read lock) */
+               spin_lock_irqsave(&q->lock);
+               ret = __blist_clone_to(q->bfirst, newb, len, offset);
+               spin_unlock_irqsave(&q->lock);
+               if (ret)
+                       block_add_extd(newb, ret, KMALLOC_WAIT);
+       } while (ret);
+       return newb;
+}
+
 /*
  *  copy from offset in the queue
  */
-struct block *qcopy(struct queue *q, int len, uint32_t offset)
+struct block *qcopy_old(struct queue *q, int len, uint32_t offset)
 {
        int sofar;
        int n;
@@ -775,6 +1047,7 @@ struct block *qcopy(struct queue *q, int len, uint32_t offset)
        for (sofar = 0; sofar < len;) {
                if (n > len - sofar)
                        n = len - sofar;
+               WARN_EXTRA(b);
                memmove(nb->wp, p, n);
                qcopycnt += n;
                sofar += n;
@@ -790,6 +1063,15 @@ struct block *qcopy(struct queue *q, int len, uint32_t offset)
        return nb;
 }
 
+struct block *qcopy(struct queue *q, int len, uint32_t offset)
+{
+#ifdef CONFIG_BLOCK_EXTRAS
+       return qclone(q, 0, len, offset);
+#else
+       return qcopy_old(q, len, offset);
+#endif
+}
+
 static void qinit_common(struct queue *q)
 {
        spinlock_init_irqsave(&q->lock);
@@ -880,6 +1162,7 @@ static int qwait(struct queue *q)
  */
 void qaddlist(struct queue *q, struct block *b)
 {
+       /* TODO: q lock? */
        /* queue the block */
        if (q->bfirst)
                q->blast->next = b;
@@ -910,6 +1193,45 @@ struct block *qremove(struct queue *q)
        return b;
 }
 
+static size_t read_from_block(struct block *b, uint8_t *to, size_t amt)
+{
+       size_t copy_amt, retval = 0;
+       struct extra_bdata *ebd;
+       
+       copy_amt = MIN(BHLEN(b), amt);
+       memcpy(to, b->rp, copy_amt);
+       /* advance the rp, since this block not be completely consumed and future
+        * reads need to know where to pick up from */
+       b->rp += copy_amt;
+       to += copy_amt;
+       amt -= copy_amt;
+       retval += copy_amt;
+       for (int i = 0; (i < b->nr_extra_bufs) && amt; i++) {
+               ebd = &b->extra_data[i];
+               /* skip empty entires.  if we track this in the struct block, we can
+                * just start the for loop early */
+               if (!ebd->base || !ebd->len)
+                       continue;
+               copy_amt = MIN(ebd->len, amt);
+               memcpy(to, (void*)(ebd->base + ebd->off), copy_amt);
+               /* we're actually consuming the entries, just like how we advance rp up
+                * above, and might only consume part of one. */
+               ebd->len -= copy_amt;
+               ebd->off += copy_amt;
+               b->extra_len -= copy_amt;
+               if (!ebd->len) {
+                       /* we don't actually have to decref here.  it's also done in
+                        * freeb().  this is the earliest we can free. */
+                       kfree((void*)ebd->base);
+                       ebd->base = ebd->off = 0;
+               }
+               to += copy_amt;
+               amt -= copy_amt;
+               retval += copy_amt;
+       }
+       return retval;
+}
+
 /*
  *  copy the contents of a string of blocks into
  *  memory.  emptied blocks are freed.  return
@@ -920,17 +1242,18 @@ struct block *bl2mem(uint8_t * p, struct block *b, int n)
        int i;
        struct block *next;
 
+       /* could be slicker here, since read_from_block is smart */
        for (; b != NULL; b = next) {
                i = BLEN(b);
                if (i > n) {
-                       memmove(p, b->rp, n);
-                       b->rp += n;
+                       /* partial block, consume some */
+                       read_from_block(b, p, n);
                        return b;
                }
-               memmove(p, b->rp, i);
+               /* full block, consume all and move on */
+               i = read_from_block(b, p, i);
                n -= i;
                p += i;
-               b->rp += i;
                next = b->next;
                freeb(b);
        }
@@ -959,6 +1282,7 @@ struct block *mem2bl(uint8_t * p, int len)
                        n = Maxatomic;
 
                *l = b = allocb(n);
+               /* TODO consider extra_data */
                memmove(b->wp, p, n);
                b->wp += n;
                p += n;
@@ -1044,6 +1368,7 @@ struct block *qbread(struct queue *q, int len)
        /* split block if it's too big and this is not a message queue */
        nb = b;
        if (n > len) {
+               WARN_EXTRA(b);
                if ((q->state & Qmsg) == 0) {
                        n -= len;
                        b = allocb(n);
@@ -1299,10 +1624,10 @@ long qibwrite(struct queue *q, struct block *b)
  */
 int qwrite(struct queue *q, void *vp, int len)
 {
-       ERRSTACK(1);
        int n, sofar;
        struct block *b;
        uint8_t *p = vp;
+       void *ext_buf;
 
        QDEBUG if (!islo())
                 printd("qwrite hi %p\n", getcallerpc(&q));
@@ -1310,18 +1635,32 @@ int qwrite(struct queue *q, void *vp, int len)
        sofar = 0;
        do {
                n = len - sofar;
+               /* This is 64K, the max amount per single block.  Still a good value? */
                if (n > Maxatomic)
                        n = Maxatomic;
 
+               /* If n is small, we don't need to bother with the extra_data.  But
+                * until the whole stack can handle extd blocks, we'll use them
+                * unconditionally. */
+#ifdef CONFIG_BLOCK_EXTRAS
+               /* allocb builds in 128 bytes of header space to all blocks, but this is
+                * only available via padblock (to the left).  we also need some space
+                * for pullupblock for some basic headers (like icmp) that get written
+                * in directly */
+               b = allocb(64);
+               ext_buf = kmalloc(n, 0);
+               memcpy(ext_buf, p + sofar, n);
+               block_add_extd(b, 1, KMALLOC_WAIT); /* returns 0 on success */
+               b->extra_data[0].base = (uintptr_t)ext_buf;
+               b->extra_data[0].off = 0;
+               b->extra_data[0].len = n;
+               b->extra_len += n;
+#else
                b = allocb(n);
-               if (waserror()) {
-                       freeb(b);
-                       nexterror();
-               }
                memmove(b->wp, p + sofar, n);
-               poperror();
                b->wp += n;
-
+#endif
+                       
                qbwrite(q, b);
 
                sofar += n;
@@ -1351,6 +1690,7 @@ int qiwrite(struct queue *q, void *vp, int len)
                b = iallocb(n);
                if (b == NULL)
                        break;
+               /* TODO consider extra_data */
                memmove(b->wp, p + sofar, n);
                /* this adjusts BLEN to be n, or at least it should */
                b->wp += n;