[PATCH] md: allow md/raid_disks to be settable
[firefly-linux-kernel-4.4.55.git] / drivers / md / md.c
index 9e57e97bd530f0193b48559470a0707fce0a703a..86e9f2efae5c7b95c69074f73f92e66a706d48c2 100644 (file)
@@ -303,12 +303,15 @@ static mdk_rdev_t * find_rdev(mddev_t * mddev, dev_t dev)
        return NULL;
 }
 
-static struct mdk_personality *find_pers(int level)
+static struct mdk_personality *find_pers(int level, char *clevel)
 {
        struct mdk_personality *pers;
-       list_for_each_entry(pers, &pers_list, list)
-               if (pers->level == level)
+       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;
 }
 
@@ -715,6 +718,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;
@@ -996,6 +1000,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;
@@ -1051,6 +1056,7 @@ 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;
@@ -1134,6 +1140,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);
@@ -1587,9 +1595,30 @@ super_show(mdk_rdev_t *rdev, char *page)
 }
 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 struct attribute *rdev_default_attrs[] = {
        &rdev_state.attr,
        &rdev_super.attr,
+       &rdev_errors.attr,
        NULL,
 };
 static ssize_t
@@ -1669,6 +1698,7 @@ static mdk_rdev_t *md_import_device(dev_t newdev, int super_format, int super_mi
        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) {
@@ -1774,15 +1804,36 @@ static ssize_t
 level_show(mddev_t *mddev, char *page)
 {
        struct mdk_personality *p = mddev->pers;
-       if (p == NULL && mddev->raid_disks == 0)
-               return 0;
-       if (mddev->level >= 0)
-               return sprintf(page, "raid%d\n", mddev->level);
-       else
+       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_RO(level);
+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)
@@ -1792,7 +1843,27 @@ raid_disks_show(mddev_t *mddev, char *page)
        return sprintf(page, "%d\n", mddev->raid_disks);
 }
 
-static struct md_sysfs_entry md_raid_disks = __ATTR_RO(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)
@@ -1819,6 +1890,92 @@ static struct md_sysfs_entry md_chunk_size =
 __ATTR(chunk_size, 0644, chunk_size_show, chunk_size_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)
 {
@@ -1887,6 +2044,8 @@ static struct attribute *md_default_attrs[] = {
        &md_level.attr,
        &md_raid_disks.attr,
        &md_chunk_size.attr,
+       &md_size.attr,
+       &md_metadata.attr,
        NULL,
 };
 
@@ -2070,7 +2229,10 @@ static int do_md_run(mddev_t * mddev)
        }
 
 #ifdef CONFIG_KMOD
-       request_module("md-level-%d", mddev->level);
+       if (mddev->level != LEVEL_NONE)
+               request_module("md-level-%d", mddev->level);
+       else if (mddev->clevel[0])
+               request_module("md-%s", mddev->clevel);
 #endif
 
        /*
@@ -2092,15 +2254,21 @@ static int do_md_run(mddev_t * mddev)
                return -ENOMEM;
 
        spin_lock(&pers_lock);
-       pers = find_pers(mddev->level);
+       pers = find_pers(mddev->level, mddev->clevel);
        if (!pers || !try_module_get(pers->owner)) {
                spin_unlock(&pers_lock);
-               printk(KERN_WARNING "md: personality for level %d is not loaded!\n",
-                      mddev->level);
+               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;
        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 */
@@ -3005,6 +3173,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.
@@ -3053,71 +3296,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;
@@ -4598,7 +4782,7 @@ static int set_ro(const char *val, struct kernel_param *kp)
        int num = simple_strtoul(val, &e, 10);
        if (*val && (*e == '\0' || *e == '\n')) {
                start_readonly = num;
-               return 0;;
+               return 0;
        }
        return -EINVAL;
 }