]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/md/md.c
Merge branch 'post-2.6.15' of git://brick.kernel.dk/data/git/linux-2.6-block
[linux-2.6-omap-h63xx.git] / drivers / md / md.c
index 9ecf51ee596fb296f64a75f8372828e0d2701012..1b76fb29fb7065b1ac52aaa1db009515e16e46ca 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/devfs_fs_kernel.h>
 #include <linux/buffer_head.h> /* for invalidate_bdev */
 #include <linux/suspend.h>
+#include <linux/poll.h>
 
 #include <linux/init.h>
 
@@ -67,7 +68,7 @@
 static void autostart_arrays (int part);
 #endif
 
-static mdk_personality_t *pers[MAX_PERSONALITY];
+static LIST_HEAD(pers_list);
 static DEFINE_SPINLOCK(pers_lock);
 
 /*
@@ -80,10 +81,22 @@ static DEFINE_SPINLOCK(pers_lock);
  * idle IO detection.
  *
  * you can change it via /proc/sys/dev/raid/speed_limit_min and _max.
+ * or /sys/block/mdX/md/sync_speed_{min,max}
  */
 
 static int sysctl_speed_limit_min = 1000;
 static int sysctl_speed_limit_max = 200000;
+static inline int speed_min(mddev_t *mddev)
+{
+       return mddev->sync_speed_min ?
+               mddev->sync_speed_min : sysctl_speed_limit_min;
+}
+
+static inline int speed_max(mddev_t *mddev)
+{
+       return mddev->sync_speed_max ?
+               mddev->sync_speed_max : sysctl_speed_limit_max;
+}
 
 static struct ctl_table_header *raid_table_header;
 
