From: Jack Wang <jinpu.wang@ionos.com>
To: Song Liu <song@kernel.org>, Yu Kuai <yukuai@fygo.io>,
linux-raid@vger.kernel.org, Nilay Shroff <nilay@linux.ibm.com>,
abd.masalkhi@gmail.com
Cc: linux-block@vger.kernel.org, Jens Axboe <axboe@kernel.dk>,
Christoph Hellwig <hch@lst.de>,
Damien Le Moal <dlemoal@kernel.org>,
Ming Lei <tom.leiming@gmail.com>, Xiao Ni <xiao@kernel.org>,
Li Nan <magiclinan@didiglobal.com>,
Mike Snitzer <snitzer@kernel.org>,
Mikulas Patocka <mpatocka@redhat.com>,
dm-devel@lists.linux.dev, linux-kernel@vger.kernel.org,
Jack Wang <jinpu.wang@cloud.ionos.com>
Subject: [PATCH v2 7/8] md: open new legs before locking the array
Date: Thu, 10 Sep 2026 10:11:12 +0200 [thread overview]
Message-ID: <20260910081114.1605746-8-jinpu.wang@ionos.com> (raw)
In-Reply-To: <20260910081114.1605746-1-jinpu.wang@ionos.com>
From: Jack Wang <jinpu.wang@cloud.ionos.com>
Opening a leg takes disk->open_mutex, and the scsi disk probe path nests
q->limits_lock inside it (sd_open() -> sd_revalidate_disk()). md opens
legs under reconfig_mutex, which this series makes q->limits_lock nest
outside, closing a cycle. Booting with lockdep on an md root reports it
during assembly:
-> #2 (&q->limits_lock): sd_revalidate_disk / sd_open
-> #1 (&disk->open_mutex): md_import_device
md_add_new_disk
md_ioctl <- ADD_NEW_DISK
-> #0 (&mddev->reconfig_mutex): md_ioctl <- RUN_ARRAY
Move every open out from under the lock. md_import_new_disk() mirrors
md_add_new_disk()'s branch selection so all three of its branches take a
pre-opened leg, and hot_add_disk(), new_dev_store() and md_setup_drive()
open before they lock as well.
The mddev fields the open depends on are read without reconfig_mutex, so
each caller rechecks them once the array is locked and rejects the add
with -EBUSY if the branch or the superblock format would have changed.
md_autostart_arrays() needs no change: it opens under
detected_devices_mutex.
Assisted-by: LLM
Signed-off-by: Jack Wang <jinpu.wang@cloud.ionos.com>
---
drivers/md/md-autodetect.c | 17 ++-
drivers/md/md.c | 238 ++++++++++++++++++++++++++-----------
drivers/md/md.h | 21 +++-
3 files changed, 203 insertions(+), 73 deletions(-)
diff --git a/drivers/md/md-autodetect.c b/drivers/md/md-autodetect.c
index e15ae2fb58a2..e592577356ad 100644
--- a/drivers/md/md-autodetect.c
+++ b/drivers/md/md-autodetect.c
@@ -208,6 +208,7 @@ static void __init md_setup_drive(struct md_setup_args *args)
.major = MAJOR(devices[i]),
.minor = MINOR(devices[i]),
};
+ struct md_new_disk nd;
if (args->level != LEVEL_NONE) {
dinfo.number = i;
@@ -216,7 +217,21 @@ static void __init md_setup_drive(struct md_setup_args *args)
(1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC);
}
- md_add_new_disk(mddev, &dinfo, NULL);
+ /*
+ * Opening a leg takes disk->open_mutex, which must not nest
+ * inside reconfig_mutex, see md_import_new_disk(). Drop the
+ * array lock around it; this is __init and the array is not
+ * reachable yet, so nothing else can touch it in between.
+ */
+ mddev_unlock(mddev);
+ if (md_import_new_disk(mddev, &dinfo, &nd)) {
+ mddev_lock_nointr(mddev);
+ continue;
+ }
+ mddev_lock_nointr(mddev);
+
+ md_add_new_disk(mddev, &dinfo, &nd, NULL);
+ md_put_new_disk(&nd);
}
/*
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 5be956e80563..fa033d7d3831 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -4942,6 +4942,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
struct md_rdev *rdev;
unsigned int noio_flags;
int err;
+ int persistent, external, major_version, minor_version;
if (!*buf || *e != ':' || !e[1] || e[1] == '\n')
return -EINVAL;
@@ -4953,32 +4954,52 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
minor != MINOR(dev))
return -EOVERFLOW;
- err = mddev_suspend_and_lock(mddev);
- if (err)
- return err;
- noio_flags = memalloc_noio_save();
- if (mddev->persistent) {
- rdev = md_import_device(dev, mddev->major_version,
- mddev->minor_version);
- if (!IS_ERR(rdev) && !list_empty(&mddev->disks)) {
- struct md_rdev *rdev0
- = list_entry(mddev->disks.next,
- struct md_rdev, same_set);
- err = super_types[mddev->major_version]
- .load_super(rdev, rdev0, mddev->minor_version);
- if (err < 0)
- goto out;
- }
- } else if (mddev->external)
+ /*
+ * Open before locking the array: bdev_open() takes disk->open_mutex,
+ * which must not nest inside reconfig_mutex, see md_import_new_disk().
+ * The fields below are read without the lock and rechecked under it.
+ */
+ persistent = READ_ONCE(mddev->persistent);
+ external = READ_ONCE(mddev->external);
+ major_version = READ_ONCE(mddev->major_version);
+ minor_version = READ_ONCE(mddev->minor_version);
+
+ if (persistent)
+ rdev = md_import_device(dev, major_version, minor_version);
+ else if (external)
rdev = md_import_device(dev, -2, -1);
else
rdev = md_import_device(dev, -1, -1);
- if (IS_ERR(rdev)) {
- memalloc_noio_restore(noio_flags);
- mddev_unlock_and_resume(mddev);
+ if (IS_ERR(rdev))
return PTR_ERR(rdev);
+
+ err = mddev_suspend_and_lock(mddev);
+ if (err) {
+ export_rdev(rdev);
+ return err;
}
+ noio_flags = memalloc_noio_save();
+
+ if (persistent != mddev->persistent || external != mddev->external ||
+ major_version != mddev->major_version ||
+ minor_version != mddev->minor_version) {
+ pr_warn("%s: array reconfigured while opening %pg\n",
+ mdname(mddev), rdev->bdev);
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (mddev->persistent && !list_empty(&mddev->disks)) {
+ struct md_rdev *rdev0
+ = list_entry(mddev->disks.next,
+ struct md_rdev, same_set);
+ err = super_types[mddev->major_version]
+ .load_super(rdev, rdev0, mddev->minor_version);
+ if (err < 0)
+ goto out;
+ }
+
err = bind_rdev_to_array(rdev, mddev);
out:
if (err)
@@ -7685,12 +7706,35 @@ static int get_disk_info(struct mddev *mddev, void __user * arg)
return 0;
}
+/*
+ * @nd carries an rdev the caller opened before locking the array, for the
+ * branch its snapshot selected. Every caller must open first; doing it
+ * here would nest disk->open_mutex inside reconfig_mutex.
+ */
int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
- struct queue_limits *lim)
+ struct md_new_disk *nd, struct queue_limits *lim)
{
struct md_rdev *rdev;
dev_t dev = MKDEV(info->major,info->minor);
+ /*
+ * The open ran unlocked, so anything that selects a different branch
+ * below, or a different superblock format, means it was done against
+ * an array that no longer looks like this one.
+ */
+ if (nd && nd->rdev &&
+ (nd->have_raid_disks != (mddev->raid_disks != 0) ||
+ nd->have_pers != !!mddev->pers ||
+ nd->persistent != mddev->persistent ||
+ nd->major_version != mddev->major_version ||
+ nd->minor_version != mddev->minor_version)) {
+ pr_warn("%s: array reconfigured while opening %pg\n",
+ mdname(mddev), nd->rdev->bdev);
+ export_rdev(nd->rdev);
+ nd->rdev = NULL;
+ return -EBUSY;
+ }
+
if (mddev_is_clustered(mddev) &&
!(info->state & ((1 << MD_DISK_CLUSTER_ADD) | (1 << MD_DISK_CANDIDATE)))) {
pr_warn("%s: Cannot add to clustered mddev.\n",
@@ -7703,13 +7747,12 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
if (!mddev->raid_disks) {
int err;
+
/* expecting a device which has a superblock */
- rdev = md_import_device(dev, mddev->major_version, mddev->minor_version);
- if (IS_ERR(rdev)) {
- pr_warn("md: md_import_device returned %ld\n",
- PTR_ERR(rdev));
- return PTR_ERR(rdev);
- }
+ if (WARN_ON_ONCE(!nd || !nd->rdev))
+ return -EINVAL;
+ rdev = nd->rdev;
+ nd->rdev = NULL;
if (!list_empty(&mddev->disks)) {
struct md_rdev *rdev0
= list_entry(mddev->disks.next,
@@ -7742,16 +7785,10 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
mdname(mddev));
return -EINVAL;
}
- if (mddev->persistent)
- rdev = md_import_device(dev, mddev->major_version,
- mddev->minor_version);
- else
- rdev = md_import_device(dev, -1, -1);
- if (IS_ERR(rdev)) {
- pr_warn("md: md_import_device returned %ld\n",
- PTR_ERR(rdev));
- return PTR_ERR(rdev);
- }
+ if (WARN_ON_ONCE(!nd || !nd->rdev))
+ return -EINVAL;
+ rdev = nd->rdev;
+ nd->rdev = NULL;
/* set saved_raid_disk if appropriate */
if (!mddev->persistent) {
if (info->state & (1<<MD_DISK_SYNC) &&
@@ -7853,12 +7890,11 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
if (!(info->state & (1<<MD_DISK_FAULTY))) {
int err;
- rdev = md_import_device(dev, -1, 0);
- if (IS_ERR(rdev)) {
- pr_warn("md: error, md_import_device() returned %ld\n",
- PTR_ERR(rdev));
- return PTR_ERR(rdev);
- }
+
+ if (WARN_ON_ONCE(!nd || !nd->rdev))
+ return -EINVAL;
+ rdev = nd->rdev;
+ nd->rdev = NULL;
rdev->desc_nr = info->number;
if (info->raid_disk < mddev->raid_disks)
rdev->raid_disk = info->raid_disk;
@@ -7930,7 +7966,8 @@ static int hot_remove_disk(struct mddev *mddev, dev_t dev,
return -EBUSY;
}
-static int hot_add_disk(struct mddev *mddev, dev_t dev)
+/* @nd carries a leg the caller opened before the array was locked */
+static int hot_add_disk(struct mddev *mddev, struct md_new_disk *nd)
{
int err;
struct md_rdev *rdev;
@@ -7949,12 +7986,10 @@ static int hot_add_disk(struct mddev *mddev, dev_t dev)
return -EINVAL;
}
- rdev = md_import_device(dev, -1, 0);
- if (IS_ERR(rdev)) {
- pr_warn("md: error, md_import_device() returned %ld\n",
- PTR_ERR(rdev));
+ if (WARN_ON_ONCE(!nd->rdev))
return -EINVAL;
- }
+ rdev = nd->rdev;
+ nd->rdev = NULL;
if (mddev->persistent)
rdev->sb_start = calc_dev_sboffset(rdev);
@@ -8497,14 +8532,61 @@ static inline int md_ioctl_valid(unsigned int cmd)
* journal device or a personality without ->hot_remove_disk, but that depends
* on disk info still in user memory here, so it is included as a whole.
*/
-static bool md_ioctl_may_add_disk(unsigned int cmd)
+
+/*
+ * Open the leg before the array is locked; bdev_open() takes
+ * disk->open_mutex, which must not nest inside reconfig_mutex. mddev is
+ * read unlocked on purpose, and md_add_new_disk() rechecks the snapshot.
+ */
+int md_import_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
+ struct md_new_disk *nd)
{
- switch (cmd) {
- case ADD_NEW_DISK:
- case HOT_REMOVE_DISK:
- return true;
- default:
- return false;
+ dev_t dev = MKDEV(info->major, info->minor);
+ struct md_rdev *rdev;
+
+ memset(nd, 0, sizeof(*nd));
+ nd->have_raid_disks = READ_ONCE(mddev->raid_disks) != 0;
+ nd->have_pers = !!READ_ONCE(mddev->pers);
+ nd->persistent = READ_ONCE(mddev->persistent);
+ nd->major_version = READ_ONCE(mddev->major_version);
+ nd->minor_version = READ_ONCE(mddev->minor_version);
+
+ if (!nd->have_raid_disks) {
+ /* a device with a superblock, for an array being assembled */
+ rdev = md_import_device(dev, nd->major_version,
+ nd->minor_version);
+ } else if (nd->have_pers) {
+ /* a hot spare; this is the branch that stacks limits */
+ nd->stacks = true;
+ if (nd->persistent)
+ rdev = md_import_device(dev, nd->major_version,
+ nd->minor_version);
+ else
+ rdev = md_import_device(dev, -1, -1);
+ } else if (nd->major_version == 0) {
+ rdev = md_import_device(dev, -1, 0);
+ } else {
+ /* md_add_new_disk() rejects this, nothing to open */
+ return 0;
+ }
+
+ if (IS_ERR(rdev)) {
+ int err = PTR_ERR(rdev);
+
+ pr_warn("md: md_import_device returned %d\n", err);
+ return err;
+ }
+
+ nd->rdev = rdev;
+ return 0;
+}
+
+/* release a leg md_add_new_disk() did not take ownership of */
+void md_put_new_disk(struct md_new_disk *nd)
+{
+ if (nd->rdev) {
+ export_rdev(nd->rdev);
+ nd->rdev = NULL;
}
}
@@ -8566,6 +8648,8 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
struct request_queue *q = NULL;
struct queue_limits lim;
struct queue_limits *limp = NULL;
+ struct md_new_disk nd = { };
+ mdu_disk_info_t info;
bool suspend;
err = md_ioctl_valid(cmd);
@@ -8616,8 +8700,27 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
if (!md_is_rdwr(mddev))
flush_work(&mddev->sync_work);
+ if (cmd == ADD_NEW_DISK) {
+ if (copy_from_user(&info, argp, sizeof(info))) {
+ err = -EFAULT;
+ goto out;
+ }
+ err = md_import_new_disk(mddev, &info, &nd);
+ if (err)
+ goto out;
+ } else if (cmd == HOT_ADD_DISK) {
+ nd.rdev = md_import_device(new_decode_dev(arg), -1, 0);
+ if (IS_ERR(nd.rdev)) {
+ pr_warn("md: error, md_import_device() returned %ld\n",
+ PTR_ERR(nd.rdev));
+ nd.rdev = NULL;
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
/* q->limits_lock nests outside both, see md_start_sync() */
- if ((md_ioctl_may_add_disk(cmd) || cmd == RUN_ARRAY) &&
+ if ((nd.stacks || cmd == HOT_REMOVE_DISK || cmd == RUN_ARRAY) &&
!mddev_is_dm(mddev)) {
q = mddev->gendisk->queue;
lim = queue_limits_start_update(q);
@@ -8681,14 +8784,10 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
* So require mddev->pers and MD_DISK_SYNC.
*/
if (mddev->pers) {
- mdu_disk_info_t info;
- if (copy_from_user(&info, argp, sizeof(info)))
- err = -EFAULT;
- else if (!(info.state & (1<<MD_DISK_SYNC)))
+ if (!(info.state & (1<<MD_DISK_SYNC)))
/* Need to clear read-only for this */
break;
- else
- err = md_add_new_disk(mddev, &info, limp);
+ err = md_add_new_disk(mddev, &info, &nd, limp);
goto unlock;
}
break;
@@ -8721,14 +8820,8 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
switch (cmd) {
case ADD_NEW_DISK:
- {
- mdu_disk_info_t info;
- if (copy_from_user(&info, argp, sizeof(info)))
- err = -EFAULT;
- else
- err = md_add_new_disk(mddev, &info, limp);
+ err = md_add_new_disk(mddev, &info, &nd, limp);
goto unlock;
- }
case CLUSTERED_DISK_NACK:
if (mddev_is_clustered(mddev))
@@ -8738,7 +8831,7 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
goto unlock;
case HOT_ADD_DISK:
- err = hot_add_disk(mddev, new_decode_dev(arg));
+ err = hot_add_disk(mddev, &nd);
goto unlock;
case RUN_ARRAY:
@@ -8770,6 +8863,9 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
}
out:
+ /* a leg we opened but nothing took ownership of */
+ md_put_new_disk(&nd);
+
if (cmd == STOP_ARRAY_RO || (err && cmd == STOP_ARRAY))
clear_bit(MD_CLOSING, &mddev->flags);
return err;
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 73f6ef20f266..73a27d83d65a 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -1051,8 +1051,27 @@ struct mdu_disk_info_s;
extern int mdp_major;
void md_autostart_arrays(int part);
int md_set_array_info(struct mddev *mddev, struct mdu_array_info_s *info);
+/*
+ * A leg opened before the array was locked, with the mddev fields that
+ * selected the branch and the superblock format. Opening takes
+ * disk->open_mutex, which must not nest inside reconfig_mutex; the fields
+ * are read unlocked and md_add_new_disk() rechecks them.
+ */
+struct md_new_disk {
+ struct md_rdev *rdev;
+ bool stacks; /* the add can reach ->hot_add_disk() */
+ bool have_pers;
+ bool have_raid_disks;
+ int persistent;
+ int major_version;
+ int minor_version;
+};
+
+int md_import_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
+ struct md_new_disk *nd);
+void md_put_new_disk(struct md_new_disk *nd);
int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info,
- struct queue_limits *lim);
+ struct md_new_disk *nd, struct queue_limits *lim);
int do_md_run(struct mddev *mddev, struct queue_limits *lim);
#define MDDEV_STACK_INTEGRITY (1u << 0)
int mddev_stack_rdev_limits(struct mddev *mddev, struct queue_limits *lim,
--
2.43.0
next prev parent reply other threads:[~2026-09-10 8:11 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 8:11 [PATCH v2 0/8] md: don't wait for q->limits_lock while md holds back I/O Jack Wang
2026-09-10 8:11 ` [PATCH v2 1/8] md: pass a queue_limits down to ->hot_add_disk() Jack Wang
2026-09-11 10:46 ` Nilay Shroff
2026-09-10 8:11 ` [PATCH v2 2/8] md: don't wait for q->limits_lock in check_sb_changes() Jack Wang
2026-09-10 8:11 ` [PATCH v2 3/8] md: pass a queue_limits through the rdev sysfs stores Jack Wang
2026-09-10 8:11 ` [PATCH v2 4/8] md: defer the io_opt update out of the sync thread Jack Wang
2026-09-10 8:11 ` [PATCH v2 5/8] md: take q->limits_lock before locking and suspending the array Jack Wang
2026-09-10 8:11 ` [PATCH v2 6/8] md: pass a queue_limits through ->run() Jack Wang
2026-09-11 10:54 ` Nilay Shroff
2026-09-10 8:11 ` Jack Wang [this message]
2026-09-10 8:11 ` [PATCH v2 8/8] md: link a new leg's holder before locking the array Jack Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910081114.1605746-8-jinpu.wang@ionos.com \
--to=jinpu.wang@ionos.com \
--cc=abd.masalkhi@gmail.com \
--cc=axboe@kernel.dk \
--cc=dlemoal@kernel.org \
--cc=dm-devel@lists.linux.dev \
--cc=hch@lst.de \
--cc=jinpu.wang@cloud.ionos.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-raid@vger.kernel.org \
--cc=magiclinan@didiglobal.com \
--cc=mpatocka@redhat.com \
--cc=nilay@linux.ibm.com \
--cc=snitzer@kernel.org \
--cc=song@kernel.org \
--cc=tom.leiming@gmail.com \
--cc=xiao@kernel.org \
--cc=yukuai@fygo.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox