MIN_BUFS = 16,
        NTARGETS = 8,
        NAOEIFS = 8,
+       NSKBPOOLMAX = 128,
 
        TIMERTICK = HZ / 10,
        MINTIMER = HZ >> 2,
        u16 useme;
        ulong lastwadj;         /* last window adjustment */
        int wpkts, rpkts;
+       int dataref;
 };
 
 struct aoedev {
        spinlock_t lock;
        struct sk_buff *sendq_hd; /* packets needing to be sent, list head */
        struct sk_buff *sendq_tl;
+       struct sk_buff *skbpool_hd;
+       struct sk_buff *skbpool_tl;
+       int nskbpool;
        mempool_t *bufpool;     /* for deadlock-free Buf allocation */
        struct list_head bufq;  /* queue of bios to work on */
        struct buf *inprocess;  /* the one we're currently working on */
 
        }
 }
 
+static void
+skb_pool_put(struct aoedev *d, struct sk_buff *skb)
+{
+       if (!d->skbpool_hd)
+               d->skbpool_hd = skb;
+       else
+               d->skbpool_tl->next = skb;
+       d->skbpool_tl = skb;
+}
+
+static struct sk_buff *
+skb_pool_get(struct aoedev *d)
+{
+       struct sk_buff *skb;
+
+       skb = d->skbpool_hd;
+       if (skb && atomic_read(&skb_shinfo(skb)->dataref) == 1) {
+               d->skbpool_hd = skb->next;
+               skb->next = NULL;
+               return skb;
+       }
+       if (d->nskbpool < NSKBPOOLMAX
+       && (skb = new_skb(ETH_ZLEN))) {
+               d->nskbpool++;
+               return skb;
+       }
+       return NULL;
+}
+
+/* freeframe is where we do our load balancing so it's a little hairy. */
 static struct frame *
 freeframe(struct aoedev *d)
 {
-       struct frame *f, *e;
+       struct frame *f, *e, *rf;
        struct aoetgt **t;
-       ulong n;
+       struct sk_buff *skb;
 
        if (d->targets[0] == NULL) {    /* shouldn't happen, but I'm paranoid */
                printk(KERN_ERR "aoe: NULL TARGETS!\n");
                return NULL;
        }
-       t = d->targets;
-       do {
-               if (t != d->htgt
-               && (*t)->ifp->nd
-               && (*t)->nout < (*t)->maxout) {
-                       n = (*t)->nframes;
+       t = d->tgt;
+       t++;
+       if (t >= &d->targets[NTARGETS] || !*t)
+               t = d->targets;
+       for (;;) {
+               if ((*t)->nout < (*t)->maxout
+               && t != d->htgt
+               && (*t)->ifp->nd) {
+                       rf = NULL;
                        f = (*t)->frames;
-                       e = f + n;
+                       e = f + (*t)->nframes;
                        for (; f < e; f++) {
                                if (f->tag != FREETAG)
                                        continue;
-                               if (atomic_read(&skb_shinfo(f->skb)->dataref)
+                               skb = f->skb;
+                               if (!skb
+                               && !(f->skb = skb = new_skb(ETH_ZLEN)))
+                                       continue;
+                               if (atomic_read(&skb_shinfo(skb)->dataref)
                                        != 1) {
-                                       n--;
+                                       if (!rf)
+                                               rf = f;
                                        continue;
                                }
-                               skb_shinfo(f->skb)->nr_frags = 0;
-                               f->skb->data_len = 0;
-                               skb_trim(f->skb, 0);
+gotone:                                skb_shinfo(skb)->nr_frags = skb->data_len = 0;
+                               skb_trim(skb, 0);
                                d->tgt = t;
                                ifrotate(*t);
                                return f;
                        }
-                       if (n == 0)     /* slow polling network card */
+                       /* Work can be done, but the network layer is
+                          holding our precious packets.  Try to grab
+                          one from the pool. */
+                       f = rf;
+                       if (f == NULL) {        /* more paranoia */
+                               printk(KERN_ERR
+                                       "aoe: freeframe: %s.\n",
+                                       "unexpected null rf");
+                               d->flags |= DEVFL_KICKME;
+                               return NULL;
+                       }
+                       skb = skb_pool_get(d);
+                       if (skb) {
+                               skb_pool_put(d, f->skb);
+                               f->skb = skb;
+                               goto gotone;
+                       }
+                       (*t)->dataref++;
+                       if ((*t)->nout == 0)
                                d->flags |= DEVFL_KICKME;
                }
+               if (t == d->tgt)        /* we've looped and found nada */
+                       break;
                t++;
-       } while (t < &d->targets[NTARGETS] && *t);
+               if (t >= &d->targets[NTARGETS] || !*t)
+                       t = d->targets;
+       }
        return NULL;
 }
 
                return NULL;
 
        t = kcalloc(1, sizeof *t, GFP_ATOMIC);
+       if (!t)
+               return NULL;
        f = kcalloc(nframes, sizeof *f, GFP_ATOMIC);
-       if (!t || !f)
-               goto bail;
+       if (!f) {
+               kfree(t);
+               return NULL;
+       }
+
        t->nframes = nframes;
        t->frames = f;
        e = f + nframes;
-       for (; f < e; f++) {
+       for (; f < e; f++)
                f->tag = FREETAG;
-               f->skb = new_skb(ETH_ZLEN);
-               if (!f->skb)
-                       break;
-       }
-       if (f != e) {
-               while (f > t->frames) {
-                       f--;
-                       dev_kfree_skb(f->skb);
-               }
-               goto bail;
-       }
        memcpy(t->addr, addr, sizeof t->addr);
        t->ifp = t->ifs;
        t->maxout = t->nframes;
        return *tt = t;
-bail:
-       kfree(t);
-       kfree(f);
-       return NULL;
 }
 
 void
 
 #include <linux/hdreg.h>
 #include <linux/blkdev.h>
 #include <linux/netdevice.h>
+#include <linux/delay.h>
 #include "aoe.h"
 
 static void dummy_timer(ulong);
 static void aoedev_freedev(struct aoedev *);
-static void freetgt(struct aoetgt *t);
+static void freetgt(struct aoedev *d, struct aoetgt *t);
+static void skbpoolfree(struct aoedev *d);
 
 static struct aoedev *devlist;
 static spinlock_t devlist_lock;
        t = d->targets;
        e = t + NTARGETS;
        for (; t < e && *t; t++)
-               freetgt(*t);
+               freetgt(d, *t);
        if (d->bufpool)
                mempool_destroy(d->bufpool);
+       skbpoolfree(d);
        kfree(d);
 }
 
        return 0;
 }
 