@@ -131,6 +144,26 @@ static ctl_table raid_root_table[] = {
 
 static struct block_device_operations md_fops;
 
+static int start_readonly;
+
+/*
+ * We have a system wide 'event count' that is incremented
+ * on any 'interesting' event, and readers of /proc/mdstat
+ * can use 'poll' or 'select' to find out when the event
+ * count increases.
+ *
+ * Events are:
+ *  start array, stop array, error, add device, remove device,
+ *  start build, activate spare
+ */
+static DECLARE_WAIT_QUEUE_HEAD(md_event_waiters);
+static atomic_t md_event_count;
+static void md_new_event(mddev_t *mddev)
+{
+       atomic_inc(&md_event_count);
+       wake_up(&md_event_waiters);
+}
+
 /*
  * Enables to iterate over all existing md arrays
  * all_mddevs_lock protects this list.
@@ -181,7 +214,7 @@ static void mddev_put(mddev_t *mddev)
        if (!mddev->raid_disks && list_empty(&mddev->disks)) {
                list_del(&mddev->all_mddevs);
                blk_put_queue(mddev->queue);
-               kfree(mddev);
+               kobject_unregister(&mddev->kobj);
        }
        spin_unlock(&all_mddevs_lock);
 }
@@ -207,12 +240,10 @@ static mddev_t * mddev_find(dev_t unit)
        }
        spin_unlock(&all_mddevs_lock);
 
-       new = (mddev_t *) kmalloc(sizeof(*new), GFP_KERNEL);
+       new = kzalloc(sizeof(*new), GFP_KERNEL);
        if (!new)
                return NULL;
 
-       memset(new, 0, sizeof(*new));
-
        new->unit = unit;
        if (MAJOR(unit) == MD_MAJOR)
                new->md_minor = MINOR(unit);
@@ -260,7 +291,7 @@ static inline void mddev_unlock(mddev_t * mddev)
        md_wakeup_thread(mddev->thread);
 }
 
-mdk_rdev_t * find_rdev_nr(mddev_t *mddev, int nr)
+static mdk_rdev_t * find_rdev_nr(mddev_t *mddev, int nr)
 {
        mdk_rdev_t * rdev;
        struct list_head *tmp;
@@ -284,6 +315,18 @@ static mdk_rdev_t * find_rdev(mddev_t * mddev, dev_t dev)
        return NULL;
 }
 
+static struct mdk_personality *find_pers(int level, char *clevel)
+{
+       struct mdk_personality *pers;
+       list_for_each_entry(pers, &pers_list, list) {
+               if (level != LEVEL_NONE && pers->level == level)
+                       return pers;
+               if (strcmp(pers->name, clevel)==0)
+                       return pers;
+       }
+       return NULL;
+}
+
 static inline sector_t calc_dev_sboffset(struct block_device *bdev)
 {
        sector_t size = bdev->bd_inode->i_size >> BLOCK_SIZE_BITS;
@@ -318,7 +361,7 @@ static int alloc_disk_sb(mdk_rdev_t * rdev)
 static void free_disk_sb(mdk_rdev_t * rdev)
 {
        if (rdev->sb_page) {
-               page_cache_release(rdev->sb_page);
+               put_page(rdev->sb_page);
                rdev->sb_loaded = 0;
                rdev->sb_page = NULL;
                rdev->sb_offset = 0;
@@ -330,18 +373,46 @@ static void free_disk_sb(mdk_rdev_t * rdev)
 static int super_written(struct bio *bio, unsigned int bytes_done, int error)
 {
        mdk_rdev_t *rdev = bio->bi_private;
+       mddev_t *mddev = rdev->mddev;
        if (bio->bi_size)
                return 1;
 
        if (error || !test_bit(BIO_UPTODATE, &bio->bi_flags))
-               md_error(rdev->mddev, rdev);
+               md_error(mddev, rdev);
 
-       if (atomic_dec_and_test(&rdev->mddev->pending_writes))
-               wake_up(&rdev->mddev->sb_wait);
+       if (atomic_dec_and_test(&mddev->pending_writes))
+               wake_up(&mddev->sb_wait);
        bio_put(bio);
        return 0;
 }
 
+static int super_written_barrier(struct bio *bio, unsigned int bytes_done, int error)
+{
+       struct bio *bio2 = bio->bi_private;
+       mdk_rdev_t *rdev = bio2->bi_private;
+       mddev_t *mddev = rdev->mddev;
+       if (bio->bi_size)
+               return 1;
+
+       if (!test_bit(BIO_UPTODATE, &bio->bi_flags) &&
+           error == -EOPNOTSUPP) {
+               unsigned long flags;
+               /* barriers don't appear to be supported :-( */
+               set_bit(BarriersNotsupp, &rdev->flags);
+               mddev->barriers_work = 0;
+               spin_lock_irqsave(&mddev->write_lock, flags);
+               bio2->bi_next = mddev->biolist;
+               mddev->biolist = bio2;
+               spin_unlock_irqrestore(&mddev->write_lock, flags);
+               wake_up(&mddev->sb_wait);
+               bio_put(bio);
+               return 0;
+       }
+       bio_put(bio2);
+       bio->bi_private = rdev;
+       return super_written(bio, bytes_done, error);
+}
+
 void md_super_write(mddev_t *mddev, mdk_rdev_t *rdev,
                   sector_t sector, int size, struct page *page)
 {
@@ -350,16 +421,54 @@ void md_super_write(mddev_t *mddev, mdk_rdev_t *rdev,
         * and decrement it on completion, waking up sb_wait
         * if zero is reached.
         * If an error occurred, call md_error
+        *
+        * As we might need to resubmit the request if BIO_RW_BARRIER
+        * causes ENOTSUPP, we allocate a spare bio...
         */
        struct bio *bio = bio_alloc(GFP_NOIO, 1);
+       int rw = (1<<BIO_RW) | (1<<BIO_RW_SYNC);
 
        bio->bi_bdev = rdev->bdev;
        bio->bi_sector = sector;
        bio_add_page(bio, page, size, 0);
        bio->bi_private = rdev;
        bio->bi_end_io = super_written;
+       bio->bi_rw = rw;
+
        atomic_inc(&mddev->pending_writes);
-       submit_bio((1<<BIO_RW)|(1<<BIO_RW_SYNC), bio);
+       if (!test_bit(BarriersNotsupp, &rdev->flags)) {
+               struct bio *rbio;
+               rw |= (1<<BIO_RW_BARRIER);
+               rbio = bio_clone(bio, GFP_NOIO);
+               rbio->bi_private = bio;
+               rbio->bi_end_io = super_written_barrier;
+               submit_bio(rw, rbio);
+       } else
+               submit_bio(rw, bio);
+}
+
+void md_super_wait(mddev_t *mddev)
+{
+       /* wait for all superblock writes that were scheduled to complete.
+        * if any had to be retried (due to BARRIER problems), retry them
+        */
+       DEFINE_WAIT(wq);
+       for(;;) {
+               prepare_to_wait(&mddev->sb_wait, &wq, TASK_UNINTERRUPTIBLE);
+               if (atomic_read(&mddev->pending_writes)==0)
+                       break;
+               while (mddev->biolist) {
+                       struct bio *bio;
+                       spin_lock_irq(&mddev->write_lock);
+                       bio = mddev->biolist;
+                       mddev->biolist = bio->bi_next ;
+                       bio->bi_next = NULL;
+                       spin_unlock_irq(&mddev->write_lock);
+                       submit_bio(bio->bi_rw, bio);
+               }
+               schedule();
+       }
+       finish_wait(&mddev->sb_wait, &wq);
 }
 
 static int bi_complete(struct bio *bio, unsigned int bytes_done, int error)
@@ -393,6 +502,7 @@ int sync_page_io(struct block_device *bdev, sector_t sector, int size,
        bio_put(bio);
        return ret;
 }
+EXPORT_SYMBOL_GPL(sync_page_io);
 
 static int read_disk_sb(mdk_rdev_t * rdev, int size)
 {
@@ -597,6 +707,10 @@ static int super_90_load(mdk_rdev_t *rdev, mdk_rdev_t *refdev, int minor_version
        }
        rdev->size = calc_dev_size(rdev, sb->chunk_size);
 
+       if (rdev->size < sb->size && sb->level > 1)
+               /* "this cannot possibly happen" ... */
+               ret = -EINVAL;
+
  abort:
        return ret;
 }
@@ -610,7 +724,7 @@ static int super_90_validate(mddev_t *mddev, mdk_rdev_t *rdev)
        mdp_super_t *sb = (mdp_super_t *)page_address(rdev->sb_page);
 
        rdev->raid_disk = -1;
-       rdev->in_sync = 0;
+       rdev->flags = 0;
        if (mddev->raid_disks == 0) {
                mddev->major_version = 0;
                mddev->minor_version = sb->minor_version;
@@ -620,6 +734,7 @@ static int super_90_validate(mddev_t *mddev, mdk_rdev_t *rdev)
                mddev->ctime = sb->ctime;
                mddev->utime = sb->utime;
                mddev->level = sb->level;
+               mddev->clevel[0] = 0;
                mddev->layout = sb->layout;
                mddev->raid_disks = sb->raid_disks;
                mddev->size = sb->size;
@@ -646,9 +761,10 @@ static int super_90_validate(mddev_t *mddev, mdk_rdev_t *rdev)
 
                if (sb->state & (1<<MD_SB_BITMAP_PRESENT) &&
                    mddev->bitmap_file == NULL) {
-                       if (mddev->level != 1 && mddev->level != 5 && mddev->level != 6) {
+                       if (mddev->level != 1 && mddev->level != 5 && mddev->level != 6
+                           && mddev->level != 10) {
                                /* FIXME use a better test */
-                               printk(KERN_WARNING "md: bitmaps only support for raid1\n");
+                               printk(KERN_WARNING "md: bitmaps not supported for this level.\n");
                                return -EINVAL;
                        }
                        mddev->bitmap_offset = mddev->default_bitmap_offset;
@@ -671,21 +787,19 @@ static int super_90_validate(mddev_t *mddev, mdk_rdev_t *rdev)
                return 0;
 
        if (mddev->level != LEVEL_MULTIPATH) {
-               rdev->faulty = 0;
-               rdev->flags = 0;
                desc = sb->disks + rdev->desc_nr;
 
                if (desc->state & (1<<MD_DISK_FAULTY))
-                       rdev->faulty = 1;
+                       set_bit(Faulty, &rdev->flags);
                else if (desc->state & (1<<MD_DISK_SYNC) &&
                         desc->raid_disk < mddev->raid_disks) {
-                       rdev->in_sync = 1;
+                       set_bit(In_sync, &rdev->flags);
                        rdev->raid_disk = desc->raid_disk;
                }
                if (desc->state & (1<<MD_DISK_WRITEMOSTLY))
                        set_bit(WriteMostly, &rdev->flags);
        } else /* MULTIPATH are always insync */
-               rdev->in_sync = 1;
+               set_bit(In_sync, &rdev->flags);
        return 0;
 }
 
@@ -699,6 +813,7 @@ static void super_90_sync(mddev_t *mddev, mdk_rdev_t *rdev)
        mdk_rdev_t *rdev2;
        int next_spare = mddev->raid_disks;
 
+
        /* make rdev->sb match mddev data..
         *
         * 1/ zero out disks
@@ -758,23 +873,27 @@ static void super_90_sync(mddev_t *mddev, mdk_rdev_t *rdev)
        sb->disks[0].state = (1<<MD_DISK_REMOVED);
        ITERATE_RDEV(mddev,rdev2,tmp) {
                mdp_disk_t *d;
-               if (rdev2->raid_disk >= 0 && rdev2->in_sync && !rdev2->faulty)
-                       rdev2->desc_nr = rdev2->raid_disk;
+               int desc_nr;
+               if (rdev2->raid_disk >= 0 && test_bit(In_sync, &rdev2->flags)
+                   && !test_bit(Faulty, &rdev2->flags))
+                       desc_nr = rdev2->raid_disk;
                else
-                       rdev2->desc_nr = next_spare++;
+                       desc_nr = next_spare++;
+               rdev2->desc_nr = desc_nr;
                d = &sb->disks[rdev2->desc_nr];
                nr_disks++;
                d->number = rdev2->desc_nr;
                d->major = MAJOR(rdev2->bdev->bd_dev);
                d->minor = MINOR(rdev2->bdev->bd_dev);
-               if (rdev2->raid_disk >= 0 && rdev->in_sync && !rdev2->faulty)
+               if (rdev2->raid_disk >= 0 && test_bit(In_sync, &rdev2->flags)
+                   && !test_bit(Faulty, &rdev2->flags))
                        d->raid_disk = rdev2->raid_disk;
                else
                        d->raid_disk = rdev2->desc_nr; /* compatibility */
-               if (rdev2->faulty) {
+               if (test_bit(Faulty, &rdev2->flags)) {
                        d->state = (1<<MD_DISK_FAULTY);
                        failed++;
-               } else if (rdev2->in_sync) {
+               } else if (test_bit(In_sync, &rdev2->flags)) {
                        d->state = (1<<MD_DISK_ACTIVE);
                        d->state |= (1<<MD_DISK_SYNC);
                        active++;
@@ -787,7 +906,6 @@ static void super_90_sync(mddev_t *mddev, mdk_rdev_t *rdev)
                if (test_bit(WriteMostly, &rdev2->flags))
                        d->state |= (1<<MD_DISK_WRITEMOSTLY);
        }
-       
        /* now set the "removed" and "faulty" bits on any missing devices */
        for (i=0 ; i < mddev->raid_disks ; i++) {
                mdp_disk_t *d = &sb->disks[i];
@@ -898,6 +1016,7 @@ static int super_1_load(mdk_rdev_t *rdev, mdk_rdev_t *refdev, int minor_version)
        }
        rdev->preferred_minor = 0xffff;
        rdev->data_offset = le64_to_cpu(sb->data_offset);
+       atomic_set(&rdev->corrected_errors, le32_to_cpu(sb->cnt_corrected_read));
 
        rdev->sb_size = le32_to_cpu(sb->max_dev) * 2 + 256;
        bmask = queue_hardsect_size(rdev->bdev->bd_disk->queue)-1;
@@ -936,6 +1055,9 @@ static int super_1_load(mdk_rdev_t *rdev, mdk_rdev_t *refdev, int minor_version)
        rdev->size = le64_to_cpu(sb->data_size)/2;
        if (le32_to_cpu(sb->chunksize))
                rdev->size &= ~((sector_t)le32_to_cpu(sb->chunksize)/2 - 1);
+
+       if (le32_to_cpu(sb->size) > rdev->size*2)
+               return -EINVAL;
        return 0;
 }
 
@@ -944,7 +1066,7 @@ static int super_1_validate(mddev_t *mddev, mdk_rdev_t *rdev)
        struct mdp_superblock_1 *sb = (struct mdp_superblock_1*)page_address(rdev->sb_page);
 
        rdev->raid_disk = -1;
-       rdev->in_sync = 0;
+       rdev->flags = 0;
        if (mddev->raid_disks == 0) {
                mddev->major_version = 1;
                mddev->patch_version = 0;
@@ -953,12 +1075,12 @@ static int super_1_validate(mddev_t *mddev, mdk_rdev_t *rdev)
                mddev->ctime = le64_to_cpu(sb->ctime) & ((1ULL << 32)-1);
                mddev->utime = le64_to_cpu(sb->utime) & ((1ULL << 32)-1);
                mddev->level = le32_to_cpu(sb->level);
+               mddev->clevel[0] = 0;
                mddev->layout = le32_to_cpu(sb->layout);
                mddev->raid_disks = le32_to_cpu(sb->raid_disks);
                mddev->size = le64_to_cpu(sb->size)/2;
                mddev->events = le64_to_cpu(sb->events);
                mddev->bitmap_offset = 0;
-               mddev->default_bitmap_offset = 0;
                mddev->default_bitmap_offset = 1024;
                
                mddev->recovery_cp = le64_to_cpu(sb->resync_offset);
@@ -968,8 +1090,9 @@ static int super_1_validate(mddev_t *mddev, mdk_rdev_t *rdev)
 
                if ((le32_to_cpu(sb->feature_map) & MD_FEATURE_BITMAP_OFFSET) &&
                    mddev->bitmap_file == NULL ) {
-                       if (mddev->level != 1) {
-                               printk(KERN_WARNING "md: bitmaps only supported for raid1\n");
+                       if (mddev->level != 1 && mddev->level != 5 && mddev->level != 6
+                           && mddev->level != 10) {
+                               printk(KERN_WARNING "md: bitmaps not supported for this level.\n");
                                return -EINVAL;
                        }
                        mddev->bitmap_offset = (__s32)le32_to_cpu(sb->bitmap_offset);
@@ -996,22 +1119,19 @@ static int super_1_validate(mddev_t *mddev, mdk_rdev_t *rdev)
                role = le16_to_cpu(sb->dev_roles[rdev->desc_nr]);
                switch(role) {
                case 0xffff: /* spare */
-                       rdev->faulty = 0;
                        break;
                case 0xfffe: /* faulty */
-                       rdev->faulty = 1;
+                       set_bit(Faulty, &rdev->flags);
                        break;
                default:
-                       rdev->in_sync = 1;
-                       rdev->faulty = 0;
+                       set_bit(In_sync, &rdev->flags);
                        rdev->raid_disk = role;
                        break;
                }
-               rdev->flags = 0;
                if (sb->devflags & WriteMostly1)
                        set_bit(WriteMostly, &rdev->flags);
        } else /* MULTIPATH are always insync */
-               rdev->in_sync = 1;
+               set_bit(In_sync, &rdev->flags);
 
        return 0;
 }
@@ -1039,6 +1159,8 @@ static void super_1_sync(mddev_t *mddev, mdk_rdev_t *rdev)
        else
                sb->resync_offset = cpu_to_le64(0);
 
+       sb->cnt_corrected_read = atomic_read(&rdev->corrected_errors);
+
        if (mddev->bitmap && mddev->bitmap_file == NULL) {
                sb->bitmap_offset = cpu_to_le32((__u32)mddev->bitmap_offset);
                sb->feature_map = cpu_to_le32(MD_FEATURE_BITMAP_OFFSET);
@@ -1055,9 +1177,9 @@ static void super_1_sync(mddev_t *mddev, mdk_rdev_t *rdev)
        
        ITERATE_RDEV(mddev,rdev2,tmp) {
                i = rdev2->desc_nr;
-               if (rdev2->faulty)
+               if (test_bit(Faulty, &rdev2->flags))
                        sb->dev_roles[i] = cpu_to_le16(0xfffe);
-               else if (rdev2->in_sync)
+               else if (test_bit(In_sync, &rdev2->flags))
                        sb->dev_roles[i] = cpu_to_le16(rdev2->raid_disk);
                else
                        sb->dev_roles[i] = cpu_to_le16(0xffff);
@@ -1115,11 +1237,20 @@ static int bind_rdev_to_array(mdk_rdev_t * rdev, mddev_t * mddev)
 {
        mdk_rdev_t *same_pdev;
        char b[BDEVNAME_SIZE], b2[BDEVNAME_SIZE];
+       struct kobject *ko;
 
        if (rdev->mddev) {
                MD_BUG();
                return -EINVAL;
        }
+       /* make sure rdev->size exceeds mddev->size */
+       if (rdev->size && (mddev->size == 0 || rdev->size < mddev->size)) {
+               if (mddev->pers)
+                       /* Cannot change size, so fail */
+                       return -ENOSPC;
+               else
+                       mddev->size = rdev->size;
+       }
        same_pdev = match_dev_unit(mddev, rdev);
        if (same_pdev)
                printk(KERN_WARNING
@@ -1143,10 +1274,22 @@ static int bind_rdev_to_array(mdk_rdev_t * rdev, mddev_t * mddev)
                if (find_rdev_nr(mddev, rdev->desc_nr))
                        return -EBUSY;
        }
+       bdevname(rdev->bdev,b);
+       if (kobject_set_name(&rdev->kobj, "dev-%s", b) < 0)
+               return -ENOMEM;
                        
        list_add(&rdev->same_set, &mddev->disks);
        rdev->mddev = mddev;
-       printk(KERN_INFO "md: bind<%s>\n", bdevname(rdev->bdev,b));
+       printk(KERN_INFO "md: bind<%s>\n", b);
+
+       rdev->kobj.parent = &mddev->kobj;
+       kobject_add(&rdev->kobj);
+
+       if (rdev->bdev->bd_part)
+               ko = &rdev->bdev->bd_part->kobj;
+       else
+               ko = &rdev->bdev->bd_disk->kobj;
+       sysfs_create_link(&rdev->kobj, ko, "block");
        return 0;
 }
 
@@ -1160,6 +1303,8 @@ static void unbind_rdev_from_array(mdk_rdev_t * rdev)
        list_del_init(&rdev->same_set);
        printk(KERN_INFO "md: unbind<%s>\n", bdevname(rdev->bdev,b));
        rdev->mddev = NULL;
+       sysfs_remove_link(&rdev->kobj, "block");
+       kobject_del(&rdev->kobj);
 }
 
 /*
@@ -1215,7 +1360,7 @@ static void export_rdev(mdk_rdev_t * rdev)
        md_autodetect_dev(rdev->bdev->bd_dev);
 #endif
        unlock_rdev(rdev);
-       kfree(rdev);
+       kobject_put(&rdev->kobj);
 }
 
 static void kick_rdev_from_array(mdk_rdev_t * rdev)
@@ -1287,7 +1432,8 @@ static void print_rdev(mdk_rdev_t *rdev)
        char b[BDEVNAME_SIZE];
        printk(KERN_INFO "md: rdev %s, SZ:%08llu F:%d S:%d DN:%u\n",
                bdevname(rdev->bdev,b), (unsigned long long)rdev->size,
-               rdev->faulty, rdev->in_sync, rdev->desc_nr);
+               test_bit(Faulty, &rdev->flags), test_bit(In_sync, &rdev->flags),
+               rdev->desc_nr);
        if (rdev->sb_loaded) {
                printk(KERN_INFO "md: rdev superblock:\n");
                print_sb((mdp_super_t*)page_address(rdev->sb_page));
@@ -1344,7 +1490,7 @@ static void md_update_sb(mddev_t * mddev)
        int sync_req;
 
 repeat:
-       spin_lock(&mddev->write_lock);
+       spin_lock_irq(&mddev->write_lock);
        sync_req = mddev->in_sync;
        mddev->utime = get_seconds();
        mddev->events ++;
@@ -1367,11 +1513,11 @@ repeat:
         */
        if (!mddev->persistent) {
                mddev->sb_dirty = 0;
-               spin_unlock(&mddev->write_lock);
+               spin_unlock_irq(&mddev->write_lock);
                wake_up(&mddev->sb_wait);
                return;
        }
-       spin_unlock(&mddev->write_lock);
+       spin_unlock_irq(&mddev->write_lock);
 
        dprintk(KERN_INFO 
                "md: updating %s RAID superblock on device (in sync %d)\n",
@@ -1381,11 +1527,11 @@ repeat:
        ITERATE_RDEV(mddev,rdev,tmp) {
                char b[BDEVNAME_SIZE];
                dprintk(KERN_INFO "md: ");
-               if (rdev->faulty)
+               if (test_bit(Faulty, &rdev->flags))
                        dprintk("(skipping faulty ");
 
                dprintk("%s ", bdevname(rdev->bdev,b));
-               if (!rdev->faulty) {
+               if (!test_bit(Faulty, &rdev->flags)) {
                        md_super_write(mddev,rdev,
                                       rdev->sb_offset<<1, rdev->sb_size,
                                       rdev->sb_page);
@@ -1399,21 +1545,230 @@ repeat:
                        /* only need to write one superblock... */
                        break;
        }
-       wait_event(mddev->sb_wait, atomic_read(&mddev->pending_writes)==0);
+       md_super_wait(mddev);
        /* if there was a failure, sb_dirty was set to 1, and we re-write super */
 
-       spin_lock(&mddev->write_lock);
+       spin_lock_irq(&mddev->write_lock);
        if (mddev->in_sync != sync_req|| mddev->sb_dirty == 1) {
                /* have to write it out again */
-               spin_unlock(&mddev->write_lock);
+               spin_unlock_irq(&mddev->write_lock);
                goto repeat;
        }
        mddev->sb_dirty = 0;
-       spin_unlock(&mddev->write_lock);
+       spin_unlock_irq(&mddev->write_lock);
        wake_up(&mddev->sb_wait);
 
 }
 
+/* words written to sysfs files may, or my not, be \n terminated.
+ * We want to accept with case. For this we use cmd_match.
+ */
+static int cmd_match(const char *cmd, const char *str)
+{
+       /* See if cmd, written into a sysfs file, matches
+        * str.  They must either be the same, or cmd can
+        * have a trailing newline
+        */
+       while (*cmd && *str && *cmd == *str) {
+               cmd++;
+               str++;
+       }
+       if (*cmd == '\n')
+               cmd++;
+       if (*str || *cmd)
+               return 0;
+       return 1;
+}
+
+struct rdev_sysfs_entry {
+       struct attribute attr;
+       ssize_t (*show)(mdk_rdev_t *, char *);
+       ssize_t (*store)(mdk_rdev_t *, const char *, size_t);
+};
+
+static ssize_t
+state_show(mdk_rdev_t *rdev, char *page)
+{
+       char *sep = "";
+       int len=0;
+
+       if (test_bit(Faulty, &rdev->flags)) {
+               len+= sprintf(page+len, "%sfaulty",sep);
+               sep = ",";
+       }
+       if (test_bit(In_sync, &rdev->flags)) {
+               len += sprintf(page+len, "%sin_sync",sep);
+               sep = ",";
+       }
+       if (!test_bit(Faulty, &rdev->flags) &&
+           !test_bit(In_sync, &rdev->flags)) {
+               len += sprintf(page+len, "%sspare", sep);
+               sep = ",";
+       }
+       return len+sprintf(page+len, "\n");
+}
+
+static struct rdev_sysfs_entry
+rdev_state = __ATTR_RO(state);
+
+static ssize_t
+super_show(mdk_rdev_t *rdev, char *page)
+{
+       if (rdev->sb_loaded && rdev->sb_size) {
+               memcpy(page, page_address(rdev->sb_page), rdev->sb_size);
+               return rdev->sb_size;
+       } else
+               return 0;
+}
+static struct rdev_sysfs_entry rdev_super = __ATTR_RO(super);
+
+static ssize_t
+errors_show(mdk_rdev_t *rdev, char *page)
+{
+       return sprintf(page, "%d\n", atomic_read(&rdev->corrected_errors));
+}
+
+static ssize_t
+errors_store(mdk_rdev_t *rdev, const char *buf, size_t len)
+{
+       char *e;
+       unsigned long n = simple_strtoul(buf, &e, 10);
+       if (*buf && (*e == 0 || *e == '\n')) {
+               atomic_set(&rdev->corrected_errors, n);
+               return len;
+       }
+       return -EINVAL;
+}
+static struct rdev_sysfs_entry rdev_errors =
+__ATTR(errors, 0644, errors_show, errors_store);
+
+static ssize_t
+slot_show(mdk_rdev_t *rdev, char *page)
+{
+       if (rdev->raid_disk < 0)
+               return sprintf(page, "none\n");
+       else
+               return sprintf(page, "%d\n", rdev->raid_disk);
+}
+
+static ssize_t
+slot_store(mdk_rdev_t *rdev, const char *buf, size_t len)
+{
+       char *e;
+       int slot = simple_strtoul(buf, &e, 10);
+       if (strncmp(buf, "none", 4)==0)
+               slot = -1;
+       else if (e==buf || (*e && *e!= '\n'))
+               return -EINVAL;
+       if (rdev->mddev->pers)
+               /* Cannot set slot in active array (yet) */
+               return -EBUSY;
+       if (slot >= rdev->mddev->raid_disks)
+               return -ENOSPC;
+       rdev->raid_disk = slot;
+       /* assume it is working */
+       rdev->flags = 0;
+       set_bit(In_sync, &rdev->flags);
+       return len;
+}
+
+
+static struct rdev_sysfs_entry rdev_slot =
+__ATTR(slot, 0644, slot_show, slot_store);
+
+static ssize_t
+offset_show(mdk_rdev_t *rdev, char *page)
+{
+       return sprintf(page, "%llu\n", (unsigned long long)rdev->data_offset);
+}
+
+static ssize_t
+offset_store(mdk_rdev_t *rdev, const char *buf, size_t len)
+{
+       char *e;
+       unsigned long long offset = simple_strtoull(buf, &e, 10);
+       if (e==buf || (*e && *e != '\n'))
+               return -EINVAL;
+       if (rdev->mddev->pers)
+               return -EBUSY;
+       rdev->data_offset = offset;
+       return len;
+}
+
+static struct rdev_sysfs_entry rdev_offset =
+__ATTR(offset, 0644, offset_show, offset_store);
+
+static ssize_t
+rdev_size_show(mdk_rdev_t *rdev, char *page)
+{
+       return sprintf(page, "%llu\n", (unsigned long long)rdev->size);
+}
+
+static ssize_t
+rdev_size_store(mdk_rdev_t *rdev, const char *buf, size_t len)
+{
+       char *e;
+       unsigned long long size = simple_strtoull(buf, &e, 10);
+       if (e==buf || (*e && *e != '\n'))
+               return -EINVAL;
+       if (rdev->mddev->pers)
+               return -EBUSY;
+       rdev->size = size;
+       if (size < rdev->mddev->size || rdev->mddev->size == 0)
+               rdev->mddev->size = size;
+       return len;
+}
+
+static struct rdev_sysfs_entry rdev_size =
+__ATTR(size, 0644, rdev_size_show, rdev_size_store);
+
+static struct attribute *rdev_default_attrs[] = {
+       &rdev_state.attr,
+       &rdev_super.attr,
+       &rdev_errors.attr,
+       &rdev_slot.attr,
+       &rdev_offset.attr,
+       &rdev_size.attr,
+       NULL,
+};
+static ssize_t
+rdev_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
+{
+       struct rdev_sysfs_entry *entry = container_of(attr, struct rdev_sysfs_entry, attr);
+       mdk_rdev_t *rdev = container_of(kobj, mdk_rdev_t, kobj);
+
+       if (!entry->show)
+               return -EIO;
+       return entry->show(rdev, page);
+}
+
+static ssize_t
+rdev_attr_store(struct kobject *kobj, struct attribute *attr,
+             const char *page, size_t length)
+{
+       struct rdev_sysfs_entry *entry = container_of(attr, struct rdev_sysfs_entry, attr);
+       mdk_rdev_t *rdev = container_of(kobj, mdk_rdev_t, kobj);
+
+       if (!entry->store)
+               return -EIO;
+       return entry->store(rdev, page, length);
+}
+
+static void rdev_free(struct kobject *ko)
+{
+       mdk_rdev_t *rdev = container_of(ko, mdk_rdev_t, kobj);
+       kfree(rdev);
+}
+static struct sysfs_ops rdev_sysfs_ops = {
+       .show           = rdev_attr_show,
+       .store          = rdev_attr_store,
+};
+static struct kobj_type rdev_ktype = {
+       .release        = rdev_free,
+       .sysfs_ops      = &rdev_sysfs_ops,
+       .default_attrs  = rdev_default_attrs,
+};
+
 /*
  * Import a device. If 'super_format' >= 0, then sanity check the superblock
  *
@@ -1431,12 +1786,11 @@ static mdk_rdev_t *md_import_device(dev_t newdev, int super_format, int super_mi
        mdk_rdev_t *rdev;
        sector_t size;
 
-       rdev = (mdk_rdev_t *) kmalloc(sizeof(*rdev), GFP_KERNEL);
+       rdev = kzalloc(sizeof(*rdev), GFP_KERNEL);
        if (!rdev) {
                printk(KERN_ERR "md: could not alloc mem for new device!\n");
                return ERR_PTR(-ENOMEM);
        }
-       memset(rdev, 0, sizeof(*rdev));
 
        if ((err = alloc_disk_sb(rdev)))
                goto abort_free;
@@ -1445,11 +1799,16 @@ static mdk_rdev_t *md_import_device(dev_t newdev, int super_format, int super_mi
        if (err)
                goto abort_free;
 
+       rdev->kobj.parent = NULL;
+       rdev->kobj.ktype = &rdev_ktype;
+       kobject_init(&rdev->kobj);
+
        rdev->desc_nr = -1;
-       rdev->faulty = 0;
-       rdev->in_sync = 0;
+       rdev->flags = 0;
        rdev->data_offset = 0;
        atomic_set(&rdev->nr_pending, 0);
+       atomic_set(&rdev->read_errors, 0);
+       atomic_set(&rdev->corrected_errors, 0);
 
        size = rdev->bdev->bd_inode->i_size >> BLOCK_SIZE_BITS;
        if (!size) {
@@ -1537,7 +1896,7 @@ static void analyze_sbs(mddev_t * mddev)
                if (mddev->level == LEVEL_MULTIPATH) {
                        rdev->desc_nr = i++;
                        rdev->raid_disk = rdev->desc_nr;
-                       rdev->in_sync = 1;
+                       set_bit(In_sync, &rdev->flags);
                }
        }
 
@@ -1551,6 +1910,461 @@ static void analyze_sbs(mddev_t * mddev)
 
 }
 
+static ssize_t
+level_show(mddev_t *mddev, char *page)
+{
+       struct mdk_personality *p = mddev->pers;
+       if (p)
+               return sprintf(page, "%s\n", p->name);
+       else if (mddev->clevel[0])
+               return sprintf(page, "%s\n", mddev->clevel);
+       else if (mddev->level != LEVEL_NONE)
+               return sprintf(page, "%d\n", mddev->level);
+       else
+               return 0;
+}
+
+static ssize_t
+level_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       int rv = len;
+       if (mddev->pers)
+               return -EBUSY;
+       if (len == 0)
+               return 0;
+       if (len >= sizeof(mddev->clevel))
+               return -ENOSPC;
+       strncpy(mddev->clevel, buf, len);
+       if (mddev->clevel[len-1] == '\n')
+               len--;
+       mddev->clevel[len] = 0;
+       mddev->level = LEVEL_NONE;
+       return rv;
+}
+
+static struct md_sysfs_entry md_level =
+__ATTR(level, 0644, level_show, level_store);
+
+static ssize_t
+raid_disks_show(mddev_t *mddev, char *page)
+{
+       if (mddev->raid_disks == 0)
+               return 0;
+       return sprintf(page, "%d\n", mddev->raid_disks);
+}
+
+static int update_raid_disks(mddev_t *mddev, int raid_disks);
+
+static ssize_t
+raid_disks_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       /* can only set raid_disks if array is not yet active */
+       char *e;
+       int rv = 0;
+       unsigned long n = simple_strtoul(buf, &e, 10);
+
+       if (!*buf || (*e && *e != '\n'))
+               return -EINVAL;
+
+       if (mddev->pers)
+               rv = update_raid_disks(mddev, n);
+       else
+               mddev->raid_disks = n;
+       return rv ? rv : len;
+}
+static struct md_sysfs_entry md_raid_disks =
+__ATTR(raid_disks, 0644, raid_disks_show, raid_disks_store);
+
+static ssize_t
+chunk_size_show(mddev_t *mddev, char *page)
+{
+       return sprintf(page, "%d\n", mddev->chunk_size);
+}
+
+static ssize_t
+chunk_size_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       /* can only set chunk_size if array is not yet active */
+       char *e;
+       unsigned long n = simple_strtoul(buf, &e, 10);
+
+       if (mddev->pers)
+               return -EBUSY;
+       if (!*buf || (*e && *e != '\n'))
+               return -EINVAL;
+
+       mddev->chunk_size = n;
+       return len;
+}
+static struct md_sysfs_entry md_chunk_size =
+__ATTR(chunk_size, 0644, chunk_size_show, chunk_size_store);
+
+static ssize_t
+null_show(mddev_t *mddev, char *page)
+{
+       return -EINVAL;
+}
+
+static ssize_t
+new_dev_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       /* buf must be %d:%d\n? giving major and minor numbers */
+       /* The new device is added to the array.
+        * If the array has a persistent superblock, we read the
+        * superblock to initialise info and check validity.
+        * Otherwise, only checking done is that in bind_rdev_to_array,
+        * which mainly checks size.
+        */
+       char *e;
+       int major = simple_strtoul(buf, &e, 10);
+       int minor;
+       dev_t dev;
+       mdk_rdev_t *rdev;
+       int err;
+
+       if (!*buf || *e != ':' || !e[1] || e[1] == '\n')
+               return -EINVAL;
+       minor = simple_strtoul(e+1, &e, 10);
+       if (*e && *e != '\n')
+               return -EINVAL;
+       dev = MKDEV(major, minor);
+       if (major != MAJOR(dev) ||
+           minor != MINOR(dev))
+               return -EOVERFLOW;
+
+
+       if (mddev->persistent) {
+               rdev = md_import_device(dev, mddev->major_version,
+                                       mddev->minor_version);
+               if (!IS_ERR(rdev) && !list_empty(&mddev->disks)) {
+                       mdk_rdev_t *rdev0 = list_entry(mddev->disks.next,
+                                                      mdk_rdev_t, same_set);
+                       err = super_types[mddev->major_version]
+                               .load_super(rdev, rdev0, mddev->minor_version);
+                       if (err < 0)
+                               goto out;
+               }
+       } else
+               rdev = md_import_device(dev, -1, -1);
+
+       if (IS_ERR(rdev))
+               return PTR_ERR(rdev);
+       err = bind_rdev_to_array(rdev, mddev);
+ out:
+       if (err)
+               export_rdev(rdev);
+       return err ? err : len;
+}
+
+static struct md_sysfs_entry md_new_device =
+__ATTR(new_dev, 0200, null_show, new_dev_store);
+
+static ssize_t
+size_show(mddev_t *mddev, char *page)
+{
+       return sprintf(page, "%llu\n", (unsigned long long)mddev->size);
+}
+
+static int update_size(mddev_t *mddev, unsigned long size);
+
+static ssize_t
+size_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       /* If array is inactive, we can reduce the component size, but
+        * not increase it (except from 0).
+        * If array is active, we can try an on-line resize
+        */
+       char *e;
+       int err = 0;
+       unsigned long long size = simple_strtoull(buf, &e, 10);
+       if (!*buf || *buf == '\n' ||
+           (*e && *e != '\n'))
+               return -EINVAL;
+
+       if (mddev->pers) {
+               err = update_size(mddev, size);
+               md_update_sb(mddev);
+       } else {
+               if (mddev->size == 0 ||
+                   mddev->size > size)
+                       mddev->size = size;
+               else
+                       err = -ENOSPC;
+       }
+       return err ? err : len;
+}
+
+static struct md_sysfs_entry md_size =
+__ATTR(component_size, 0644, size_show, size_store);
+
+
+/* Metdata version.
+ * This is either 'none' for arrays with externally managed metadata,
+ * or N.M for internally known formats
+ */
+static ssize_t
+metadata_show(mddev_t *mddev, char *page)
+{
+       if (mddev->persistent)
+               return sprintf(page, "%d.%d\n",
+                              mddev->major_version, mddev->minor_version);
+       else
+               return sprintf(page, "none\n");
+}
+
+static ssize_t
+metadata_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       int major, minor;
+       char *e;
+       if (!list_empty(&mddev->disks))
+               return -EBUSY;
+
+       if (cmd_match(buf, "none")) {
+               mddev->persistent = 0;
+               mddev->major_version = 0;
+               mddev->minor_version = 90;
+               return len;
+       }
+       major = simple_strtoul(buf, &e, 10);
+       if (e==buf || *e != '.')
+               return -EINVAL;
+       buf = e+1;
+       minor = simple_strtoul(buf, &e, 10);
+       if (e==buf || *e != '\n')
+               return -EINVAL;
+       if (major >= sizeof(super_types)/sizeof(super_types[0]) ||
+           super_types[major].name == NULL)
+               return -ENOENT;
+       mddev->major_version = major;
+       mddev->minor_version = minor;
+       mddev->persistent = 1;
+       return len;
+}
+
+static struct md_sysfs_entry md_metadata =
+__ATTR(metadata_version, 0644, metadata_show, metadata_store);
+
+static ssize_t
+action_show(mddev_t *mddev, char *page)
+{
+       char *type = "idle";
+       if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) ||
+           test_bit(MD_RECOVERY_NEEDED, &mddev->recovery)) {
+               if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) {
+                       if (!test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery))
+                               type = "resync";
+                       else if (test_bit(MD_RECOVERY_CHECK, &mddev->recovery))
+                               type = "check";
+                       else
+                               type = "repair";
+               } else
+                       type = "recover";
+       }
+       return sprintf(page, "%s\n", type);
+}
+
+static ssize_t
+action_store(mddev_t *mddev, const char *page, size_t len)
+{
+       if (!mddev->pers || !mddev->pers->sync_request)
+               return -EINVAL;
+
+       if (cmd_match(page, "idle")) {
+               if (mddev->sync_thread) {
+                       set_bit(MD_RECOVERY_INTR, &mddev->recovery);
+                       md_unregister_thread(mddev->sync_thread);
+                       mddev->sync_thread = NULL;
+                       mddev->recovery = 0;
+               }
+       } else if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) ||
+                  test_bit(MD_RECOVERY_NEEDED, &mddev->recovery))
+               return -EBUSY;
+       else if (cmd_match(page, "resync") || cmd_match(page, "recover"))
+               set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+       else {
+               if (cmd_match(page, "check"))
+                       set_bit(MD_RECOVERY_CHECK, &mddev->recovery);
+               else if (cmd_match(page, "repair"))
+                       return -EINVAL;
+               set_bit(MD_RECOVERY_REQUESTED, &mddev->recovery);
+               set_bit(MD_RECOVERY_SYNC, &mddev->recovery);
+       }
+       set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+       md_wakeup_thread(mddev->thread);
+       return len;
+}
+
+static ssize_t
+mismatch_cnt_show(mddev_t *mddev, char *page)
+{
+       return sprintf(page, "%llu\n",
+                      (unsigned long long) mddev->resync_mismatches);
+}
+
+static struct md_sysfs_entry
+md_scan_mode = __ATTR(sync_action, S_IRUGO|S_IWUSR, action_show, action_store);
+
+
+static struct md_sysfs_entry
+md_mismatches = __ATTR_RO(mismatch_cnt);
+
+static ssize_t
+sync_min_show(mddev_t *mddev, char *page)
+{
+       return sprintf(page, "%d (%s)\n", speed_min(mddev),
+                      mddev->sync_speed_min ? "local": "system");
+}
+
+static ssize_t
+sync_min_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       int min;
+       char *e;
+       if (strncmp(buf, "system", 6)==0) {
+               mddev->sync_speed_min = 0;
+               return len;
+       }
+       min = simple_strtoul(buf, &e, 10);
+       if (buf == e || (*e && *e != '\n') || min <= 0)
+               return -EINVAL;
+       mddev->sync_speed_min = min;
+       return len;
+}
+
+static struct md_sysfs_entry md_sync_min =
+__ATTR(sync_speed_min, S_IRUGO|S_IWUSR, sync_min_show, sync_min_store);
+
+static ssize_t
+sync_max_show(mddev_t *mddev, char *page)
+{
+       return sprintf(page, "%d (%s)\n", speed_max(mddev),
+                      mddev->sync_speed_max ? "local": "system");
+}
+
+static ssize_t
+sync_max_store(mddev_t *mddev, const char *buf, size_t len)
+{
+       int max;
+       char *e;
+       if (strncmp(buf, "system", 6)==0) {
+               mddev->sync_speed_max = 0;
+               return len;
+       }
+       max = simple_strtoul(buf, &e, 10);
+       if (buf == e || (*e && *e != '\n') || max <= 0)
+               return -EINVAL;
+       mddev->sync_speed_max = max;
+       return len;
+}
+
+static struct md_sysfs_entry md_sync_max =
+__ATTR(sync_speed_max, S_IRUGO|S_IWUSR, sync_max_show, sync_max_store);
+
+
+static ssize_t
+sync_speed_show(mddev_t *mddev, char *page)
+{
+       unsigned long resync, dt, db;
+       resync = (mddev->curr_resync - atomic_read(&mddev->recovery_active));
+       dt = ((jiffies - mddev->resync_mark) / HZ);
+       if (!dt) dt++;
+       db = resync - (mddev->resync_mark_cnt);
+       return sprintf(page, "%ld\n", db/dt/2); /* K/sec */
+}
+
+static struct md_sysfs_entry
+md_sync_speed = __ATTR_RO(sync_speed);
+
+static ssize_t
+sync_completed_show(mddev_t *mddev, char *page)
+{
+       unsigned long max_blocks, resync;
+
+       if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery))
+               max_blocks = mddev->resync_max_sectors;
+       else
+               max_blocks = mddev->size << 1;
+
+       resync = (mddev->curr_resync - atomic_read(&mddev->recovery_active));
+       return sprintf(page, "%lu / %lu\n", resync, max_blocks);
+}
+
+static struct md_sysfs_entry
+md_sync_completed = __ATTR_RO(sync_completed);
+
+static struct attribute *md_default_attrs[] = {
+       &md_level.attr,
+       &md_raid_disks.attr,
+       &md_chunk_size.attr,
+       &md_size.attr,
+       &md_metadata.attr,
+       &md_new_device.attr,
+       NULL,
+};
+
+static struct attribute *md_redundancy_attrs[] = {
+       &md_scan_mode.attr,
+       &md_mismatches.attr,
+       &md_sync_min.attr,
+       &md_sync_max.attr,
+       &md_sync_speed.attr,
+       &md_sync_completed.attr,
+       NULL,
+};
+static struct attribute_group md_redundancy_group = {
+       .name = NULL,
+       .attrs = md_redundancy_attrs,
+};
+
+
+static ssize_t
+md_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
+{
+       struct md_sysfs_entry *entry = container_of(attr, struct md_sysfs_entry, attr);
+       mddev_t *mddev = container_of(kobj, struct mddev_s, kobj);
+       ssize_t rv;
+
+       if (!entry->show)
+               return -EIO;
+       mddev_lock(mddev);
+       rv = entry->show(mddev, page);
+       mddev_unlock(mddev);
+       return rv;
+}
+
+static ssize_t
+md_attr_store(struct kobject *kobj, struct attribute *attr,
+             const char *page, size_t length)
+{
+       struct md_sysfs_entry *entry = container_of(attr, struct md_sysfs_entry, attr);
+       mddev_t *mddev = container_of(kobj, struct mddev_s, kobj);
+       ssize_t rv;
+
+       if (!entry->store)
+               return -EIO;
+       mddev_lock(mddev);
+       rv = entry->store(mddev, page, length);
+       mddev_unlock(mddev);
+       return rv;
+}
+
+static void md_free(struct kobject *ko)
+{
+       mddev_t *mddev = container_of(ko, mddev_t, kobj);
+       kfree(mddev);
+}
+
+static struct sysfs_ops md_sysfs_ops = {
+       .show   = md_attr_show,
+       .store  = md_attr_store,
+};
+static struct kobj_type md_ktype = {
+       .release        = md_free,
+       .sysfs_ops      = &md_sysfs_ops,
+       .default_attrs  = md_default_attrs,
+};
+
 int mdp_major = 0;
 
 static struct kobject *md_probe(dev_t dev, int *part, void *data)
