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: 17+ 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-10 8:31 ` sashiko-bot
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:36 ` sashiko-bot
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:37 ` sashiko-bot
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:51 ` sashiko-bot
2026-09-10 8:11 ` [PATCH v2 6/8] md: pass a queue_limits through ->run() Jack Wang
2026-09-10 8:49 ` sashiko-bot
2026-09-11 10:54 ` Nilay Shroff
2026-09-10 8:11 ` Jack Wang [this message]
2026-09-10 8:48 ` [PATCH v2 7/8] md: open new legs before locking the array sashiko-bot
2026-09-10 8:11 ` [PATCH v2 8/8] md: link a new leg's holder " 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.