+/* I'm not really sure that this is a realistic problem, but if the
+network driver goes gonzo let's just leak memory after complaining. */
+static void
+skbfree(struct sk_buff *skb)
+{
+       enum { Sms = 100, Tms = 3*1000};
+       int i = Tms / Sms;
+
+       if (skb == NULL)
+               return;
+       while (atomic_read(&skb_shinfo(skb)->dataref) != 1 && i-- > 0)
+               msleep(Sms);
+       if (i <= 0) {
+               printk(KERN_ERR
+                       "aoe: %s holds ref: %s\n",
+                       skb->dev ? skb->dev->name : "netif",
+                       "cannot free skb -- memory leaked.");
+               return;
+       }
+       skb_shinfo(skb)->nr_frags = skb->data_len = 0;
+       skb_trim(skb, 0);
+       dev_kfree_skb(skb);
+}
+
+static void
+skbpoolfree(struct aoedev *d)
+{
+       struct sk_buff *skb;
+
+       while ((skb = d->skbpool_hd)) {
+               d->skbpool_hd = skb->next;
+               skb->next = NULL;
+               skbfree(skb);
+       }
+       d->skbpool_tl = NULL;
+}
+
 /* find it or malloc it */
 struct aoedev *
 aoedev_by_sysminor_m(ulong sysminor)
 }
 
 static void
-freetgt(struct aoetgt *t)
+freetgt(struct aoedev *d, struct aoetgt *t)
 {
        struct frame *f, *e;
 
        f = t->frames;
        e = f + t->nframes;
-       for (; f < e; f++) {
-               skb_shinfo(f->skb)->nr_frags = 0;
-               dev_kfree_skb(f->skb);
-       }
+       for (; f < e; f++)
+               skbfree(f->skb);
        kfree(t->frames);
        kfree(t);
 }