diff --git a/drivers/block/aoe/aoe.h b/drivers/block/aoe/aoe.h index 2248ab226576..67ef4d755e64 100644 --- a/drivers/block/aoe/aoe.h +++ b/drivers/block/aoe/aoe.h @@ -89,6 +89,7 @@ enum { MIN_BUFS = 16, NTARGETS = 8, NAOEIFS = 8, + NSKBPOOLMAX = 128, TIMERTICK = HZ / 10, MINTIMER = HZ >> 2, @@ -138,6 +139,7 @@ struct aoetgt { u16 useme; ulong lastwadj; /* last window adjustment */ int wpkts, rpkts; + int dataref; }; struct aoedev { @@ -159,6 +161,9 @@ 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 */ diff --git a/drivers/block/aoe/aoecmd.c b/drivers/block/aoe/aoecmd.c index 1be5150bcd3b..b49e06ef121e 100644 --- a/drivers/block/aoe/aoecmd.c +++ b/drivers/block/aoe/aoecmd.c @@ -106,45 +106,104 @@ ifrotate(struct aoetgt *t) } } +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; } @@ -894,33 +953,23 @@ addtgt(struct aoedev *d, char *addr, ulong nframes) 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 diff --git a/drivers/block/aoe/aoedev.c b/drivers/block/aoe/aoedev.c index e26f6f4a28a2..839a964906ce 100644 --- a/drivers/block/aoe/aoedev.c +++ b/drivers/block/aoe/aoedev.c @@ -7,11 +7,13 @@ #include #include #include +#include #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; @@ -125,9 +127,10 @@ aoedev_freedev(struct aoedev *d) 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); } @@ -176,6 +179,43 @@ aoedev_flush(const char __user *str, size_t cnt) 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) @@ -215,16 +255,14 @@ 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); }