]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
md: allow configuring logical block size
authorLi Nan <linan122@huawei.com>
Mon, 3 Nov 2025 12:57:57 +0000 (20:57 +0800)
committerYu Kuai <yukuai@fnnas.com>
Tue, 11 Nov 2025 03:20:15 +0000 (11:20 +0800)
Previously, raid array used the maximum logical block size (LBS)
of all member disks. Adding a larger LBS disk at runtime could
unexpectedly increase RAID's LBS, risking corruption of existing
partitions. This can be reproduced by:

```
  # LBS of sd[de] is 512 bytes, sdf is 4096 bytes.
  mdadm -CRq /dev/md0 -l1 -n3 /dev/sd[de] missing --assume-clean

  # LBS is 512
  cat /sys/block/md0/queue/logical_block_size

  # create partition md0p1
  parted -s /dev/md0 mklabel gpt mkpart primary 1MiB 100%
  lsblk | grep md0p1

  # LBS becomes 4096 after adding sdf
  mdadm --add -q /dev/md0 /dev/sdf
  cat /sys/block/md0/queue/logical_block_size

  # partition lost
  partprobe /dev/md0
  lsblk | grep md0p1
```

Simply restricting larger-LBS disks is inflexible. In some scenarios,
only disks with 512 bytes LBS are available currently, but later, disks
with 4KB LBS may be added to the array.

Making LBS configurable is the best way to solve this scenario.
After this patch, the raid will:
  - store LBS in disk metadata
  - add a read-write sysfs 'mdX/logical_block_size'

Future mdadm should support setting LBS via metadata field during RAID
creation and the new sysfs. Though the kernel allows runtime LBS changes,
users should avoid modifying it after creating partitions or filesystems
to prevent compatibility issues.

Only 1.x metadata supports configurable LBS. 0.90 metadata inits all
fields to default values at auto-detect. Supporting 0.90 would require
more extensive changes and no such use case has been observed.

Note that many RAID paths rely on PAGE_SIZE alignment, including for
metadata I/O. A larger LBS than PAGE_SIZE will result in metadata
read/write failures. So this config should be prevented.

Link: https://lore.kernel.org/linux-raid/20251103125757.1405796-6-linan666@huaweicloud.com
Signed-off-by: Li Nan <linan122@huawei.com>
Reviewed-by: Xiao Ni <xni@redhat.com>
Signed-off-by: Yu Kuai <yukuai@fnnas.com>
Documentation/admin-guide/md.rst
drivers/md/md-linear.c
drivers/md/md.c
drivers/md/md.h
drivers/md/raid0.c
drivers/md/raid1.c
drivers/md/raid10.c
drivers/md/raid5.c
include/uapi/linux/raid/md_p.h

index deed823eab016cf94e56ef80bbadc29a56cf4e88..dc7eab191caaa6783ea1bcd2b6b11efbd1d3f5df 100644 (file)
@@ -238,6 +238,16 @@ All md devices contain:
      the number of devices in a raid4/5/6, or to support external
      metadata formats which mandate such clipping.
 
+  logical_block_size
+     Configure the array's logical block size in bytes. This attribute
+     is only supported for 1.x meta. Write the value before starting
+     array. The final array LBS uses the maximum between this
+     configuration and LBS of all combined devices. Note that
+     LBS cannot exceed PAGE_SIZE before RAID supports folio.
+     WARNING: Arrays created on new kernel cannot be assembled at old
+     kernel due to padding check, Set module parameter 'check_new_feature'
+     to false to bypass, but data loss may occur.
+
   reshape_position
      This is either ``none`` or a sector number within the devices of
      the array where ``reshape`` is up to.  If this is set, the three
index 25a6ddedea658abf48c069a6494d2a5820a50879..8d7b82c4a7234c21b64d6de7d073fb88f59ef899 100644 (file)
@@ -72,6 +72,7 @@ static int linear_set_limits(struct mddev *mddev)
 
        md_init_stacking_limits(&lim);
        lim.max_hw_sectors = mddev->chunk_sectors;
+       lim.logical_block_size = mddev->logical_block_size;
        lim.max_write_zeroes_sectors = mddev->chunk_sectors;
        lim.max_hw_wzeroes_unmap_sectors = mddev->chunk_sectors;
        lim.io_min = mddev->chunk_sectors << 9;
