* [PATCH RFC v2 1/2] block: scan partitions for hidden disks
2026-08-18 12:08 [PATCH RFC v2 0/2] fix NVMe multipath partition diskstats John Garry
@ 2026-08-18 12:08 ` John Garry
2026-08-19 9:00 ` Christoph Hellwig
2026-08-18 12:08 ` [PATCH RFC v2 2/2] nvme-multipath: fix diskstats for partitions John Garry
1 sibling, 1 reply; 7+ messages in thread
From: John Garry @ 2026-08-18 12:08 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: linux-block, linux-nvme, John Garry, John Garry
From: John Garry <john.garry@linux.dev>
If a disk is hidden (GENHD_FL_HIDDEN flags set), we currently do not scan
the disk partition table.
GENHD_FL_HIDDEN is used in NVMe multipath support to hide the per-path
disk.
However, it would be useful there to actually have the per-path disk
partition table available for situations where we want to send bios to
a specific per-path disk partition.
Change GENHD_FL_HIDDEN to scan partitions. For anyone wanting to avoid
scanning the partition, flag GENHD_FL_NO_PART can still be used.
In diskstats_show(), don't show per-path partitions as this info is not
too interesting and can just bloat the output.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/genhd.c | 20 ++++++++++----------
block/partitions/core.c | 5 +++--
drivers/nvme/host/core.c | 4 ----
include/linux/blkdev.h | 2 +-
4 files changed, 14 insertions(+), 17 deletions(-)
diff --git a/block/genhd.c b/block/genhd.c
index 30ac0ffe65174..05301ee5d4ca5 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -406,15 +406,15 @@ static void add_disk_final(struct gendisk *disk)
{
struct device *ddev = disk_to_dev(disk);
- if (!(disk->flags & GENHD_FL_HIDDEN)) {
- /* Make sure the first partition scan will be proceed */
- if (get_capacity(disk) && disk_has_partscan(disk))
- set_bit(GD_NEED_PART_SCAN, &disk->state);
+ /* Make sure the first partition scan will be proceed */
+ if (get_capacity(disk) && disk_has_partscan(disk))
+ set_bit(GD_NEED_PART_SCAN, &disk->state);
- bdev_add(disk->part0, ddev->devt);
- if (get_capacity(disk))
- disk_scan_partitions(disk, BLK_OPEN_READ);
+ bdev_add(disk->part0, ddev->devt);
+ if (get_capacity(disk))
+ disk_scan_partitions(disk, BLK_OPEN_READ);
+ if (!(disk->flags & GENHD_FL_HIDDEN)) {
/*
* Announce the disk and partitions after all partitions are
* created. (for hidden disks uevents remain suppressed forever)
@@ -491,8 +491,7 @@ static int __add_disk(struct device *parent, struct gendisk *disk,
dev_set_name(ddev, "%s", disk->disk_name);
if (fwnode)
device_set_node(ddev, fwnode);
- if (!(disk->flags & GENHD_FL_HIDDEN))
- ddev->devt = MKDEV(disk->major, disk->first_minor);
+ ddev->devt = MKDEV(disk->major, disk->first_minor);
ret = device_add(ddev);
if (ret)
goto out_free_ext_minor;
@@ -1368,7 +1367,8 @@ static int diskstats_show(struct seq_file *seqf, void *v)
rcu_read_lock();
xa_for_each(&gp->part_tbl, idx, hd) {
- if (bdev_is_partition(hd) && !bdev_nr_sectors(hd))
+ if (bdev_is_partition(hd) &&
+ (!bdev_nr_sectors(hd) ||(gp->flags & GENHD_FL_HIDDEN)))
continue;
inflight = bdev_count_inflight(hd);
diff --git a/block/partitions/core.c b/block/partitions/core.c
index b5c59b79ca7cb..a48896c6c791d 100644
--- a/block/partitions/core.c
+++ b/block/partitions/core.c
@@ -152,7 +152,8 @@ static struct parsed_partitions *check_partition(struct gendisk *hd)
}
if (res > 0) {
- printk(KERN_INFO "%s", seq_buf_str(&state->pp_buf));
+ if (!(hd->flags & GENHD_FL_HIDDEN))
+ printk(KERN_INFO "%s", seq_buf_str(&state->pp_buf));
kfree(state->pp_buf.buffer);
return state;
@@ -164,7 +165,7 @@ static struct parsed_partitions *check_partition(struct gendisk *hd)
*/
if (err)
res = err;
- if (res) {
+ if (res && !(hd->flags & GENHD_FL_HIDDEN)) {
seq_buf_puts(&state->pp_buf,
" unable to read partition table\n");
printk(KERN_INFO "%s", seq_buf_str(&state->pp_buf));
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 1322c678f4eb8..ed21f4ef36c9a 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -1808,10 +1808,6 @@ static void nvme_enable_aen(struct nvme_ctrl *ctrl)
static int nvme_ns_open(struct nvme_ns *ns)
{
-
- /* should never be called due to GENHD_FL_HIDDEN */
- if (WARN_ON_ONCE(nvme_ns_head_multipath(ns->head)))
- goto fail;
if (!nvme_get_ns(ns))
goto fail;
if (!try_module_get(ns->ctrl->ops->module))
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 9213a5716f95a..a270cf1394c7a 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -260,7 +260,7 @@ static inline unsigned int disk_openers(struct gendisk *disk)
*/
static inline bool disk_has_partscan(struct gendisk *disk)
{
- return !(disk->flags & (GENHD_FL_NO_PART | GENHD_FL_HIDDEN)) &&
+ return !(disk->flags & GENHD_FL_NO_PART) &&
!test_bit(GD_SUPPRESS_PART_SCAN, &disk->state);
}
--
2.43.7
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH RFC v2 2/2] nvme-multipath: fix diskstats for partitions
2026-08-18 12:08 [PATCH RFC v2 0/2] fix NVMe multipath partition diskstats John Garry
2026-08-18 12:08 ` [PATCH RFC v2 1/2] block: scan partitions for hidden disks John Garry
@ 2026-08-18 12:08 ` John Garry
1 sibling, 0 replies; 7+ messages in thread
From: John Garry @ 2026-08-18 12:08 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: linux-block, linux-nvme, John Garry, John Garry
From: John Garry <john.garry@linux.dev>
Currently diskstats for partitions are never updated:
$ ./fio_read nvme1n1p1 # run traffic on /dev/nvme1n1p1
...
$ more /proc/diskstats | grep nvme1
259 2 nvme1c1n1 49857 0 400344 768565 0 0 0 0 0 2334 768565 0 0 0 0 0 0
259 3 nvme1n1 99710 0 800680 1599285 0 0 0 0 0 2346 1599285 0 0 0 0 0 0
259 5 nvme1n1p1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
259 4 nvme1c2n1 49853 0 400336 831472 0 0 0 0 0 2315 831472 0 0 0 0 0 0
This is because we only ever update the diskstats for the multipath disk in
nvme_mpath_end_request(), and we never take into account that the original
bi_bdev may been a partition of this disk.
Functions bdev_start_io_acct() and bdev_start_io_acct() do handle
updating diskstats for a partition, in that they also update the whole
disk also (if a partition), so use the partition (if applicable) when
calling those functions.
Change any functionality which used to lookup the gendisk part0 to now
lookup the specific gendisk partition.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/nvme/host/multipath.c | 31 +++++++++++++++++++++++++------
1 file changed, 25 insertions(+), 6 deletions(-)
diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
index 75dbb58286a32..5c95a2ab7755f 100644
--- a/drivers/nvme/host/multipath.c
+++ b/drivers/nvme/host/multipath.c
@@ -144,6 +144,19 @@ void nvme_mpath_start_freeze(struct nvme_subsystem *subsys)
blk_freeze_queue_start(h->disk->queue);
}
+static struct block_device *nvme_to_disk_part(struct gendisk *disk, u8 partno)
+{
+ struct block_device *bdev;
+
+ /* Quick lookup for whole disk */
+ if (!partno)
+ return disk->part0;
+ rcu_read_lock();
+ bdev = xa_load(&disk->part_tbl, partno);
+ rcu_read_unlock();
+ return bdev;
+}
+
void nvme_failover_req(struct request *req)
{
struct nvme_ns *ns = req->q->queuedata;
@@ -165,8 +178,10 @@ void nvme_failover_req(struct request *req)
}
spin_lock_irqsave(&ns->head->requeue_lock, flags);
- for (bio = req->bio; bio; bio = bio->bi_next)
- bio_set_dev(bio, ns->head->disk->part0);
+ for (bio = req->bio; bio; bio = bio->bi_next) {
+ bio_set_dev(bio, nvme_to_disk_part(ns->head->disk,
+ bdev_partno(bio->bi_bdev)));
+ }
blk_steal_bios(&ns->head->requeue_list, req);
spin_unlock_irqrestore(&ns->head->requeue_lock, flags);
@@ -194,8 +209,9 @@ void nvme_mpath_start_request(struct request *rq)
return;
nvme_req(rq)->flags |= NVME_MPATH_IO_STATS;
- nvme_req(rq)->start_time = bdev_start_io_acct(disk->part0, req_op(rq),
- jiffies);
+ nvme_req(rq)->start_time = bdev_start_io_acct(
+ nvme_to_disk_part(disk, bdev_partno(rq->part)),
+ req_op(rq), jiffies);
}
EXPORT_SYMBOL_GPL(nvme_mpath_start_request);
@@ -208,7 +224,8 @@ void nvme_mpath_end_request(struct request *rq)
if (!(nvme_req(rq)->flags & NVME_MPATH_IO_STATS))
return;
- bdev_end_io_acct(ns->head->disk->part0, req_op(rq),
+ bdev_end_io_acct(nvme_to_disk_part(ns->head->disk,
+ bdev_partno(rq->part)), req_op(rq),
blk_rq_bytes(rq) >> SECTOR_SHIFT,
nvme_req(rq)->start_time);
}
@@ -549,7 +566,9 @@ static void nvme_ns_head_submit_bio(struct bio *bio)
srcu_idx = srcu_read_lock(&head->srcu);
ns = nvme_find_path(head);
if (likely(ns)) {
- bio_set_dev(bio, ns->disk->part0);
+ bio_set_dev(bio, nvme_to_disk_part(ns->disk,
+ bdev_partno(bio->bi_bdev)));
+
/*
* Use BIO_REMAPPED to skip bio_check_eod() when this bio
* enters submit_bio_noacct() for the per-path device. The EOD
--
2.43.7
^ permalink raw reply related [flat|nested] 7+ messages in thread