Add support to allocate and free a multipath gendisk.
NVMe has almost like-for-like equivalents here:
- mpath_alloc_head_disk() -> nvme_mpath_alloc_disk()
- multipath_partition_scan_work() -> nvme_partition_scan_work()
- mpath_remove_disk() -> nvme_remove_head()
- mpath_device_set_live() -> nvme_mpath_set_live()
struct mpath_head_template is introduced as a method for drivers to
provide custom multipath functionality.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 41 ++++++++++++
lib/multipath.c | 129 ++++++++++++++++++++++++++++++++++++++
2 files changed, 170 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 18cd133b7ca21..be9dd9fb83345 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -5,11 +5,28 @@
#include <linux/blkdev.h>
#include <linux/srcu.h>
+extern const struct block_device_operations mpath_ops;
+
+struct mpath_disk {
+ struct gendisk *disk;
+ struct kref ref;
+ struct work_struct partition_scan_work;
+ struct mutex lock;
+ struct mpath_head *mpath_head;
+ struct device *parent;
+};
+
struct mpath_device {
struct list_head siblings;
struct gendisk *disk;
};
+struct mpath_head_template {
+ const struct attribute_group **device_groups;
+};
+
+#define MPATH_HEAD_DISK_LIVE 0
+
struct mpath_head {
struct srcu_struct srcu;
struct list_head dev_list; /* list of all mpath_devs */
@@ -17,12 +34,36 @@ struct mpath_head {
struct kref ref;
+ unsigned long flags;
struct mpath_device __rcu *current_path[MAX_NUMNODES];
+ const struct mpath_head_template *mpdt;
void *drvdata;
};
+static inline struct mpath_disk *mpath_bd_device_to_disk(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline struct mpath_disk *mpath_gendisk_to_disk(struct gendisk *disk)
+{
+ return mpath_bd_device_to_disk(disk_to_dev(disk));
+}
+
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
struct mpath_head *mpath_alloc_head(void);
+void mpath_put_disk(struct mpath_disk *mpath_disk);
+void mpath_remove_disk(struct mpath_disk *mpath_disk);
+void mpath_unregister_disk(struct mpath_disk *mpath_disk);
+struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim,
+ int numa_node);
+void mpath_device_set_live(struct mpath_disk *mpath_disk,
+ struct mpath_device *mpath_device);
+void mpath_unregister_disk(struct mpath_disk *mpath_disk);
+static inline bool is_mpath_head(struct gendisk *disk)
+{
+ return disk->fops == &mpath_ops;
+}
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 15c495675d729..88efb0ae16acb 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -32,6 +32,135 @@ void mpath_put_head(struct mpath_head *mpath_head)
}
EXPORT_SYMBOL_GPL(mpath_put_head);
+static void mpath_free_disk(struct kref *ref)
+{
+ struct mpath_disk *mpath_disk =
+ container_of(ref, struct mpath_disk, ref);
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+
+ put_disk(mpath_disk->disk);
+ mpath_put_head(mpath_head);
+ kfree(mpath_disk);
+}
+
+void mpath_put_disk(struct mpath_disk *mpath_disk)
+{
+ kref_put(&mpath_disk->ref, mpath_free_disk);
+}
+EXPORT_SYMBOL_GPL(mpath_put_disk);
+
+static int mpath_get_disk(struct mpath_disk *mpath_disk)
+{
+ if (!kref_get_unless_zero(&mpath_disk->ref)) {
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode)
+{
+ struct mpath_disk *mpath_disk = disk->private_data;
+
+ return mpath_get_disk(mpath_disk);
+}
+
+static void mpath_bdev_release(struct gendisk *disk)
+{
+ struct mpath_disk *mpath_disk = disk->private_data;
+
+ mpath_put_disk(mpath_disk);
+}
+
+const struct block_device_operations mpath_ops = {
+ .owner = THIS_MODULE,
+ .open = mpath_bdev_open,
+ .release = mpath_bdev_release,
+};
+EXPORT_SYMBOL_GPL(mpath_ops);
+
+static void multipath_partition_scan_work(struct work_struct *work)
+{
+ struct mpath_disk *mpath_disk =
+ container_of(work, struct mpath_disk, partition_scan_work);
+
+ if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN,
+ &mpath_disk->disk->state)))
+ return;
+
+ mutex_lock(&mpath_disk->disk->open_mutex);
+ bdev_disk_changed(mpath_disk->disk, false);
+ mutex_unlock(&mpath_disk->disk->open_mutex);
+}
+
+void mpath_remove_disk(struct mpath_disk *mpath_disk)
+{
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+
+ if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ struct gendisk *disk = mpath_disk->disk;
+
+ del_gendisk(disk);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_remove_disk);
+
+void mpath_unregister_disk(struct mpath_disk *mpath_disk)
+{
+ mpath_remove_disk(mpath_disk);
+ mpath_put_disk(mpath_disk);
+}
+EXPORT_SYMBOL_GPL(mpath_unregister_disk);
+
+struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim, int numa_node)
+{
+ struct mpath_disk *mpath_disk;
+
+ mpath_disk = kzalloc(sizeof(*mpath_disk), GFP_KERNEL);
+ if (!mpath_disk)
+ return NULL;
+
+ INIT_WORK(&mpath_disk->partition_scan_work,
+ multipath_partition_scan_work);
+ mutex_init(&mpath_disk->lock);
+ kref_init(&mpath_disk->ref);
+
+ mpath_disk->disk = blk_alloc_disk(lim, numa_node);
+ if (IS_ERR(mpath_disk->disk)) {
+ kfree(mpath_disk);
+ return NULL;
+ }
+
+ mpath_disk->disk->private_data = mpath_disk;
+ mpath_disk->disk->fops = &mpath_ops;
+
+ set_bit(GD_SUPPRESS_PART_SCAN, &mpath_disk->disk->state);
+
+ return mpath_disk;
+}
+EXPORT_SYMBOL_GPL(mpath_alloc_head_disk);
+
+void mpath_device_set_live(struct mpath_disk *mpath_disk,
+ struct mpath_device *mpath_device)
+{
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+ int ret;
+
+ if (!mpath_disk)
+ return;
+
+ if (!test_and_set_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ dev_set_drvdata(disk_to_dev(mpath_disk->disk), mpath_disk);
+ ret = device_add_disk(mpath_disk->parent, mpath_disk->disk,
+ mpath_head->mpdt->device_groups);
+ if (ret) {
+ clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags);
+ return;
+ }
+ queue_work(mpath_wq, &mpath_disk->partition_scan_work);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_device_set_live);
+
struct mpath_head *mpath_alloc_head(void)
{
struct mpath_head *mpath_head;
--
2.43.5
…
> +++ b/lib/multipath.c
…
> +static void multipath_partition_scan_work(struct work_struct *work)
> +{
…
> + mutex_lock(&mpath_disk->disk->open_mutex);
> + bdev_disk_changed(mpath_disk->disk, false);
> + mutex_unlock(&mpath_disk->disk->open_mutex);
> +}
…
Under which circumstances would you become interested to apply a statement
like “guard(mutex)(&mpath_disk->disk->open_mutex);”?
https://elixir.bootlin.com/linux/v6.19.3/source/include/linux/mutex.h#L253
Regards,
Markus
On 2/25/26 9:02 PM, John Garry wrote:
> Add support to allocate and free a multipath gendisk.
>
> NVMe has almost like-for-like equivalents here:
> - mpath_alloc_head_disk() -> nvme_mpath_alloc_disk()
> - multipath_partition_scan_work() -> nvme_partition_scan_work()
> - mpath_remove_disk() -> nvme_remove_head()
> - mpath_device_set_live() -> nvme_mpath_set_live()
>
> struct mpath_head_template is introduced as a method for drivers to
> provide custom multipath functionality.
>
> Signed-off-by: John Garry <john.g.garry@oracle.com>
> ---
> include/linux/multipath.h | 41 ++++++++++++
> lib/multipath.c | 129 ++++++++++++++++++++++++++++++++++++++
> 2 files changed, 170 insertions(+)
>
> diff --git a/include/linux/multipath.h b/include/linux/multipath.h
> index 18cd133b7ca21..be9dd9fb83345 100644
> --- a/include/linux/multipath.h
> +++ b/include/linux/multipath.h
> @@ -5,11 +5,28 @@
> #include <linux/blkdev.h>
> #include <linux/srcu.h>
>
> +extern const struct block_device_operations mpath_ops;
> +
> +struct mpath_disk {
> + struct gendisk *disk;
> + struct kref ref;
> + struct work_struct partition_scan_work;
> + struct mutex lock;
> + struct mpath_head *mpath_head;
> + struct device *parent;
> +};
> +
> struct mpath_device {
> struct list_head siblings;
> struct gendisk *disk;
> };
>
> +struct mpath_head_template {
> + const struct attribute_group **device_groups;
> +};
> +
> +#define MPATH_HEAD_DISK_LIVE 0
> +
> struct mpath_head {
> struct srcu_struct srcu;
> struct list_head dev_list; /* list of all mpath_devs */
> @@ -17,12 +34,36 @@ struct mpath_head {
>
> struct kref ref;
>
> + unsigned long flags;
> struct mpath_device __rcu *current_path[MAX_NUMNODES];
> + const struct mpath_head_template *mpdt;
> void *drvdata;
> };
>
Not sure why we don't have back reference to struct mpath_disk
from struct mpath_head here. Does it make sense to have this?
> +static inline struct mpath_disk *mpath_bd_device_to_disk(struct device *dev)
> +{
> + return dev_get_drvdata(dev);
> +}
> +
> +static inline struct mpath_disk *mpath_gendisk_to_disk(struct gendisk *disk)
> +{
> + return mpath_bd_device_to_disk(disk_to_dev(disk));
> +}
> +
> int mpath_get_head(struct mpath_head *mpath_head);
> void mpath_put_head(struct mpath_head *mpath_head);
> struct mpath_head *mpath_alloc_head(void);
> +void mpath_put_disk(struct mpath_disk *mpath_disk);
> +void mpath_remove_disk(struct mpath_disk *mpath_disk);
> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
> +struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim,
> + int numa_node);
> +void mpath_device_set_live(struct mpath_disk *mpath_disk,
> + struct mpath_device *mpath_device);
> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
>
> +static inline bool is_mpath_head(struct gendisk *disk)
> +{
> + return disk->fops == &mpath_ops;
> +}
> #endif // _LIBMULTIPATH_H
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 15c495675d729..88efb0ae16acb 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
> @@ -32,6 +32,135 @@ void mpath_put_head(struct mpath_head *mpath_head)
> }
> EXPORT_SYMBOL_GPL(mpath_put_head);
>
> +static void mpath_free_disk(struct kref *ref)
> +{
> + struct mpath_disk *mpath_disk =
> + container_of(ref, struct mpath_disk, ref);
> + struct mpath_head *mpath_head = mpath_disk->mpath_head;
> +
> + put_disk(mpath_disk->disk);
> + mpath_put_head(mpath_head);
> + kfree(mpath_disk);
> +}
> +
The mpath_alloc_head_disk() doesn't get a reference to the
mpath_head object but here while freeing mpath_disk we put
the reference to mpath_head. Would that create a reference
imbalance? Yes we got a reference to mpath_head while
allocating it but then these are two (alloc mpath_disk and
alloc mpath_head) disjoint operations. In that case, can't
we have both mpath_disk and mpath_head allocated under one
libmultipath API?
Thanks,
--Nilay
On 02/03/2026 12:31, Nilay Shroff wrote:
>>
>> +#define MPATH_HEAD_DISK_LIVE 0
>> +
>> struct mpath_head {
>> struct srcu_struct srcu;
>> struct list_head dev_list; /* list of all mpath_devs */
>> @@ -17,12 +34,36 @@ struct mpath_head {
>> struct kref ref;
>> + unsigned long flags;
>> struct mpath_device __rcu *current_path[MAX_NUMNODES];
>> + const struct mpath_head_template *mpdt;
>> void *drvdata;
>> };
> Not sure why we don't have back reference to struct mpath_disk
> from struct mpath_head here. Does it make sense to have this?
We can get away without it.
Some more background info .. so the concept of separate mpath_head and
mpath_disk is driven by SCSI, which has scsi_device and scsi_disk
classes. The scsi_disk driver (sd.c) controls the per-path gendisk and
the mpath_disk, and these internals are hidden from the scsi_core (which
controls the scsi_device). SCSI having this layered approach makes
things more complicated. This is unlike NVMe, where the core driver
controls the NS gendisk also.
>
>
>> +static inline struct mpath_disk *mpath_bd_device_to_disk(struct
>> device *dev)
>> +{
>> + return dev_get_drvdata(dev);
>> +}
>> +
>> +static inline struct mpath_disk *mpath_gendisk_to_disk(struct gendisk
>> *disk)
>> +{
>> + return mpath_bd_device_to_disk(disk_to_dev(disk));
>> +}
>> +
>> int mpath_get_head(struct mpath_head *mpath_head);
>> void mpath_put_head(struct mpath_head *mpath_head);
>> struct mpath_head *mpath_alloc_head(void);
>> +void mpath_put_disk(struct mpath_disk *mpath_disk);
>> +void mpath_remove_disk(struct mpath_disk *mpath_disk);
>> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
>> +struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim,
>> + int numa_node);
>> +void mpath_device_set_live(struct mpath_disk *mpath_disk,
>> + struct mpath_device *mpath_device);
>> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
>> +static inline bool is_mpath_head(struct gendisk *disk)
>> +{
>> + return disk->fops == &mpath_ops;
>> +}
>> #endif // _LIBMULTIPATH_H
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 15c495675d729..88efb0ae16acb 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>> @@ -32,6 +32,135 @@ void mpath_put_head(struct mpath_head *mpath_head)
>> }
>> EXPORT_SYMBOL_GPL(mpath_put_head);
>> +static void mpath_free_disk(struct kref *ref)
>> +{
>> + struct mpath_disk *mpath_disk =
>> + container_of(ref, struct mpath_disk, ref);
>> + struct mpath_head *mpath_head = mpath_disk->mpath_head;
>> +
>> + put_disk(mpath_disk->disk);
>> + mpath_put_head(mpath_head);
>> + kfree(mpath_disk);
>> +}
>> +
>
> The mpath_alloc_head_disk() doesn't get a reference to the
> mpath_head object but here while freeing mpath_disk we put
> the reference to mpath_head. Would that create a reference
> imbalance?
I think that what I done can be improved. If you check
nvme_mpath_alloc_disk(), when we alloc the head the ref is 1, and then
we rely on the disk release to release that head reference.
> Yes we got a reference to mpath_head while
> allocating it but then these are two (alloc mpath_disk and
> alloc mpath_head) disjoint operations. In that case, can't
> we have both mpath_disk and mpath_head allocated under one
> libmultipath API?
I would like to have something simpler (like mainline NVMe code), but I
have it this way because of SCSI, as above.
Thanks
On 3/2/26 9:09 PM, John Garry wrote:
> On 02/03/2026 12:31, Nilay Shroff wrote:
>>>
>>> +#define MPATH_HEAD_DISK_LIVE 0
>>> +
>>> struct mpath_head {
>>> struct srcu_struct srcu;
>>> struct list_head dev_list; /* list of all mpath_devs */
>>> @@ -17,12 +34,36 @@ struct mpath_head {
>>> struct kref ref;
>>> + unsigned long flags;
>>> struct mpath_device __rcu *current_path[MAX_NUMNODES];
>>> + const struct mpath_head_template *mpdt;
>>> void *drvdata;
>>> };
>> Not sure why we don't have back reference to struct mpath_disk
>> from struct mpath_head here. Does it make sense to have this?
>
> We can get away without it.
>
> Some more background info .. so the concept of separate mpath_head and
> mpath_disk is driven by SCSI, which has scsi_device and scsi_disk
> classes. The scsi_disk driver (sd.c) controls the per-path gendisk and
> the mpath_disk, and these internals are hidden from the scsi_core (which
> controls the scsi_device). SCSI having this layered approach makes
> things more complicated. This is unlike NVMe, where the core driver
> controls the NS gendisk also.
>
>>
>>
>>> +static inline struct mpath_disk *mpath_bd_device_to_disk(struct
>>> device *dev)
>>> +{
>>> + return dev_get_drvdata(dev);
>>> +}
>>> +
>>> +static inline struct mpath_disk *mpath_gendisk_to_disk(struct
>>> gendisk *disk)
>>> +{
>>> + return mpath_bd_device_to_disk(disk_to_dev(disk));
>>> +}
>>> +
>>> int mpath_get_head(struct mpath_head *mpath_head);
>>> void mpath_put_head(struct mpath_head *mpath_head);
>>> struct mpath_head *mpath_alloc_head(void);
>>> +void mpath_put_disk(struct mpath_disk *mpath_disk);
>>> +void mpath_remove_disk(struct mpath_disk *mpath_disk);
>>> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
>>> +struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim,
>>> + int numa_node);
>>> +void mpath_device_set_live(struct mpath_disk *mpath_disk,
>>> + struct mpath_device *mpath_device);
>>> +void mpath_unregister_disk(struct mpath_disk *mpath_disk);
>>> +static inline bool is_mpath_head(struct gendisk *disk)
>>> +{
>>> + return disk->fops == &mpath_ops;
>>> +}
>>> #endif // _LIBMULTIPATH_H
>>> diff --git a/lib/multipath.c b/lib/multipath.c
>>> index 15c495675d729..88efb0ae16acb 100644
>>> --- a/lib/multipath.c
>>> +++ b/lib/multipath.c
>>> @@ -32,6 +32,135 @@ void mpath_put_head(struct mpath_head *mpath_head)
>>> }
>>> EXPORT_SYMBOL_GPL(mpath_put_head);
>>> +static void mpath_free_disk(struct kref *ref)
>>> +{
>>> + struct mpath_disk *mpath_disk =
>>> + container_of(ref, struct mpath_disk, ref);
>>> + struct mpath_head *mpath_head = mpath_disk->mpath_head;
>>> +
>>> + put_disk(mpath_disk->disk);
>>> + mpath_put_head(mpath_head);
>>> + kfree(mpath_disk);
>>> +}
>>> +
>>
>> The mpath_alloc_head_disk() doesn't get a reference to the
>> mpath_head object but here while freeing mpath_disk we put
>> the reference to mpath_head. Would that create a reference
>> imbalance?
>
> I think that what I done can be improved. If you check
> nvme_mpath_alloc_disk(), when we alloc the head the ref is 1, and then
> we rely on the disk release to release that head reference.
>
>> Yes we got a reference to mpath_head while
>> allocating it but then these are two (alloc mpath_disk and
>> alloc mpath_head) disjoint operations. In that case, can't
>> we have both mpath_disk and mpath_head allocated under one
>> libmultipath API?
>
> I would like to have something simpler (like mainline NVMe code), but I
> have it this way because of SCSI, as above.
>
I understand the intended lifetime model due to SCSI, but the current
flow is somewhat confusing.
In nvme_mpath_alloc_disk(), mpath_disk and mpath_head are allocated
separately. However, during teardown, both objects are ultimately
released through mpath_free_disk(), which drops the reference to
mpath_head via mpath_put_head().
Since the allocation of mpath_disk and mpath_head happens independently,
it is not immediately obvious why their lifetime is tied together and
why they are not freed independently when the NVMe head node is removed.
This coupling makes the ownership and reference flow harder to reason
about.
Additionally, I noticed that nvme_remove_head() has been removed in the
NVMe code that integrates with libmultipath. IMO, It might be clearer to
retain this function and make the teardown sequence explicit (after
removing mpath_put_head() from mpath_free_disk()).
For example:
nvme_remove_head():
mpath_unregister_disk(); /* removes mpath_disk and drops its ref */
mpath_put_head(); /* drops mpath_head reference */
nvme_put_ns_head(); /* drops NVMe namespace head reference */
Does the above example makes sense?
Thanks,
--Nilay
On 03/03/2026 12:39, Nilay Shroff wrote: >> >>> Yes we got a reference to mpath_head while >>> allocating it but then these are two (alloc mpath_disk and >>> alloc mpath_head) disjoint operations. In that case, can't >>> we have both mpath_disk and mpath_head allocated under one >>> libmultipath API? >> >> I would like to have something simpler (like mainline NVMe code), but >> I have it this way because of SCSI, as above. >> > I understand the intended lifetime model due to SCSI, but the current > flow is somewhat confusing. > > In nvme_mpath_alloc_disk(), mpath_disk and mpath_head are allocated > separately. However, during teardown, both objects are ultimately > released through mpath_free_disk(), which drops the reference to > mpath_head via mpath_put_head(). > > Since the allocation of mpath_disk and mpath_head happens independently, > it is not immediately obvious why their lifetime is tied together and > why they are not freed independently when the NVMe head node is removed. > This coupling makes the ownership and reference flow harder to reason > about. Yes, and also having 2x separate structures bloats the code, as we are continuously looking up one from another and so on. Only having a mpath_head for a nvme_ns_head would be nice - I'll look at this approach (again). > > Additionally, I noticed that nvme_remove_head() has been removed in the > NVMe code that integrates with libmultipath. IMO, It might be clearer to > retain this function and make the teardown sequence explicit (after > removing mpath_put_head() from mpath_free_disk()). > For example: > > nvme_remove_head(): > mpath_unregister_disk(); /* removes mpath_disk and drops its ref */ > mpath_put_head(); /* drops mpath_head reference */ > nvme_put_ns_head(); /* drops NVMe namespace head reference */ > > Does the above example makes sense? Yeah, something like that would be simpler. I just need to make it work for scsi :) My current implementation has it that the scsi_device manages the mpath_head and the scsi_disk manages the mpath_disk. Supporting a single structure for scsi is more complicated, as the lifetime of the scsi_disk is not tied to that of the scsi_device, i.e. we can do something like this: echo "8:0:0:0" > /sys/bus/scsi/drivers/sd/unbind Which removes the scsi_disk, but the scsi_device remains. So, for multipath support, doing this would mean the that mpath_disk would be removed (if the last path), but not the mpath_head. Furthermore, the scsi_disk info is private to sd.c, so this would mean that the mpath_disk could/should also be private. My point is that scsi makes things more complicated. Thanks for checking!
On Wed, Feb 25, 2026 at 03:32:14PM +0000, John Garry wrote:
> Add support to allocate and free a multipath gendisk.
>
> NVMe has almost like-for-like equivalents here:
> - mpath_alloc_head_disk() -> nvme_mpath_alloc_disk()
> - multipath_partition_scan_work() -> nvme_partition_scan_work()
> - mpath_remove_disk() -> nvme_remove_head()
> - mpath_device_set_live() -> nvme_mpath_set_live()
>
> struct mpath_head_template is introduced as a method for drivers to
> provide custom multipath functionality.
>
> Signed-off-by: John Garry <john.g.garry@oracle.com>
> ---
> +
> +void mpath_device_set_live(struct mpath_disk *mpath_disk,
> + struct mpath_device *mpath_device)
> +{
> + struct mpath_head *mpath_head = mpath_disk->mpath_head;
You're dereferencing mpath_disk here, before the check if it's NULL.
-Ben
> + int ret;
> +
> + if (!mpath_disk)
> + return;
> +
> + if (!test_and_set_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
> + dev_set_drvdata(disk_to_dev(mpath_disk->disk), mpath_disk);
> + ret = device_add_disk(mpath_disk->parent, mpath_disk->disk,
> + mpath_head->mpdt->device_groups);
> + if (ret) {
> + clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags);
> + return;
> + }
> + queue_work(mpath_wq, &mpath_disk->partition_scan_work);
> + }
> +}
> +EXPORT_SYMBOL_GPL(mpath_device_set_live);
> +
> struct mpath_head *mpath_alloc_head(void)
> {
> struct mpath_head *mpath_head;
> --
> 2.43.5
On 26/02/2026 02:16, Benjamin Marzinski wrote:
>> struct mpath_head_template is introduced as a method for drivers to
>> provide custom multipath functionality.
>>
>> Signed-off-by: John Garry<john.g.garry@oracle.com>
>> ---
>> +
>> +void mpath_device_set_live(struct mpath_disk *mpath_disk,
>> + struct mpath_device *mpath_device)
>> +{
>> + struct mpath_head *mpath_head = mpath_disk->mpath_head;
> You're dereferencing mpath_disk here, before the check if it's NULL.
>
> -Ben
>
>> + int ret;
>> +
>> + if (!mpath_disk)
Yeah, this NULL check is not needed.
Thanks!
© 2016 - 2026 Red Hat, Inc.