index 9676e2477df6b35bfc30926d1156001fccab9dda..7b5c5967568fba1310bbea57072cf2d183c5222c 100644 (file)
@@ -1999,6 +1999,7 @@ static int super_1_validate(struct mddev *mddev, struct md_rdev *freshest, struc
                mddev->layout = le32_to_cpu(sb->layout);
                mddev->raid_disks = le32_to_cpu(sb->raid_disks);
                mddev->dev_sectors = le64_to_cpu(sb->size);
+               mddev->logical_block_size = le32_to_cpu(sb->logical_block_size);
                mddev->events = ev1;
                mddev->bitmap_info.offset = 0;
                mddev->bitmap_info.space = 0;
@@ -2208,6 +2209,7 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev)
        sb->chunksize = cpu_to_le32(mddev->chunk_sectors);
        sb->level = cpu_to_le32(mddev->level);
        sb->layout = cpu_to_le32(mddev->layout);
+       sb->logical_block_size = cpu_to_le32(mddev->logical_block_size);
        if (test_bit(FailFast, &rdev->flags))
                sb->devflags |= FailFast1;
        else
@@ -5936,6 +5938,68 @@ static struct md_sysfs_entry md_serialize_policy =
 __ATTR(serialize_policy, S_IRUGO | S_IWUSR, serialize_policy_show,
        serialize_policy_store);
 
