]> git.openfabrics.org - ~shefty/rdma-dev.git/blobdiff - drivers/block/aoe/aoedev.c
aoe: avoid races between device destruction and discovery
[~shefty/rdma-dev.git] / drivers / block / aoe / aoedev.c
index f0c0c7416aed280658c7ac07802bf1bac738eb8f..3776715eb2557d94dd8d3825bd98719f2c81277a 100644 (file)
@@ -15,7 +15,6 @@
 #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);
 
@@ -236,29 +235,6 @@ aoedev_downdev(struct aoedev *d)
                set_capacity(d->gd, 0);
 }
 
-static void
-aoedev_freedev(struct aoedev *d)
-{
-       struct aoetgt **t, **e;
-
-       cancel_work_sync(&d->work);
-       if (d->gd) {
-               aoedisk_rm_sysfs(d);
-               del_gendisk(d->gd);
-               put_disk(d->gd);
-               blk_cleanup_queue(d->blkq);
-       }
-       t = d->targets;
-       e = t + NTARGETS;
-       for (; t < e && *t; t++)
-               freetgt(d, *t);
-       if (d->bufpool)
-               mempool_destroy(d->bufpool);
-       skbpoolfree(d);
-       minor_free(d->sysminor);
-       kfree(d);
-}
-
 /* return whether the user asked for this particular
  * device to be flushed
  */
@@ -283,17 +259,62 @@ user_req(char *s, size_t slen, struct aoedev *d)
        return !strncmp(s, p, lim);
 }
 
-int
-aoedev_flush(const char __user *str, size_t cnt)
+static void
+freedev(struct aoedev *d)
+{
+       struct aoetgt **t, **e;
+       int freeing = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&d->lock, flags);
+       if (d->flags & DEVFL_TKILL
+       && !(d->flags & DEVFL_FREEING)) {
+               d->flags |= DEVFL_FREEING;
+               freeing = 1;
+       }
+       spin_unlock_irqrestore(&d->lock, flags);
+       if (!freeing)
+               return;
+
+       del_timer_sync(&d->timer);
+       if (d->gd) {
+               aoedisk_rm_sysfs(d);
+               del_gendisk(d->gd);
+               put_disk(d->gd);
+               blk_cleanup_queue(d->blkq);
+       }
+       t = d->targets;
+       e = t + NTARGETS;
+       for (; t < e && *t; t++)
+               freetgt(d, *t);
+       if (d->bufpool)
+               mempool_destroy(d->bufpool);
+       skbpoolfree(d);
+       minor_free(d->sysminor);
+
+       spin_lock_irqsave(&d->lock, flags);
+       d->flags |= DEVFL_FREED;
+       spin_unlock_irqrestore(&d->lock, flags);
+}
+
+enum flush_parms {
+       NOT_EXITING = 0,
+       EXITING = 1,
+};
+
+static int
+flush(const char __user *str, size_t cnt, int exiting)
 {
        ulong flags;
        struct aoedev *d, **dd;
-       struct aoedev *rmd = NULL;
        char buf[16];
        int all = 0;
        int specified = 0;      /* flush a specific device */
+       unsigned int skipflags;
+
+       skipflags = DEVFL_GDALLOC | DEVFL_NEWSIZE | DEVFL_TKILL;
 
-       if (cnt >= 3) {
+       if (!exiting && cnt >= 3) {
                if (cnt > sizeof buf)
                        cnt = sizeof buf;
                if (copy_from_user(buf, str, cnt))
@@ -303,39 +324,71 @@ aoedev_flush(const char __user *str, size_t cnt)
                        specified = 1;
        }
 
+       flush_scheduled_work();
+       /* pass one: without sleeping, do aoedev_downdev */
        spin_lock_irqsave(&devlist_lock, flags);
-       dd = &devlist;
-       while ((d = *dd)) {
+       for (d = devlist; d; d = d->next) {
                spin_lock(&d->lock);
-               if (specified) {
+               if (exiting) {
+                       /* unconditionally take each device down */
+               } else if (specified) {
                        if (!user_req(buf, cnt, d))
-                               goto skip;
+                               goto cont;
                } else if ((!all && (d->flags & DEVFL_UP))
-               || (d->flags & (DEVFL_GDALLOC|DEVFL_NEWSIZE))
+               || d->flags & skipflags
                || d->nopen
                || d->ref)
-                       goto skip;
+                       goto cont;
 
-               *dd = d->next;
                aoedev_downdev(d);
                d->flags |= DEVFL_TKILL;
+cont:
                spin_unlock(&d->lock);
-               d->next = rmd;
-               rmd = d;
-               continue;
-skip:
-               spin_unlock(&d->lock);
-               dd = &d->next;
        }
        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 */
+
+       /* pass two: call freedev, which might sleep,
+        * for aoedevs marked with DEVFL_TKILL
+        */
+restart:
+       spin_lock_irqsave(&devlist_lock, flags);
+       for (d = devlist; d; d = d->next) {
+               spin_lock(&d->lock);
+               if (d->flags & DEVFL_TKILL
+               && !(d->flags & DEVFL_FREEING)) {
+                       spin_unlock(&d->lock);
+                       spin_unlock_irqrestore(&devlist_lock, flags);
+                       freedev(d);
+                       goto restart;
+               }
+               spin_unlock(&d->lock);
        }
+
+       /* pass three: remove aoedevs marked with DEVFL_FREED */
+       for (dd = &devlist, d = *dd; d; d = *dd) {
+               struct aoedev *doomed = NULL;
+
+               spin_lock(&d->lock);
+               if (d->flags & DEVFL_FREED) {
+                       *dd = d->next;
+                       doomed = d;
+               } else {
+                       dd = &d->next;
+               }
+               spin_unlock(&d->lock);
+               kfree(doomed);
+       }
+       spin_unlock_irqrestore(&devlist_lock, flags);
+
        return 0;
 }
 
+int
+aoedev_flush(const char __user *str, size_t cnt)
+{
+       return flush(str, cnt, NOT_EXITING);
+}
+
 /* This has been confirmed to occur once with Tms=3*1000 due to the
  * driver changing link and not processing its transmit ring.  The
  * problem is hard enough to solve by returning an error that I'm
@@ -388,7 +441,14 @@ aoedev_by_aoeaddr(ulong maj, int min, int do_alloc)
 
        for (d=devlist; d; d=d->next)
                if (d->aoemajor == maj && d->aoeminor == min) {
+                       spin_lock(&d->lock);
+                       if (d->flags & DEVFL_TKILL) {
+                               spin_unlock(&d->lock);
+                               d = NULL;
+                               goto out;
+                       }
                        d->ref++;
+                       spin_unlock(&d->lock);
                        break;
                }
        if (d || !do_alloc || minor_get(&sysminor, maj, min) < 0)
@@ -448,21 +508,9 @@ freetgt(struct aoedev *d, struct aoetgt *t)
 void
 aoedev_exit(void)
 {
-       struct aoedev *d;
-       ulong flags;
-
+       flush_scheduled_work();
        aoe_flush_iocq();
-       while ((d = devlist)) {
-               devlist = d->next;
-
-               spin_lock_irqsave(&d->lock, flags);
-               aoedev_downdev(d);
-               d->flags |= DEVFL_TKILL;
-               spin_unlock_irqrestore(&d->lock, flags);
-
-               del_timer_sync(&d->timer);
-               aoedev_freedev(d);
-       }
+       flush(NULL, 0, EXITING);
 }
 
 int __init