Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6
[safe/jmp/linux-2.6] / drivers / block / aoe / aoedev.c
index ec16c64..eeea477 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2004 Coraid, Inc.  See COPYING for GPL terms. */
+/* Copyright (c) 2007 Coraid, Inc.  See COPYING for GPL terms. */
 /*
  * aoedev.c
  * AoE device utility functions; maintains device list.
@@ -7,10 +7,16 @@
 #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 aoedev *d, struct aoetgt *t);
+static void skbpoolfree(struct aoedev *d);
+
 static struct aoedev *devlist;
-static spinlock_t devlist_lock;
+static DEFINE_SPINLOCK(devlist_lock);
 
 struct aoedev *
 aoedev_by_aoeaddr(int maj, int min)
@@ -28,127 +34,219 @@ aoedev_by_aoeaddr(int maj, int min)
        return d;
 }
 
-/* called with devlist lock held */
-static struct aoedev *
-aoedev_newdev(ulong nframes)
+static void
+dummy_timer(ulong vp)
 {
        struct aoedev *d;
-       struct frame *f, *e;
-
-       d = kcalloc(1, sizeof *d, GFP_ATOMIC);
-       if (d == NULL)
-               return NULL;
-       f = kcalloc(nframes, sizeof *f, GFP_ATOMIC);
-       if (f == NULL) {
-               kfree(d);
-               return NULL;
-       }
-
-       d->nframes = nframes;
-       d->frames = f;
-       e = f + nframes;
-       for (; f<e; f++)
-               f->tag = FREETAG;
-
-       spin_lock_init(&d->lock);
-       init_timer(&d->timer);
-       d->bufpool = NULL;      /* defer to aoeblk_gdalloc */
-       INIT_LIST_HEAD(&d->bufq);
-       d->next = devlist;
-       devlist = d;
 
-       return d;
+       d = (struct aoedev *)vp;
+       if (d->flags & DEVFL_TKILL)
+               return;
+       d->timer.expires = jiffies + HZ;
+       add_timer(&d->timer);
 }
 
 void
 aoedev_downdev(struct aoedev *d)
 {
+       struct aoetgt **t, **te;
        struct frame *f, *e;
        struct buf *buf;
        struct bio *bio;
 
-       d->flags |= DEVFL_TKILL;
-       del_timer(&d->timer);
-
-       f = d->frames;
-       e = f + d->nframes;
-       for (; f<e; f->tag = FREETAG, f->buf = NULL, f++) {
-               if (f->tag == FREETAG || f->buf == NULL)
-                       continue;
-               buf = f->buf;
-               bio = buf->bio;
-               if (--buf->nframesout == 0) {
-                       mempool_free(buf, d->bufpool);
-                       bio_endio(bio, bio->bi_size, -EIO);
+       t = d->targets;
+       te = t + NTARGETS;
+       for (; t < te && *t; t++) {
+               f = (*t)->frames;
+               e = f + (*t)->nframes;
+               for (; f < e; f->tag = FREETAG, f->buf = NULL, f++) {
+                       if (f->tag == FREETAG || f->buf == NULL)
+                               continue;
+                       buf = f->buf;
+                       bio = buf->bio;
+                       if (--buf->nframesout == 0
+                       && buf != d->inprocess) {
+                               mempool_free(buf, d->bufpool);
+                               bio_endio(bio, -EIO);
+                       }
                }
+               (*t)->maxout = (*t)->nframes;
+               (*t)->nout = 0;
+       }
+       buf = d->inprocess;
+       if (buf) {
+               bio = buf->bio;
+               mempool_free(buf, d->bufpool);
+               bio_endio(bio, -EIO);
        }
        d->inprocess = NULL;
+       d->htgt = NULL;
 
        while (!list_empty(&d->bufq)) {
                buf = container_of(d->bufq.next, struct buf, bufs);
                list_del(d->bufq.next);
                bio = buf->bio;
                mempool_free(buf, d->bufpool);
-               bio_endio(bio, bio->bi_size, -EIO);
+               bio_endio(bio, -EIO);
        }
 
-       if (d->nopen)
-               d->flags |= DEVFL_CLOSEWAIT;
        if (d->gd)
-               d->gd->capacity = 0;
+               set_capacity(d->gd, 0);
 
        d->flags &= ~DEVFL_UP;
 }
 
-struct aoedev *
-aoedev_set(ulong sysminor, unsigned char *addr, struct net_device *ifp, ulong bufcnt)
+static void
+aoedev_freedev(struct aoedev *d)
+{
+       struct aoetgt **t, **e;
+
+       if (d->gd) {
+               aoedisk_rm_sysfs(d);
+               del_gendisk(d->gd);
+               put_disk(d->gd);
+       }
+       t = d->targets;
+       e = t + NTARGETS;
+       for (; t < e && *t; t++)
+               freetgt(d, *t);
+       if (d->bufpool)
+               mempool_destroy(d->bufpool);
+       skbpoolfree(d);
+       kfree(d);
+}
+
+int
+aoedev_flush(const char __user *str, size_t cnt)
 {
-       struct aoedev *d;
        ulong flags;
+       struct aoedev *d, **dd;
+       struct aoedev *rmd = NULL;
+       char buf[16];
+       int all = 0;
+
+       if (cnt >= 3) {
+               if (cnt > sizeof buf)
+                       cnt = sizeof buf;
+               if (copy_from_user(buf, str, cnt))
+                       return -EFAULT;
+               all = !strncmp(buf, "all", 3);
+       }
 
+       flush_scheduled_work();
        spin_lock_irqsave(&devlist_lock, flags);
+       dd = &devlist;
+       while ((d = *dd)) {
+               spin_lock(&d->lock);
+               if ((!all && (d->flags & DEVFL_UP))
+               || (d->flags & (DEVFL_GDALLOC|DEVFL_NEWSIZE))
+               || d->nopen) {
+                       spin_unlock(&d->lock);
+                       dd = &d->next;
+                       continue;
+               }
+               *dd = d->next;
+               aoedev_downdev(d);
+               d->flags |= DEVFL_TKILL;
+               spin_unlock(&d->lock);
+               d->next = rmd;
+               rmd = d;
+       }
+       spin_unlock_irqrestore(&devlist_lock, flags);
+       while ((d = rmd)) {
+               rmd = d->next;
+               del_timer_sync(&d->timer);
+               aoedev_freedev(d);      /* must be able to sleep */
+       }
+       return 0;
+}
 
-       for (d=devlist; d; d=d->next)
-               if (d->sysminor == sysminor
-               || memcmp(d->addr, addr, sizeof d->addr) == 0)
-                       break;
-
-       if (d == NULL && (d = aoedev_newdev(bufcnt)) == NULL) {
-               spin_unlock_irqrestore(&devlist_lock, flags);
-               printk(KERN_INFO "aoe: aoedev_set: aoedev_newdev failure.\n");
-               return NULL;
+/* 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);
+}
 
-       spin_unlock_irqrestore(&devlist_lock, flags);
-       spin_lock_irqsave(&d->lock, flags);
+static void
+skbpoolfree(struct aoedev *d)
+{
+       struct sk_buff *skb, *tmp;
 
-       d->ifp = ifp;
+       skb_queue_walk_safe(&d->skbpool, skb, tmp)
+               skbfree(skb);
 
-       if (d->sysminor != sysminor
-       || (d->flags & DEVFL_UP) == 0) {
-               aoedev_downdev(d); /* flushes outstanding frames */
-               memcpy(d->addr, addr, sizeof d->addr);
-               d->sysminor = sysminor;
-               d->aoemajor = AOEMAJOR(sysminor);
-               d->aoeminor = AOEMINOR(sysminor);
-       }
+       __skb_queue_head_init(&d->skbpool);
+}
+
+/* find it or malloc it */
+struct aoedev *
+aoedev_by_sysminor_m(ulong sysminor)
+{
+       struct aoedev *d;
+       ulong flags;
+
+       spin_lock_irqsave(&devlist_lock, flags);
 
-       spin_unlock_irqrestore(&d->lock, flags);
+       for (d=devlist; d; d=d->next)
+               if (d->sysminor == sysminor)
+                       break;
+       if (d)
+               goto out;
+       d = kcalloc(1, sizeof *d, GFP_ATOMIC);
+       if (!d)
+               goto out;
+       INIT_WORK(&d->work, aoecmd_sleepwork);
+       spin_lock_init(&d->lock);
+       skb_queue_head_init(&d->sendq);
+       skb_queue_head_init(&d->skbpool);
+       init_timer(&d->timer);
+       d->timer.data = (ulong) d;
+       d->timer.function = dummy_timer;
+       d->timer.expires = jiffies + HZ;
+       add_timer(&d->timer);
+       d->bufpool = NULL;      /* defer to aoeblk_gdalloc */
+       d->tgt = d->targets;
+       INIT_LIST_HEAD(&d->bufq);
+       d->sysminor = sysminor;
+       d->aoemajor = AOEMAJOR(sysminor);
+       d->aoeminor = AOEMINOR(sysminor);
+       d->mintimer = MINTIMER;
+       d->next = devlist;
+       devlist = d;
+ out:
+       spin_unlock_irqrestore(&devlist_lock, flags);
        return d;
 }
 
 static void
-aoedev_freedev(struct aoedev *d)
+freetgt(struct aoedev *d, struct aoetgt *t)
 {
-       if (d->gd) {
-               aoedisk_rm_sysfs(d);
-               del_gendisk(d->gd);
-               put_disk(d->gd);
-       }
-       kfree(d->frames);
-       if (d->bufpool)
-               mempool_destroy(d->bufpool);
-       kfree(d);
+       struct frame *f, *e;
+
+       f = t->frames;
+       e = f + t->nframes;
+       for (; f < e; f++)
+               skbfree(f->skb);
+       kfree(t->frames);
+       kfree(t);
 }
 
 void
@@ -164,6 +262,7 @@ aoedev_exit(void)
 
                spin_lock_irqsave(&d->lock, flags);
                aoedev_downdev(d);
+               d->flags |= DEVFL_TKILL;
                spin_unlock_irqrestore(&d->lock, flags);
 
                del_timer_sync(&d->timer);
@@ -174,7 +273,5 @@ aoedev_exit(void)
 int __init
 aoedev_init(void)
 {
-       spin_lock_init(&devlist_lock);
        return 0;
 }
-