+static int mddev_set_logical_block_size(struct mddev *mddev,
+                               unsigned int lbs)
+{
+       int err = 0;
+       struct queue_limits lim;
+
+       if (queue_logical_block_size(mddev->gendisk->queue) >= lbs) {
+               pr_err("%s: Cannot set LBS smaller than mddev LBS %u\n",
+                      mdname(mddev), lbs);
+               return -EINVAL;
+       }
+
+       lim = queue_limits_start_update(mddev->gendisk->queue);
+       lim.logical_block_size = lbs;
+       pr_info("%s: logical_block_size is changed, data may be lost\n",
+               mdname(mddev));
+       err = queue_limits_commit_update(mddev->gendisk->queue, &lim);
+       if (err)
+               return err;
+
+       mddev->logical_block_size = lbs;
+       /* New lbs will be written to superblock after array is running */
+       set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags);
+       return 0;
+}
+
+static ssize_t
+lbs_show(struct mddev *mddev, char *page)
+{
+       return sprintf(page, "%u\n", mddev->logical_block_size);
+}
+
+static ssize_t
+lbs_store(struct mddev *mddev, const char *buf, size_t len)
+{
+       unsigned int lbs;
+       int err = -EBUSY;
+
+       /* Only 1.x meta supports configurable LBS */
+       if (mddev->major_version == 0)
+               return -EINVAL;
+
+       if (mddev->pers)
+               return -EBUSY;
+
+       err = kstrtouint(buf, 10, &lbs);
+       if (err < 0)
+               return -EINVAL;
+
+       err = mddev_lock(mddev);
+       if (err)
+               goto unlock;
+
+       err = mddev_set_logical_block_size(mddev, lbs);
+
+unlock:
+       mddev_unlock(mddev);
+       return err ?: len;
+}
+
+static struct md_sysfs_entry md_logical_block_size =
+__ATTR(logical_block_size, 0644, lbs_show, lbs_store);
 
 static struct attribute *md_default_attrs[] = {
        &md_level.attr,
@@ -5958,6 +6022,7 @@ static struct attribute *md_default_attrs[] = {
        &md_consistency_policy.attr,
        &md_fail_last_dev.attr,
        &md_serialize_policy.attr,
+       &md_logical_block_size.attr,
        NULL,
 };
 
@@ -6088,6 +6153,17 @@ int mddev_stack_rdev_limits(struct mddev *mddev, struct queue_limits *lim,
                        return -EINVAL;
        }
 
+       /*
+        * Before RAID adding folio support, the logical_block_size
+        * should be smaller than the page size.
+        */
+       if (lim->logical_block_size > PAGE_SIZE) {
+               pr_err("%s: logical_block_size must not larger than PAGE_SIZE\n",
+                       mdname(mddev));
+               return -EINVAL;
+       }
+       mddev->logical_block_size = lim->logical_block_size;
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(mddev_stack_rdev_limits);
@@ -6699,6 +6775,7 @@ static void md_clean(struct mddev *mddev)
        mddev->chunk_sectors = 0;
        mddev->ctime = mddev->utime = 0;
        mddev->layout = 0;
+       mddev->logical_block_size = 0;
        mddev->max_disks = 0;
        mddev->events = 0;
        mddev->can_decrease_events = 0;
index fd6e001c1d38f1767f1624bb43b4ae8570398281..6985f2829bbd6a0b0f63b6a724c3e5e829636815 100644 (file)
@@ -433,6 +433,7 @@ struct mddev {
        sector_t                        array_sectors; /* exported array size */
        int                             external_size; /* size managed
                                                        * externally */
+       unsigned int                    logical_block_size;
        __u64                           events;
        /* If the last 'event' was simply a clean->dirty transition, and
         * we didn't write it to the spares, then it is safe and simple
index fbf7634015215b9652c6b458cc1d46ab4fefe083..47aee1b1d4d17995577e9f71a2484e00e8542331 100644 (file)
@@ -380,6 +380,7 @@ static int raid0_set_limits(struct mddev *mddev)
        lim.max_hw_sectors = mddev->chunk_sectors;
        lim.max_write_zeroes_sectors = mddev->chunk_sectors;
        lim.max_hw_wzeroes_unmap_sectors = mddev->chunk_sectors;
+       lim.logical_block_size = mddev->logical_block_size;
        lim.io_min = mddev->chunk_sectors << 9;
        lim.io_opt = lim.io_min * mddev->raid_disks;
        lim.chunk_sectors = mddev->chunk_sectors;
index 592a402330047423d972f3f8b7cec036488ed8a8..57d50465eed1b76ca29f596ddc432ba02783302e 100644 (file)
@@ -3213,6 +3213,7 @@ static int raid1_set_limits(struct mddev *mddev)
        md_init_stacking_limits(&lim);
        lim.max_write_zeroes_sectors = 0;
        lim.max_hw_wzeroes_unmap_sectors = 0;
+       lim.logical_block_size = mddev->logical_block_size;
        lim.features |= BLK_FEAT_ATOMIC_WRITES;
        err = mddev_stack_rdev_limits(mddev, &lim, MDDEV_STACK_INTEGRITY);
        if (err)
index 14dcd5142eb4772ea7933dd41304a0b33e52fc7f..84be4cc7e87399ecb3b686301c630b25d4d58ee7 100644 (file)
@@ -4000,6 +4000,7 @@ static int raid10_set_queue_limits(struct mddev *mddev)
        md_init_stacking_limits(&lim);
        lim.max_write_zeroes_sectors = 0;
        lim.max_hw_wzeroes_unmap_sectors = 0;
+       lim.logical_block_size = mddev->logical_block_size;
        lim.io_min = mddev->chunk_sectors << 9;
        lim.chunk_sectors = mddev->chunk_sectors;
        lim.io_opt = lim.io_min * raid10_nr_stripes(conf);
index 24b32a0c95b4031ba94087646c9a38343545963f..cdbc7eba5c54018de59dd070b6b1566c3506621d 100644 (file)
@@ -7745,6 +7745,7 @@ static int raid5_set_limits(struct mddev *mddev)
        stripe = roundup_pow_of_two(data_disks * (mddev->chunk_sectors << 9));
 
        md_init_stacking_limits(&lim);
+       lim.logical_block_size = mddev->logical_block_size;
        lim.io_min = mddev->chunk_sectors << 9;
        lim.io_opt = lim.io_min * (conf->raid_disks - conf->max_degraded);
        lim.features |= BLK_FEAT_RAID_PARTIAL_STRIPES_EXPENSIVE;
index ac74133a47688773d9aa75fab1ef376ddfe3da91..310068bb2a1dae83138a0211e3b81e58a9ab4db8 100644 (file)
@@ -291,7 +291,8 @@ struct mdp_superblock_1 {
        __le64  resync_offset;  /* data before this offset (from data_offset) known to be in sync */
        __le32  sb_csum;        /* checksum up to devs[max_dev] */
        __le32  max_dev;        /* size of devs[] array to consider */
-       __u8    pad3[64-32];    /* set to 0 when writing */
+       __le32  logical_block_size;     /* same as q->limits->logical_block_size */
+       __u8    pad3[64-36];    /* set to 0 when writing */
 
        /* device state information. Indexed by dev_number.
         * 2 bytes per device