From nobody Fri Sep 25 14:31:50 2026 Received: from mailout1.samsung.com (mailout1.samsung.com [203.254.224.24]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 33EB0411FA1 for ; Fri, 11 Sep 2026 06:47:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=203.254.224.24 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789109255; cv=none; b=QIckncmB9bhQJGv/75xH3SA8/dABrRE127BN+E6+S18KQe2pxg/d/Oazb6HCItV5kkly1QllNonnqAemyr5neii5dZNGfjsW8cR3dWNG6M9sEn7lQf2bTvoJdBgT6GxdmM0TiBFpwgzH5so+CKZG0qRLLqlZp3IdNCt7dgJXCKU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789109255; c=relaxed/simple; bh=YHZcjxwKiAHIu8TghwLYCjOIi5LF+P22DFhT2hsVNGs=; h=Mime-Version:Subject:From:To:CC:In-Reply-To:Message-ID:Date: Content-Type:References; b=UNzK+7NjABQrfWBMv+HPPqL5Kk7lH016L0fuAjrDt/6rP3vk2ixYhKj7vtmqgJVWetT1Vg+9bdyVpKcyRnbNVrm4V84SxdfHNvqosRi+QvQqov+FG06REiA7pdx5NUWKOKOK4m2U9xVgz9mIOJ7tpAAI8JHbth5hHX3coYZbDiE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=Kw0Msg48; arc=none smtp.client-ip=203.254.224.24 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="Kw0Msg48" Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20260911064726epoutp017fd6ab66da3d19cd8535235935221b53~UMWGCFUKO1816818168epoutp01U for ; Fri, 11 Sep 2026 06:47:26 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20260911064726epoutp017fd6ab66da3d19cd8535235935221b53~UMWGCFUKO1816818168epoutp01U DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1789109246; bh=K2nPs1gd52O/bnO84GX6blGypq4FG75Urc/oLYdPXiM=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=Kw0Msg48Z0P7bpSzdityOTJEJoglzcNLJZHqX9t+7ShLtyhReYPtlNpnwUTtVmV89 WZZ4yCbVzmlyC7bR0dgA7vTCUddc/Nm7Vdpb0NJoTvW40HNzaOXINKtmc07hcs0MZM k0LKjPzeCaKfqAObIafWQe/akv8x0Ieln5FpdASs= Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p1.samsung.com (KnoxPortal) with ESMTPS id 20260911064726epcas2p11b632fcfe465765089eb970dcd8460bb~UMWFodZ1z0941409414epcas2p1W; Fri, 11 Sep 2026 06:47:26 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.209]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4hh4q20RX4z2SSKs; Fri, 11 Sep 2026 06:47:26 +0000 (GMT) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 Subject: [PATCH 1/2] block: allow write streams on partitions Reply-To: keosung.park@samsung.com Sender: Keoseong Park From: Keoseong Park To: "axboe@kernel.dk" , "hch@lst.de" , "kbusch@kernel.org" , "hare@suse.de" , Kanchan Joshi , Nitesh Shetty CC: Jinyoung Choi , Jeuk Kim , Yunjae Jo , "linux-block@vger.kernel.org" , "linux-kernel@vger.kernel.org" , SSDR Gost Dev X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b@epcms2p4> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20260911064725epcms2p7c3928bafd87fcddc72560e615c7ab8a0@epcms2p7> Date: Fri, 11 Sep 2026 15:47:25 +0900 X-CMS-MailID: 20260911064725epcms2p7c3928bafd87fcddc72560e615c7ab8a0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b References: <20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b@epcms2p4> bdev_max_write_streams() returns 0 for partitions, so nothing on top of a partition can use write streams. Passing the disk's streams through would not be correct either: two partitions sharing a stream would put unrelated data into the same reclaim unit. Give each partition its own subset of the disk's write streams, kept in a per-partition map in struct block_device. bdev_max_write_streams() reports the partition's count, so existing users work with partition-relative stream numbers (1..count), and blk_partition_remap() translates them to disk streams like it remaps sectors. Only REQ_OP_WRITE bios are translated, as that is the only operation that consumes a write stream. A write with a stream beyond the partition's count fails the bio; blkdev_direct_IO() rejects those before submission, so this only catches in-kernel submitters. Nothing assigns streams to partitions yet, so there is no functional change. The idea of translating the stream in blk_partition_remap() is from an earlier patch by Christoph Hellwig, which assigned a contiguous range of streams to each partition instead of a map. Link: https://lore.kernel.org/all/20241119121632.1225556-16-hch@lst.de/ Signed-off-by: Keoseong Park --- block/bdev.c | 1 + block/blk-core.c | 10 +++++++++- include/linux/blk_types.h | 3 +++ include/linux/blkdev.h | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/block/bdev.c b/block/bdev.c index cd8323083740..7306a4ef0a86 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -438,6 +438,7 @@ static void bdev_free_inode(struct inode *inode) =20 free_percpu(bdev->bd_stats); kfree(bdev->bd_meta_info); + kfree(bdev->bd_write_stream_map); security_bdev_free(bdev); =20 if (!bdev_is_partition(bdev)) { diff --git a/block/blk-core.c b/block/blk-core.c index 196bccf27f58..3b6f09b3943f 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -616,7 +616,8 @@ static inline int bio_check_eod(struct bio *bio) } =20 /* - * Remap block n of partition p to block n+start(p) of the disk. + * Remap block n of partition p to block n+start(p) of the disk, and the + * write streams of partition p to the disk write streams reserved for the= m. */ static int blk_partition_remap(struct bio *bio) { @@ -630,6 +631,13 @@ static int blk_partition_remap(struct bio *bio) bio->bi_iter.bi_sector - p->bd_start_sect); } + if (bio->bi_write_stream && bio_op(bio) =3D=3D REQ_OP_WRITE) { + if (unlikely(bio->bi_write_stream > p->bd_nr_write_streams)) + return -EINVAL; + + bio->bi_write_stream =3D + p->bd_write_stream_map[bio->bi_write_stream - 1]; + } bio_set_flag(bio, BIO_REMAPPED); return 0; } diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 98e21b4cbf32..0bbfa2708099 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -71,6 +71,9 @@ struct block_device { =20 struct partition_meta_info *bd_meta_info; int bd_writers; + /* partition write stream s is disk stream bd_write_stream_map[s - 1] */ + u8 bd_nr_write_streams; + u8 *bd_write_stream_map; #ifdef CONFIG_SECURITY void *bd_security; #endif diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 4f7905c3412b..686ed60a0bb7 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1303,7 +1303,7 @@ static inline unsigned int bdev_max_segments(struct b= lock_device *bdev) static inline unsigned short bdev_max_write_streams(struct block_device *b= dev) { if (bdev_is_partition(bdev)) - return 0; + return bdev->bd_nr_write_streams; return bdev_limits(bdev)->max_write_streams; } =20 --=20 2.43.0 From nobody Fri Sep 25 14:31:50 2026 Received: from mailout1.samsung.com (mailout1.samsung.com [203.254.224.24]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 06D2741D23B for ; Fri, 11 Sep 2026 06:48:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=203.254.224.24 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789109304; cv=none; b=ZftjhcIM2SitEKvTH0bF37SAZgzxcWRU8dL0ldL+uDjDdC8ytfxhz3yivfEUgF14VxzhVV4kDaCRZpRT5EAAwg37+toyp6cZ9lNj8J8jTtXUoD34hVqALpRSF4wIaEQxpWXFfb373VWp87eivkRtuYsNb+epTOMZSZPmJI5s1+4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789109304; c=relaxed/simple; bh=1Tw16ecFeY3trHefO0ZqGu0P4NYetUtW5mTvXs9+G8k=; h=Mime-Version:Subject:From:To:CC:In-Reply-To:Message-ID:Date: Content-Type:References; b=seJP9k+BI3IFzRcanzhUowE26ljVO1TEDOxtlrehWoeqw1X9G+RdR3+x5KGn15S1EhHhUh5SW4jntbSpArnH/nST8OlyNYATCLYeHD59r8k3r91rUm3wanYaF5AsO3xzsZAvyQYotlGGcWKQtPokhegCrROVF97kM9TU9kaIJLk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=eH1VHeMt; arc=none smtp.client-ip=203.254.224.24 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="eH1VHeMt" Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20260911064811epoutp01ecb955bb13a61dd90dd2fd4a20a699ea~UMWwDNTjO1935919359epoutp01V for ; Fri, 11 Sep 2026 06:48:11 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20260911064811epoutp01ecb955bb13a61dd90dd2fd4a20a699ea~UMWwDNTjO1935919359epoutp01V DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1789109291; bh=MJWGPirATsuvDBDEAquo3ZEpJ2LRuh/TvxwBOW6E+cQ=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=eH1VHeMtocuPF4aOydWcNAfzC+z3XOVkVwOjP4Jw3+iCTrC5CZLtG1Ef2+/O4fi6b 9kptne+HLD4Gl/MqywcJhZfRvClqYi93OpdClKZHwCs2Sgu9ZoNUZPtle9X0q7Xfq9 TerXyy588OK+zLha5AMH88J4NRfgPG1Ne6ShnBeQ= Received: from epsnrtp01.localdomain (unknown [182.195.42.153]) by epcas2p2.samsung.com (KnoxPortal) with ESMTPS id 20260911064811epcas2p26b635da1fcaa7ac5d7ea971bb6fecfe1~UMWvdf5b-1477414774epcas2p2I; Fri, 11 Sep 2026 06:48:11 +0000 (GMT) Received: from epcas2p3.samsung.com (unknown [182.195.38.207]) by epsnrtp01.localdomain (Postfix) with ESMTP id 4hh4qt5hRVz6B9mB; Fri, 11 Sep 2026 06:48:10 +0000 (GMT) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 Subject: [PATCH 2/2] block: add a sysfs interface to reserve write streams for a partition Reply-To: keosung.park@samsung.com Sender: Keoseong Park From: Keoseong Park To: "axboe@kernel.dk" , "hch@lst.de" , "kbusch@kernel.org" , "hare@suse.de" , Kanchan Joshi , Nitesh Shetty CC: Jinyoung Choi , Jeuk Kim , Yunjae Jo , "linux-block@vger.kernel.org" , "linux-kernel@vger.kernel.org" , SSDR Gost Dev X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b@epcms2p4> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20260911064810epcms2p602edf30b82d843295e88c391b9cd62e6@epcms2p6> Date: Fri, 11 Sep 2026 15:48:10 +0900 X-CMS-MailID: 20260911064810epcms2p602edf30b82d843295e88c391b9cd62e6 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b References: <20260911064529epcms2p4791af8edd7c24d1a44b24e77c95c3f0b@epcms2p4> Add /sys/block///write_streams. Writing N reserves N of the disk's write streams that no other partition can use, exposed as its streams 1..N. Writing 0 gives them back. N is capped at U8_MAX because bio->bi_write_stream is a u8, and asking for more streams than the disk has left fails with -ENOSPC. The IDs come from a per-disk bitmap protected by open_mutex and are released when the partition is removed, including by a partition table rescan. I/O to the whole-disk device can use any write stream, just as it can write to any sector of a partition. The count can only be changed while the partition is not open, the same rule as for a partition table change: users see a fixed number of streams from open to close, and no I/O can be in flight while the map is replaced. Writing the current value is always allowed so that a rule that sets it can be re-run. Growing or shrinking keeps the streams that stay, so that a partition keeps writing through the same disk streams and its data stays together. The store must not wait for open_mutex while holding the sysfs active reference, as drop_partition() removes the attribute with open_mutex held. Break the active protection first and re-check that the partition still exists under the mutex. Nothing changes until the file is written. Signed-off-by: Keoseong Park --- Documentation/ABI/stable/sysfs-block | 19 ++++ block/partitions/core.c | 128 +++++++++++++++++++++++++++ include/linux/blkdev.h | 2 + 3 files changed, 149 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-block b/Documentation/ABI/stabl= e/sysfs-block index 900b3fc4c72d..442f4c221bde 100644 --- a/Documentation/ABI/stable/sysfs-block +++ b/Documentation/ABI/stable/sysfs-block @@ -218,6 +218,25 @@ Description: same as the format of /sys/block//stat. =20 =20 +What: /sys/block///write_streams +Date: September 2026 +Contact: Keoseong Park +Description: + [RW] Number of the disk's write streams reserved for the + partition. Writing N reserves N disk write streams that no + other partition can use and exposes them as the partition's + write streams 1 to N. Writing 0 gives them back. At most + /sys/block//queue/max_write_streams streams, and never + more than 255, can be reserved on a disk. The number can + only be changed while the partition is not open, and a change + keeps the stream numbers that survive it pointing at the same + disk write streams. Partitions have no write streams unless + reserved here, and the reservation is lost when the partition + is removed, including by a partition table rescan. I/O to + the whole-disk device can use any write stream, just as it + can write to any sector of a partition. + + What: /sys/block//queue/add_random Date: June 2010 Contact: linux-block@vger.kernel.org diff --git a/block/partitions/core.c b/block/partitions/core.c index b5c59b79ca7c..77852eb2acbe 100644 --- a/block/partitions/core.c +++ b/block/partitions/core.c @@ -205,6 +205,130 @@ static ssize_t part_discard_alignment_show(struct dev= ice *dev, return sysfs_emit(buf, "%u\n", bdev_discard_alignment(dev_to_bdev(dev))); } =20 +static ssize_t part_write_streams_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", dev_to_bdev(dev)->bd_nr_write_streams); +} + +/* + * Give the write streams of @part back to the disk. Called from + * drop_partition() with open_mutex held; the map is left alone because a + * write racing with del_gendisk() still uses it until the bdev is freed. + */ +static void part_put_write_streams(struct block_device *part) +{ + struct gendisk *disk =3D part->bd_disk; + unsigned int i; + + lockdep_assert_held(&disk->open_mutex); + + for (i =3D 0; i < part->bd_nr_write_streams; i++) + __clear_bit(part->bd_write_stream_map[i], + disk->write_streams_reserved); +} + +static int part_set_write_streams(struct block_device *part, u8 nr) +{ + struct gendisk *disk =3D part->bd_disk; + unsigned int max =3D min_t(unsigned int, + bdev_limits(part)->max_write_streams, U8_MAX); + u8 *map =3D NULL; + unsigned int i, id, nr_free; + int ret =3D 0; + u8 cur; + + if (nr > max) + return -EINVAL; + + if (nr) { + map =3D kmalloc(nr, GFP_KERNEL); + if (!map) + return -ENOMEM; + } + + mutex_lock(&disk->open_mutex); + + /* the partition may have been dropped while waiting for the mutex */ + if (xa_load(&disk->part_tbl, bdev_partno(part)) !=3D part) { + ret =3D -ENXIO; + goto out; + } + + cur =3D part->bd_nr_write_streams; + + /* a no-op change is allowed while open */ + if (nr =3D=3D cur) + goto out; + + /* the streams must not change under a user of the partition */ + if (atomic_read(&part->bd_openers)) { + ret =3D -EBUSY; + goto out; + } + + nr_free =3D max - bitmap_weight(disk->write_streams_reserved, max + 1); + if (nr > cur && nr - cur > nr_free) { + ret =3D -ENOSPC; + goto out; + } + + /* + * Keep the streams that stay, so that a partition keeps writing + * through the same disk streams and its data stays together. + */ + for (i =3D 0; i < min(cur, nr); i++) + map[i] =3D part->bd_write_stream_map[i]; + + if (nr < cur) { + for (i =3D nr; i < cur; i++) + __clear_bit(part->bd_write_stream_map[i], + disk->write_streams_reserved); + } else { + for (i =3D cur; i < nr; i++) { + id =3D find_next_zero_bit(disk->write_streams_reserved, + max + 1, 1); + __set_bit(id, disk->write_streams_reserved); + map[i] =3D id; + } + } + + swap(part->bd_write_stream_map, map); + part->bd_nr_write_streams =3D nr; + +out: + mutex_unlock(&disk->open_mutex); + kfree(map); + + return ret; +} + +static ssize_t part_write_streams_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kernfs_node *kn; + u8 nr; + int ret; + + ret =3D kstrtou8(buf, 10, &nr); + if (ret) + return ret; + + /* + * drop_partition() removes this attribute with open_mutex held, so + * don't hold the active reference while waiting for the mutex. + */ + kn =3D sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) + return -ENXIO; + + ret =3D part_set_write_streams(dev_to_bdev(dev), nr); + sysfs_unbreak_active_protection(kn); + + return ret ? ret : count; +} + static DEVICE_ATTR(partition, 0444, part_partition_show, NULL); static DEVICE_ATTR(start, 0444, part_start_show, NULL); static DEVICE_ATTR(size, 0444, part_size_show, NULL); @@ -213,6 +337,8 @@ static DEVICE_ATTR(alignment_offset, 0444, part_alignme= nt_offset_show, NULL); static DEVICE_ATTR(discard_alignment, 0444, part_discard_alignment_show, N= ULL); static DEVICE_ATTR(stat, 0444, part_stat_show, NULL); static DEVICE_ATTR(inflight, 0444, part_inflight_show, NULL); +static DEVICE_ATTR(write_streams, 0644, part_write_streams_show, + part_write_streams_store); #ifdef CONFIG_FAIL_MAKE_REQUEST static struct device_attribute dev_attr_fail =3D __ATTR(make-it-fail, 0644, part_fail_show, part_fail_store); @@ -227,6 +353,7 @@ static struct attribute *part_attrs[] =3D { &dev_attr_discard_alignment.attr, &dev_attr_stat.attr, &dev_attr_inflight.attr, + &dev_attr_write_streams.attr, #ifdef CONFIG_FAIL_MAKE_REQUEST &dev_attr_fail.attr, #endif @@ -274,6 +401,7 @@ void drop_partition(struct block_device *part) { lockdep_assert_held(&part->bd_disk->open_mutex); =20 + part_put_write_streams(part); xa_erase(&part->bd_disk->part_tbl, bdev_partno(part)); kobject_put(part->bd_holder_dir); =20 diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 686ed60a0bb7..5d241d7b0802 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -178,6 +178,8 @@ struct gendisk { =20 struct mutex open_mutex; /* open/close mutex */ unsigned open_partitions; /* number of open partitions */ + /* reserved by partitions, bit N =3D stream N (0 unused), open_mutex */ + DECLARE_BITMAP(write_streams_reserved, U8_MAX + 1); =20 struct backing_dev_info *bdi; struct kobject queue_kobj; /* the queue/ directory */ --=20 2.43.0