@@ -1592,6 +2406,11 @@ static struct kobject *md_probe(dev_t dev, int *part, void *data)
        add_disk(disk);
        mddev->gendisk = disk;
        up(&disks_sem);
+       mddev->kobj.parent = &disk->kobj;
+       mddev->kobj.k_name = NULL;
+       snprintf(mddev->kobj.name, KOBJ_NAME_LEN, "%s", "md");
+       mddev->kobj.ktype = &md_ktype;
+       kobject_register(&mddev->kobj);
        return NULL;
 }
 
@@ -1605,14 +2424,16 @@ static void md_safemode_timeout(unsigned long data)
        md_wakeup_thread(mddev->thread);
 }
 
+static int start_dirty_degraded;
 
 static int do_md_run(mddev_t * mddev)
 {
-       int pnum, err;
+       int err;
        int chunk_size;
        struct list_head *tmp;
        mdk_rdev_t *rdev;
        struct gendisk *disk;
+       struct mdk_personality *pers;
        char b[BDEVNAME_SIZE];
 
        if (list_empty(&mddev->disks))
@@ -1629,20 +2450,8 @@ static int do_md_run(mddev_t * mddev)
                analyze_sbs(mddev);
 
        chunk_size = mddev->chunk_size;
-       pnum = level_to_pers(mddev->level);
 
-       if ((pnum != MULTIPATH) && (pnum != RAID1)) {
-               if (!chunk_size) {
-                       /*
-                        * 'default chunksize' in the old md code used to
-                        * be PAGE_SIZE, baaad.
-                        * we abort here to be on the safe side. We don't
-                        * want to continue the bad practice.
-                        */
-                       printk(KERN_ERR 
-                               "no chunksize specified, see 'man raidtab'\n");
-                       return -EINVAL;
-               }
+       if (chunk_size) {
                if (chunk_size > MAX_CHUNK_SIZE) {
                        printk(KERN_ERR "too big chunk_size: %d > %d\n",
                                chunk_size, MAX_CHUNK_SIZE);
@@ -1663,7 +2472,7 @@ static int do_md_run(mddev_t * mddev)
 
                /* devices must have minimum size of one chunk */
                ITERATE_RDEV(mddev,rdev,tmp) {
-                       if (rdev->faulty)
+                       if (test_bit(Faulty, &rdev->flags))
                                continue;
                        if (rdev->size < chunk_size / 1024) {
                                printk(KERN_WARNING
@@ -1678,10 +2487,10 @@ static int do_md_run(mddev_t * mddev)
        }
 
 #ifdef CONFIG_KMOD
-       if (!pers[pnum])
-       {
-               request_module("md-personality-%d", pnum);
-       }
+       if (mddev->level != LEVEL_NONE)
+               request_module("md-level-%d", mddev->level);
+       else if (mddev->clevel[0])
+               request_module("md-%s", mddev->clevel);
 #endif
 
        /*
@@ -1691,7 +2500,7 @@ static int do_md_run(mddev_t * mddev)
         * Also find largest hardsector size
         */
        ITERATE_RDEV(mddev,rdev,tmp) {
-               if (rdev->faulty)
+               if (test_bit(Faulty, &rdev->flags))
                        continue;
                sync_blockdev(rdev->bdev);
                invalidate_bdev(rdev->bdev, 0);
@@ -1703,26 +2512,39 @@ static int do_md_run(mddev_t * mddev)
                return -ENOMEM;
 
        spin_lock(&pers_lock);
-       if (!pers[pnum] || !try_module_get(pers[pnum]->owner)) {
+       pers = find_pers(mddev->level, mddev->clevel);
+       if (!pers || !try_module_get(pers->owner)) {
                spin_unlock(&pers_lock);
-               printk(KERN_WARNING "md: personality %d is not loaded!\n",
-                      pnum);
+               if (mddev->level != LEVEL_NONE)
+                       printk(KERN_WARNING "md: personality for level %d is not loaded!\n",
+                              mddev->level);
+               else
+                       printk(KERN_WARNING "md: personality for level %s is not loaded!\n",
+                              mddev->clevel);
                return -EINVAL;
        }
-
-       mddev->pers = pers[pnum];
+       mddev->pers = pers;
        spin_unlock(&pers_lock);
+       mddev->level = pers->level;
+       strlcpy(mddev->clevel, pers->name, sizeof(mddev->clevel));
 
        mddev->recovery = 0;
        mddev->resync_max_sectors = mddev->size << 1; /* may be over-ridden by personality */
+       mddev->barriers_work = 1;
+       mddev->ok_start_degraded = start_dirty_degraded;
 
-       /* before we start the array running, initialise the bitmap */
-       err = bitmap_create(mddev);
-       if (err)
-               printk(KERN_ERR "%s: failed to create bitmap (%d)\n",
-                       mdname(mddev), err);
-       else
-               err = mddev->pers->run(mddev);
+       if (start_readonly)
+               mddev->ro = 2; /* read-only, but switch on first write */
+
+       err = mddev->pers->run(mddev);
+       if (!err && mddev->pers->sync_request) {
+               err = bitmap_create(mddev);
+               if (err) {
+                       printk(KERN_ERR "%s: failed to create bitmap (%d)\n",
+                              mdname(mddev), err);
+                       mddev->pers->stop(mddev);
+               }
+       }
        if (err) {
                printk(KERN_ERR "md: pers->run() failed ...\n");
                module_put(mddev->pers->owner);
@@ -1730,12 +2552,24 @@ static int do_md_run(mddev_t * mddev)
                bitmap_destroy(mddev);
                return err;
        }
+       if (mddev->pers->sync_request)
+               sysfs_create_group(&mddev->kobj, &md_redundancy_group);
+       else if (mddev->ro == 2) /* auto-readonly not meaningful */
+               mddev->ro = 0;
+
        atomic_set(&mddev->writes_pending,0);
        mddev->safemode = 0;
        mddev->safemode_timer.function = md_safemode_timeout;
        mddev->safemode_timer.data = (unsigned long) mddev;
        mddev->safemode_delay = (20 * HZ)/1000 +1; /* 20 msec delay */
        mddev->in_sync = 1;
+
+       ITERATE_RDEV(mddev,rdev,tmp)
+               if (rdev->raid_disk >= 0) {
+                       char nm[20];
+                       sprintf(nm, "rd%d", rdev->raid_disk);
+                       sysfs_create_link(&mddev->kobj, &rdev->kobj, nm);
+               }
        
        set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
        md_wakeup_thread(mddev->thread);
@@ -1756,6 +2590,7 @@ static int do_md_run(mddev_t * mddev)
        mddev->queue->make_request_fn = mddev->pers->make_request;
 
        mddev->changed = 1;
+       md_new_event(mddev);
        return 0;
 }
 
@@ -1821,16 +2656,19 @@ static int do_md_stop(mddev_t * mddev, int ro)
 
                if (ro) {
                        err  = -ENXIO;
-                       if (mddev->ro)
+                       if (mddev->ro==1)
                                goto out;
                        mddev->ro = 1;
                } else {
                        bitmap_flush(mddev);
-                       wait_event(mddev->sb_wait, atomic_read(&mddev->pending_writes)==0);
+                       md_super_wait(mddev);
                        if (mddev->ro)
                                set_disk_ro(disk, 0);
                        blk_queue_make_request(mddev->queue, md_fail_request);
                        mddev->pers->stop(mddev);
+                       if (mddev->pers->sync_request)
+                               sysfs_remove_group(&mddev->kobj, &md_redundancy_group);
+
                        module_put(mddev->pers->owner);
                        mddev->pers = NULL;
                        if (mddev->ro)
@@ -1857,9 +2695,18 @@ static int do_md_stop(mddev_t * mddev, int ro)
         * Free resources if final stop
         */
        if (!ro) {
+               mdk_rdev_t *rdev;
+               struct list_head *tmp;
                struct gendisk *disk;
                printk(KERN_INFO "md: %s stopped.\n", mdname(mddev));
 
+               ITERATE_RDEV(mddev,rdev,tmp)
+                       if (rdev->raid_disk >= 0) {
+                               char nm[20];
+                               sprintf(nm, "rd%d", rdev->raid_disk);
+                               sysfs_remove_link(&mddev->kobj, nm);
+                       }
+
                export_array(mddev);
 
                mddev->array_size = 0;
@@ -1871,6 +2718,7 @@ static int do_md_stop(mddev_t * mddev, int ro)
                printk(KERN_INFO "md: %s switched to read-only mode.\n",
                        mdname(mddev));
        err = 0;
+       md_new_event(mddev);
 out:
        return err;
 }
@@ -2012,7 +2860,7 @@ static int autostart_array(dev_t startdev)
                return err;
        }
 
-       if (start_rdev->faulty) {
+       if (test_bit(Faulty, &start_rdev->flags)) {
                printk(KERN_WARNING 
                        "md: can not autostart based on faulty %s!\n",
                        bdevname(start_rdev->bdev,b));
@@ -2071,11 +2919,11 @@ static int get_array_info(mddev_t * mddev, void __user * arg)
        nr=working=active=failed=spare=0;
        ITERATE_RDEV(mddev,rdev,tmp) {
                nr++;
-               if (rdev->faulty)
+               if (test_bit(Faulty, &rdev->flags))
                        failed++;
                else {
                        working++;
-                       if (rdev->in_sync)
+                       if (test_bit(In_sync, &rdev->flags))
                                active++;       
                        else
                                spare++;
@@ -2166,9 +3014,9 @@ static int get_disk_info(mddev_t * mddev, void __user * arg)
                info.minor = MINOR(rdev->bdev->bd_dev);
                info.raid_disk = rdev->raid_disk;
                info.state = 0;
-               if (rdev->faulty)
+               if (test_bit(Faulty, &rdev->flags))
                        info.state |= (1<<MD_DISK_FAULTY);
-               else if (rdev->in_sync) {
+               else if (test_bit(In_sync, &rdev->flags)) {
                        info.state |= (1<<MD_DISK_ACTIVE);
                        info.state |= (1<<MD_DISK_SYNC);
                }
@@ -2261,7 +3109,7 @@ static int add_new_disk(mddev_t * mddev, mdu_disk_info_t *info)
                                validate_super(mddev, rdev);
                rdev->saved_raid_disk = rdev->raid_disk;
 
-               rdev->in_sync = 0; /* just to be sure */
+               clear_bit(In_sync, &rdev->flags); /* just to be sure */
                if (info->state & (1<<MD_DISK_WRITEMOSTLY))
                        set_bit(WriteMostly, &rdev->flags);
 
@@ -2299,21 +3147,15 @@ static int add_new_disk(mddev_t * mddev, mdu_disk_info_t *info)
                else
                        rdev->raid_disk = -1;
 
-               rdev->faulty = 0;
+               rdev->flags = 0;
+
                if (rdev->raid_disk < mddev->raid_disks)
-                       rdev->in_sync = (info->state & (1<<MD_DISK_SYNC));
-               else
-                       rdev->in_sync = 0;
+                       if (info->state & (1<<MD_DISK_SYNC))
+                               set_bit(In_sync, &rdev->flags);
 
                if (info->state & (1<<MD_DISK_WRITEMOSTLY))
                        set_bit(WriteMostly, &rdev->flags);
 
-               err = bind_rdev_to_array(rdev, mddev);
-               if (err) {
-                       export_rdev(rdev);
-                       return err;
-               }
-
                if (!mddev->persistent) {
                        printk(KERN_INFO "md: nonpersistent superblock ...\n");
                        rdev->sb_offset = rdev->bdev->bd_inode->i_size >> BLOCK_SIZE_BITS;
@@ -2321,8 +3163,11 @@ static int add_new_disk(mddev_t * mddev, mdu_disk_info_t *info)
                        rdev->sb_offset = calc_dev_sboffset(rdev->bdev);
                rdev->size = calc_dev_size(rdev, mddev->chunk_size);
 
-               if (!mddev->size || (mddev->size > rdev->size))
-                       mddev->size = rdev->size;
+               err = bind_rdev_to_array(rdev, mddev);
+               if (err) {
+                       export_rdev(rdev);
+                       return err;
+               }
        }
 
        return 0;
@@ -2345,6 +3190,7 @@ static int hot_remove_disk(mddev_t * mddev, dev_t dev)
 
        kick_rdev_from_array(rdev);
        md_update_sb(mddev);
+       md_new_event(mddev);
 
        return 0;
 busy:
@@ -2393,25 +3239,18 @@ static int hot_add_disk(mddev_t * mddev, dev_t dev)
        size = calc_dev_size(rdev, mddev->chunk_size);
        rdev->size = size;
 
-       if (size < mddev->size) {
-               printk(KERN_WARNING 
-                       "%s: disk size %llu blocks < array size %llu\n",
-                       mdname(mddev), (unsigned long long)size,
-                       (unsigned long long)mddev->size);
-               err = -ENOSPC;
-               goto abort_export;
-       }
-
-       if (rdev->faulty) {
+       if (test_bit(Faulty, &rdev->flags)) {
                printk(KERN_WARNING 
                        "md: can not hot-add faulty %s disk to %s!\n",
                        bdevname(rdev->bdev,b), mdname(mddev));
                err = -EINVAL;
                goto abort_export;
        }
-       rdev->in_sync = 0;
+       clear_bit(In_sync, &rdev->flags);
        rdev->desc_nr = -1;
-       bind_rdev_to_array(rdev, mddev);
+       err = bind_rdev_to_array(rdev, mddev);
+       if (err)
+               goto abort_export;
 
        /*
         * The rest should better be atomic, we can have disk failures
@@ -2435,7 +3274,7 @@ static int hot_add_disk(mddev_t * mddev, dev_t dev)
         */
        set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
        md_wakeup_thread(mddev->thread);
-
+       md_new_event(mddev);
        return 0;
 
 abort_unbind_export:
@@ -2571,6 +3410,9 @@ static int set_array_info(mddev_t * mddev, mdu_array_info_t *info)
 
        mddev->sb_dirty      = 1;
 
+       mddev->default_bitmap_offset = MD_SB_BYTES >> 9;
+       mddev->bitmap_offset = 0;
+
        /*
         * Generate a 128 bit UUID
         */
@@ -2579,6 +3421,81 @@ static int set_array_info(mddev_t * mddev, mdu_array_info_t *info)
        return 0;
 }
 
+static int update_size(mddev_t *mddev, unsigned long size)
+{
+       mdk_rdev_t * rdev;
+       int rv;
+       struct list_head *tmp;
+
+       if (mddev->pers->resize == NULL)
+               return -EINVAL;
+       /* The "size" is the amount of each device that is used.
+        * This can only make sense for arrays with redundancy.
+        * linear and raid0 always use whatever space is available
+        * We can only consider changing the size if no resync
+        * or reconstruction is happening, and if the new size
+        * is acceptable. It must fit before the sb_offset or,
+        * if that is <data_offset, it must fit before the
+        * size of each device.
+        * If size is zero, we find the largest size that fits.
+        */
+       if (mddev->sync_thread)
+               return -EBUSY;
+       ITERATE_RDEV(mddev,rdev,tmp) {
+               sector_t avail;
+               int fit = (size == 0);
+               if (rdev->sb_offset > rdev->data_offset)
+                       avail = (rdev->sb_offset*2) - rdev->data_offset;
+               else
+                       avail = get_capacity(rdev->bdev->bd_disk)
+                               - rdev->data_offset;
+               if (fit && (size == 0 || size > avail/2))
+                       size = avail/2;
+               if (avail < ((sector_t)size << 1))
+                       return -ENOSPC;
+       }
+       rv = mddev->pers->resize(mddev, (sector_t)size *2);
+       if (!rv) {
+               struct block_device *bdev;
+
+               bdev = bdget_disk(mddev->gendisk, 0);
+               if (bdev) {
+                       down(&bdev->bd_inode->i_sem);
+                       i_size_write(bdev->bd_inode, mddev->array_size << 10);
+                       up(&bdev->bd_inode->i_sem);
+                       bdput(bdev);
+               }
+       }
+       return rv;
+}
+
+static int update_raid_disks(mddev_t *mddev, int raid_disks)
+{
+       int rv;
+       /* change the number of raid disks */
+       if (mddev->pers->reshape == NULL)
+               return -EINVAL;
+       if (raid_disks <= 0 ||
+           raid_disks >= mddev->max_disks)
+               return -EINVAL;
+       if (mddev->sync_thread)
+               return -EBUSY;
+       rv = mddev->pers->reshape(mddev, raid_disks);
+       if (!rv) {
+               struct block_device *bdev;
+
+               bdev = bdget_disk(mddev->gendisk, 0);
+               if (bdev) {
+                       down(&bdev->bd_inode->i_sem);
+                       i_size_write(bdev->bd_inode, mddev->array_size << 10);
+                       up(&bdev->bd_inode->i_sem);
+                       bdput(bdev);
+               }
+       }
+       return rv;
+}
+
+
 /*
  * update_array_info is used to change the configuration of an
  * on-line array.
@@ -2627,71 +3544,12 @@ static int update_array_info(mddev_t *mddev, mdu_array_info_t *info)
                else
                        return mddev->pers->reconfig(mddev, info->layout, -1);
        }
-       if (mddev->size != info->size) {
-               mdk_rdev_t * rdev;
-               struct list_head *tmp;
-               if (mddev->pers->resize == NULL)
-                       return -EINVAL;
-               /* The "size" is the amount of each device that is used.
-                * This can only make sense for arrays with redundancy.
-                * linear and raid0 always use whatever space is available
-                * We can only consider changing the size if no resync
-                * or reconstruction is happening, and if the new size
-                * is acceptable. It must fit before the sb_offset or,
-                * if that is <data_offset, it must fit before the
-                * size of each device.
-                * If size is zero, we find the largest size that fits.
-                */
-               if (mddev->sync_thread)
-                       return -EBUSY;
-               ITERATE_RDEV(mddev,rdev,tmp) {
-                       sector_t avail;
-                       int fit = (info->size == 0);
-                       if (rdev->sb_offset > rdev->data_offset)
-                               avail = (rdev->sb_offset*2) - rdev->data_offset;
-                       else
-                               avail = get_capacity(rdev->bdev->bd_disk)
-                                       - rdev->data_offset;
-                       if (fit && (info->size == 0 || info->size > avail/2))
-                               info->size = avail/2;
-                       if (avail < ((sector_t)info->size << 1))
-                               return -ENOSPC;
-               }
-               rv = mddev->pers->resize(mddev, (sector_t)info->size *2);
-               if (!rv) {
-                       struct block_device *bdev;
-
-                       bdev = bdget_disk(mddev->gendisk, 0);
-                       if (bdev) {
-                               down(&bdev->bd_inode->i_sem);
-                               i_size_write(bdev->bd_inode, mddev->array_size << 10);
-                               up(&bdev->bd_inode->i_sem);
-                               bdput(bdev);
-                       }
-               }
-       }
-       if (mddev->raid_disks    != info->raid_disks) {
-               /* change the number of raid disks */
-               if (mddev->pers->reshape == NULL)
-                       return -EINVAL;
-               if (info->raid_disks <= 0 ||
-                   info->raid_disks >= mddev->max_disks)
-                       return -EINVAL;
-               if (mddev->sync_thread)
-                       return -EBUSY;
-               rv = mddev->pers->reshape(mddev, info->raid_disks);
-               if (!rv) {
-                       struct block_device *bdev;
-
-                       bdev = bdget_disk(mddev->gendisk, 0);
-                       if (bdev) {
-                               down(&bdev->bd_inode->i_sem);
-                               i_size_write(bdev->bd_inode, mddev->array_size << 10);
-                               up(&bdev->bd_inode->i_sem);
-                               bdput(bdev);
-                       }
-               }
-       }
+       if (mddev->size != info->size)
+               rv = update_size(mddev, info->size);
+
+       if (mddev->raid_disks    != info->raid_disks)
+               rv = update_raid_disks(mddev, info->raid_disks);
+
        if ((state ^ info->state) & (1<<MD_SB_BITMAP_PRESENT)) {
                if (mddev->pers->quiesce == NULL)
                        return -EINVAL;
@@ -2795,7 +3653,7 @@ static int md_ioctl(struct inode *inode, struct file *file,
                if (cnt > 0 ) {
                        printk(KERN_WARNING
                               "md: %s(pid %d) used deprecated START_ARRAY ioctl. "
-                              "This will not be supported beyond 2.6\n",
+                              "This will not be supported beyond July 2006\n",
                               current->comm, current->pid);
                        cnt--;
                }
@@ -2929,12 +3787,22 @@ static int md_ioctl(struct inode *inode, struct file *file,
 
        /*
         * The remaining ioctls are changing the state of the
-        * superblock, so we do not allow read-only arrays
-        * here:
+        * superblock, so we do not allow them on read-only arrays.
+        * However non-MD ioctls (e.g. get-size) will still come through
+        * here and hit the 'default' below, so only disallow
+        * 'md' ioctls, and switch to rw mode if started auto-readonly.
         */
-       if (mddev->ro) {
-               err = -EROFS;
-               goto abort_unlock;
+       if (_IOC_TYPE(cmd) == MD_MAJOR &&
+           mddev->ro && mddev->pers) {
+               if (mddev->ro == 2) {
+                       mddev->ro = 0;
+               set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+               md_wakeup_thread(mddev->thread);
+
+               } else {
+                       err = -EROFS;
+                       goto abort_unlock;
+               }
        }
 
        switch (cmd)
@@ -3064,21 +3932,26 @@ static int md_thread(void * arg)
         */
 
        allow_signal(SIGKILL);
-       complete(thread->event);
        while (!kthread_should_stop()) {
-               void (*run)(mddev_t *);
 
-               wait_event_interruptible_timeout(thread->wqueue,
-                                                test_bit(THREAD_WAKEUP, &thread->flags)
-                                                || kthread_should_stop(),
-                                                thread->timeout);
+               /* We need to wait INTERRUPTIBLE so that
+                * we don't add to the load-average.
+                * That means we need to be sure no signals are
+                * pending
+                */
+               if (signal_pending(current))
+                       flush_signals(current);
+
+               wait_event_interruptible_timeout
+                       (thread->wqueue,
+                        test_bit(THREAD_WAKEUP, &thread->flags)
+                        || kthread_should_stop(),
+                        thread->timeout);
                try_to_freeze();
 
                clear_bit(THREAD_WAKEUP, &thread->flags);
 
-               run = thread->run;
-               if (run)
-                       run(thread->mddev);
+               thread->run(thread->mddev);
        }
 
        return 0;
@@ -3097,27 +3970,21 @@ mdk_thread_t *md_register_thread(void (*run) (mddev_t *), mddev_t *mddev,
                                 const char *name)
 {
        mdk_thread_t *thread;
-       struct completion event;
 
-       thread = kmalloc(sizeof(mdk_thread_t), GFP_KERNEL);
+       thread = kzalloc(sizeof(mdk_thread_t), GFP_KERNEL);
        if (!thread)
                return NULL;
 
-       memset(thread, 0, sizeof(mdk_thread_t));
        init_waitqueue_head(&thread->wqueue);
 
-       init_completion(&event);
-       thread->event = &event;
        thread->run = run;
        thread->mddev = mddev;
-       thread->name = name;
        thread->timeout = MAX_SCHEDULE_TIMEOUT;
        thread->tsk = kthread_run(md_thread, thread, name, mdname(thread->mddev));
        if (IS_ERR(thread->tsk)) {
                kfree(thread);
                return NULL;
        }
-       wait_for_completion(&event);
        return thread;
 }
 
@@ -3136,7 +4003,7 @@ void md_error(mddev_t *mddev, mdk_rdev_t *rdev)
                return;
        }
 
-       if (!rdev || rdev->faulty)
+       if (!rdev || test_bit(Faulty, &rdev->flags))
                return;
 /*
        dprintk("md_error dev:%s, rdev:(%d:%d), (caller: %p,%p,%p,%p).\n",
@@ -3151,6 +4018,7 @@ void md_error(mddev_t *mddev, mdk_rdev_t *rdev)
        set_bit(MD_RECOVERY_INTR, &mddev->recovery);
        set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
        md_wakeup_thread(mddev->thread);
+       md_new_event(mddev);
 }
 
 /* seq_file implementation /proc/mdstat */
@@ -3291,24 +4159,29 @@ static void md_seq_stop(struct seq_file *seq, void *v)
                mddev_put(mddev);
 }
 
+struct mdstat_info {
+       int event;
+};
+
 static int md_seq_show(struct seq_file *seq, void *v)
 {
        mddev_t *mddev = v;
        sector_t size;
        struct list_head *tmp2;
        mdk_rdev_t *rdev;
-       int i;
+       struct mdstat_info *mi = seq->private;
        struct bitmap *bitmap;
 
        if (v == (void*)1) {
+               struct mdk_personality *pers;
                seq_printf(seq, "Personalities : ");
                spin_lock(&pers_lock);
-               for (i = 0; i < MAX_PERSONALITY; i++)
-                       if (pers[i])
-                               seq_printf(seq, "[%s] ", pers[i]->name);
+               list_for_each_entry(pers, &pers_list, list)
+                       seq_printf(seq, "[%s] ", pers->name);
 
                spin_unlock(&pers_lock);
                seq_printf(seq, "\n");
+               mi->event = atomic_read(&md_event_count);
                return 0;
        }
        if (v == (void*)2) {
@@ -3322,8 +4195,10 @@ static int md_seq_show(struct seq_file *seq, void *v)
                seq_printf(seq, "%s : %sactive", mdname(mddev),
                                                mddev->pers ? "" : "in");
                if (mddev->pers) {
-                       if (mddev->ro)
+                       if (mddev->ro==1)
                                seq_printf(seq, " (read-only)");
+                       if (mddev->ro==2)
+                               seq_printf(seq, "(auto-read-only)");
                        seq_printf(seq, " %s", mddev->pers->name);
                }
 
@@ -3334,7 +4209,7 @@ static int md_seq_show(struct seq_file *seq, void *v)
                                bdevname(rdev->bdev,b), rdev->desc_nr);
                        if (test_bit(WriteMostly, &rdev->flags))
                                seq_printf(seq, "(W)");
-                       if (rdev->faulty) {
+                       if (test_bit(Faulty, &rdev->flags)) {
                                seq_printf(seq, "(F)");
                                continue;
                        } else if (rdev->raid_disk < 0)
@@ -3363,11 +4238,15 @@ static int md_seq_show(struct seq_file *seq, void *v)
                if (mddev->pers) {
                        mddev->pers->status (seq, mddev);
                        seq_printf(seq, "\n      ");
-                       if (mddev->curr_resync > 2) {
-                               status_resync (seq, mddev);
-                               seq_printf(seq, "\n      ");
-                       } else if (mddev->curr_resync == 1 || mddev->curr_resync == 2)
-                               seq_printf(seq, "       resync=DELAYED\n      ");
+                       if (mddev->pers->sync_request) {
+                               if (mddev->curr_resync > 2) {
+                                       status_resync (seq, mddev);
+                                       seq_printf(seq, "\n      ");
+                               } else if (mddev->curr_resync == 1 || mddev->curr_resync == 2)
+                                       seq_printf(seq, "\tresync=DELAYED\n      ");
+                               else if (mddev->recovery_cp < MaxSector)
+                                       seq_printf(seq, "\tresync=PENDING\n      ");
+                       }
                } else
                        seq_printf(seq, "\n       ");
 
@@ -3411,47 +4290,68 @@ static struct seq_operations md_seq_ops = {
 static int md_seq_open(struct inode *inode, struct file *file)
 {
        int error;
+       struct mdstat_info *mi = kmalloc(sizeof(*mi), GFP_KERNEL);
+       if (mi == NULL)
+               return -ENOMEM;
 
        error = seq_open(file, &md_seq_ops);
+       if (error)
+               kfree(mi);
+       else {
+               struct seq_file *p = file->private_data;
+               p->private = mi;
+               mi->event = atomic_read(&md_event_count);
+       }
        return error;
 }
 
+static int md_seq_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *m = file->private_data;
+       struct mdstat_info *mi = m->private;
+       m->private = NULL;
+       kfree(mi);
+       return seq_release(inode, file);
+}
+
+static unsigned int mdstat_poll(struct file *filp, poll_table *wait)
+{
+       struct seq_file *m = filp->private_data;
+       struct mdstat_info *mi = m->private;
+       int mask;
+
+       poll_wait(filp, &md_event_waiters, wait);
+
+       /* always allow read */
+       mask = POLLIN | POLLRDNORM;
+
+       if (mi->event != atomic_read(&md_event_count))
+               mask |= POLLERR | POLLPRI;
+       return mask;
+}
+
 static struct file_operations md_seq_fops = {
        .open           = md_seq_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
-       .release        = seq_release,
+       .release        = md_seq_release,
+       .poll           = mdstat_poll,
 };
 
-int register_md_personality(int pnum, mdk_personality_t *p)
+int register_md_personality(struct mdk_personality *p)
 {
-       if (pnum >= MAX_PERSONALITY) {
-               printk(KERN_ERR
-                      "md: tried to install personality %s as nr %d, but max is %lu\n",
-                      p->name, pnum, MAX_PERSONALITY-1);
-               return -EINVAL;
-       }
-
        spin_lock(&pers_lock);
-       if (pers[pnum]) {
-               spin_unlock(&pers_lock);
-               return -EBUSY;
-       }
-
-       pers[pnum] = p;
-       printk(KERN_INFO "md: %s personality registered as nr %d\n", p->name, pnum);
+       list_add_tail(&p->list, &pers_list);
+       printk(KERN_INFO "md: %s personality registered for level %d\n", p->name, p->level);
        spin_unlock(&pers_lock);
        return 0;
 }
 
-int unregister_md_personality(int pnum)
+int unregister_md_personality(struct mdk_personality *p)
 {
-       if (pnum >= MAX_PERSONALITY)
-               return -EINVAL;
-
-       printk(KERN_INFO "md: %s personality unregistered\n", pers[pnum]->name);
+       printk(KERN_INFO "md: %s personality unregistered\n", p->name);
        spin_lock(&pers_lock);
-       pers[pnum] = NULL;
+       list_del_init(&p->list);
        spin_unlock(&pers_lock);
        return 0;
 }
@@ -3469,11 +4369,20 @@ static int is_mddev_idle(mddev_t *mddev)
                curr_events = disk_stat_read(disk, sectors[0]) + 
                                disk_stat_read(disk, sectors[1]) - 
                                atomic_read(&disk->sync_io);
-               /* Allow some slack between valud of curr_events and last_events,
-                * as there are some uninteresting races.
+               /* The difference between curr_events and last_events
+                * will be affected by any new non-sync IO (making
+                * curr_events bigger) and any difference in the amount of
+                * in-flight syncio (making current_events bigger or smaller)
+                * The amount in-flight is currently limited to
+                * 32*64K in raid1/10 and 256*PAGE_SIZE in raid5/6
+                * which is at most 4096 sectors.
+                * These numbers are fairly fragile and should be made
+                * more robust, probably by enforcing the
+                * 'window size' that md_do_sync sort-of uses.
+                *
                 * Note: the following is an unsigned comparison.
                 */
-               if ((curr_events - rdev->last_events + 32) > 64) {
+               if ((curr_events - rdev->last_events + 4096) > 8192) {
                        rdev->last_events = curr_events;
                        idle = 0;
                }
@@ -3504,15 +4413,22 @@ void md_write_start(mddev_t *mddev, struct bio *bi)
        if (bio_data_dir(bi) != WRITE)
                return;
 
+       BUG_ON(mddev->ro == 1);
+       if (mddev->ro == 2) {
+               /* need to switch to read/write */
+               mddev->ro = 0;
+               set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+               md_wakeup_thread(mddev->thread);
+       }
        atomic_inc(&mddev->writes_pending);
        if (mddev->in_sync) {
-               spin_lock(&mddev->write_lock);
+               spin_lock_irq(&mddev->write_lock);
                if (mddev->in_sync) {
                        mddev->in_sync = 0;
                        mddev->sb_dirty = 1;
                        md_wakeup_thread(mddev->thread);
                }
-               spin_unlock(&mddev->write_lock);
+               spin_unlock_irq(&mddev->write_lock);
        }
        wait_event(mddev->sb_wait, mddev->sb_dirty==0);
 }
@@ -3568,9 +4484,7 @@ static void md_do_sync(mddev_t *mddev)
                mddev->curr_resync = 2;
 
        try_again:
-               if (signal_pending(current) ||
-                   kthread_should_stop()) {
-                       flush_signals(current);
+               if (kthread_should_stop()) {
                        set_bit(MD_RECOVERY_INTR, &mddev->recovery);
                        goto skip;
                }
@@ -3590,9 +4504,8 @@ static void md_do_sync(mddev_t *mddev)
                                         * time 'round when curr_resync == 2
                                         */
                                        continue;
-                               prepare_to_wait(&resync_wait, &wq, TASK_INTERRUPTIBLE);
-                               if (!signal_pending(current) &&
-                                   !kthread_should_stop() &&
+                               prepare_to_wait(&resync_wait, &wq, TASK_UNINTERRUPTIBLE);
+                               if (!kthread_should_stop() &&
                                    mddev2->curr_resync >= mddev->curr_resync) {
                                        printk(KERN_INFO "md: delaying resync of %s"
                                               " until %s has finished resync (they"
@@ -3608,25 +4521,27 @@ static void md_do_sync(mddev_t *mddev)
                }
        } while (mddev->curr_resync < 2);
 
-       if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery))
+       if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) {
                /* resync follows the size requested by the personality,
                 * which defaults to physical size, but can be virtual size
                 */
                max_sectors = mddev->resync_max_sectors;
-       else
+               mddev->resync_mismatches = 0;
+       } else
                /* recovery follows the physical size of devices */
                max_sectors = mddev->size << 1;
 
        printk(KERN_INFO "md: syncing RAID array %s\n", mdname(mddev));
        printk(KERN_INFO "md: minimum _guaranteed_ reconstruction speed:"
-               " %d KB/sec/disc.\n", sysctl_speed_limit_min);
+               " %d KB/sec/disc.\n", speed_min(mddev));
        printk(KERN_INFO "md: using maximum available idle IO bandwidth "
               "(but not more than %d KB/sec) for reconstruction.\n",
-              sysctl_speed_limit_max);
+              speed_max(mddev));
 
        is_mddev_idle(mddev); /* this also initializes IO event counters */
        /* we don't use the checkpoint if there's a bitmap */
-       if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) && !mddev->bitmap)
+       if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) && !mddev->bitmap
+           && ! test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery))
                j = mddev->recovery_cp;
        else
                j = 0;
@@ -3662,7 +4577,7 @@ static void md_do_sync(mddev_t *mddev)
 
                skipped = 0;
                sectors = mddev->pers->sync_request(mddev, j, &skipped,
-                                           currspeed < sysctl_speed_limit_min);
+                                           currspeed < speed_min(mddev));
                if (sectors == 0) {
                        set_bit(MD_RECOVERY_ERR, &mddev->recovery);
                        goto out;
@@ -3675,7 +4590,11 @@ static void md_do_sync(mddev_t *mddev)
 
                j += sectors;
                if (j>1) mddev->curr_resync = j;
-
+               if (last_check == 0)
+                       /* this is the earliers that rebuilt will be
+                        * visible in /proc/mdstat
+                        */
+                       md_new_event(mddev);
 
                if (last_check + window > io_sectors || j == max_sectors)
                        continue;
@@ -3699,13 +4618,12 @@ static void md_do_sync(mddev_t *mddev)
                }
 
 
-               if (signal_pending(current) || kthread_should_stop()) {
+               if (kthread_should_stop()) {
                        /*
                         * got a signal, exit.
                         */
                        printk(KERN_INFO 
                                "md: md_do_sync() got signal ... exiting\n");
-                       flush_signals(current);
                        set_bit(MD_RECOVERY_INTR, &mddev->recovery);
                        goto out;
                }
@@ -3724,10 +4642,10 @@ static void md_do_sync(mddev_t *mddev)
                currspeed = ((unsigned long)(io_sectors-mddev->resync_mark_cnt))/2
                        /((jiffies-mddev->resync_mark)/HZ +1) +1;
 
-               if (currspeed > sysctl_speed_limit_min) {
-                       if ((currspeed > sysctl_speed_limit_max) ||
+               if (currspeed > speed_min(mddev)) {
+                       if ((currspeed > speed_max(mddev)) ||
                                        !is_mddev_idle(mddev)) {
-                               msleep_interruptible(250);
+                               msleep(500);
                                goto repeat;
                        }
                }
@@ -3820,7 +4738,7 @@ void md_check_recovery(mddev_t *mddev)
        if (mddev_trylock(mddev)==0) {
                int spares =0;
 
-               spin_lock(&mddev->write_lock);
+               spin_lock_irq(&mddev->write_lock);
                if (mddev->safemode && !atomic_read(&mddev->writes_pending) &&
                    !mddev->in_sync && mddev->recovery_cp == MaxSector) {
                        mddev->in_sync = 1;
@@ -3828,7 +4746,7 @@ void md_check_recovery(mddev_t *mddev)
                }
                if (mddev->safemode == 1)
                        mddev->safemode = 0;
-               spin_unlock(&mddev->write_lock);
+               spin_unlock_irq(&mddev->write_lock);
 
                if (mddev->sb_dirty)
                        md_update_sb(mddev);
@@ -3862,11 +4780,16 @@ void md_check_recovery(mddev_t *mddev)
                        mddev->recovery = 0;
                        /* flag recovery needed just to double check */
                        set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+                       md_new_event(mddev);
                        goto unlock;
                }
-               if (mddev->recovery)
-                       /* probably just the RECOVERY_NEEDED flag */
-                       mddev->recovery = 0;
+               /* Clear some bits that don't mean anything, but
+                * might be left set
+                */
+               clear_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+               clear_bit(MD_RECOVERY_ERR, &mddev->recovery);
+               clear_bit(MD_RECOVERY_INTR, &mddev->recovery);
+               clear_bit(MD_RECOVERY_DONE, &mddev->recovery);
 
                /* no recovery is running.
                 * remove any failed drives, then
@@ -3876,31 +4799,42 @@ void md_check_recovery(mddev_t *mddev)
                 */
                ITERATE_RDEV(mddev,rdev,rtmp)
                        if (rdev->raid_disk >= 0 &&
-                           (rdev->faulty || ! rdev->in_sync) &&
+                           (test_bit(Faulty, &rdev->flags) || ! test_bit(In_sync, &rdev->flags)) &&
                            atomic_read(&rdev->nr_pending)==0) {
-                               if (mddev->pers->hot_remove_disk(mddev, rdev->raid_disk)==0)
+                               if (mddev->pers->hot_remove_disk(mddev, rdev->raid_disk)==0) {
+                                       char nm[20];
+                                       sprintf(nm,"rd%d", rdev->raid_disk);
+                                       sysfs_remove_link(&mddev->kobj, nm);
                                        rdev->raid_disk = -1;
+                               }
                        }
 
                if (mddev->degraded) {
                        ITERATE_RDEV(mddev,rdev,rtmp)
                                if (rdev->raid_disk < 0
-                                   && !rdev->faulty) {
-                                       if (mddev->pers->hot_add_disk(mddev,rdev))
+                                   && !test_bit(Faulty, &rdev->flags)) {
+                                       if (mddev->pers->hot_add_disk(mddev,rdev)) {
+                                               char nm[20];
+                                               sprintf(nm, "rd%d", rdev->raid_disk);
+                                               sysfs_create_link(&mddev->kobj, &rdev->kobj, nm);
                                                spares++;
-                                       else
+                                               md_new_event(mddev);
+                                       } else
                                                break;
                                }
                }
 
-               if (!spares && (mddev->recovery_cp == MaxSector )) {
-                       /* nothing we can do ... */
+               if (spares) {
+                       clear_bit(MD_RECOVERY_SYNC, &mddev->recovery);
+                       clear_bit(MD_RECOVERY_CHECK, &mddev->recovery);
+               } else if (mddev->recovery_cp < MaxSector) {
+                       set_bit(MD_RECOVERY_SYNC, &mddev->recovery);
+               } else if (!test_bit(MD_RECOVERY_SYNC, &mddev->recovery))
+                       /* nothing to be done ... */
                        goto unlock;
-               }
+
                if (mddev->pers->sync_request) {
                        set_bit(MD_RECOVERY_RUNNING, &mddev->recovery);
-                       if (!spares)
-                               set_bit(MD_RECOVERY_SYNC, &mddev->recovery);
                        if (spares && mddev->bitmap && ! mddev->bitmap->file) {
                                /* We are adding a device or devices to an array
                                 * which has the bitmap stored on all devices.
@@ -3917,9 +4851,9 @@ void md_check_recovery(mddev_t *mddev)
                                        mdname(mddev));
                                /* leave the spares where they are, it shouldn't hurt */
                                mddev->recovery = 0;
-                       } else {
+                       } else
                                md_wakeup_thread(mddev->sync_thread);
-                       }
+                       md_new_event(mddev);
                }
        unlock:
                mddev_unlock(mddev);
@@ -3975,7 +4909,7 @@ static int __init md_init(void)
                        " MD_SB_DISKS=%d\n",
                        MD_MAJOR_VERSION, MD_MINOR_VERSION,
                        MD_PATCHLEVEL_VERSION, MAX_MD_DEVS, MD_SB_DISKS);
-       printk(KERN_INFO "md: bitmap version %d.%d\n", BITMAP_MAJOR,
+       printk(KERN_INFO "md: bitmap version %d.%d\n", BITMAP_MAJOR_HI,
                        BITMAP_MINOR);
 
        if (register_blkdev(MAJOR_NR, "md"))
@@ -4039,7 +4973,7 @@ static void autostart_arrays(int part)
                if (IS_ERR(rdev))
                        continue;
 
-               if (rdev->faulty) {
+               if (test_bit(Faulty, &rdev->flags)) {
                        MD_BUG();
                        continue;
                }
@@ -4086,6 +5020,25 @@ static __exit void md_exit(void)
 module_init(md_init)
 module_exit(md_exit)
 
+static int get_ro(char *buffer, struct kernel_param *kp)
+{
+       return sprintf(buffer, "%d", start_readonly);
+}
+static int set_ro(const char *val, struct kernel_param *kp)
+{
+       char *e;
+       int num = simple_strtoul(val, &e, 10);
+       if (*val && (*e == '\0' || *e == '\n')) {
+               start_readonly = num;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+module_param_call(start_ro, set_ro, get_ro, NULL, 0600);
+module_param(start_dirty_degraded, int, 0644);
+
+
 EXPORT_SYMBOL(register_md_personality);
 EXPORT_SYMBOL(unregister_md_personality);
 EXPORT_SYMBOL(md_error);