Linux block layer
 help / color / mirror / Atom feed
* Re: [PATCH blktests] Fix _get_page_size()
From: Bart Van Assche @ 2026-06-20  3:55 UTC (permalink / raw)
  To: Shin'ichiro Kawasaki, Jeff Moyer; +Cc: linux-block, osandov, kch
In-Reply-To: <ajXmBu9lDZwgMG7_@shinmob>

On 6/20/26 3:26 AM, Shin'ichiro Kawasaki wrote:
> This is a rather fundamental change, so I would like to ask opinions from
> other blktests users, especially Omar and Chaitanya. What do you think about
> the idea to add getconf to the requirement list?

CONFIG_PAGE_SHIFT was introduced in the Linux kernel in February 2024
(commit ba89f9c8ccba ("arch: consolidate existing CONFIG_PAGE_SIZE_*KB
definitions")). Older kernels had CONFIG_PAGE_SIZE_4KB,
CONFIG_PAGE_SIZE_16KB, etc. This means that it is possible to derive the
kernel page size from the kernel configuration file for all upstream and
distro kernels, isn't it?

Thanks,

Bart.

^ permalink raw reply

* Re: [PATCH blktests] Fix _get_page_size()
From: Shin'ichiro Kawasaki @ 2026-06-20  1:26 UTC (permalink / raw)
  To: Jeff Moyer; +Cc: linux-block, osandov, kch
In-Reply-To: <x497bnvlxlc.fsf@segfault.usersys.redhat.com>

CC+: Omar, Chaitanya,

On Jun 18, 2026 / 10:41, Jeff Moyer wrote:
> There is no CONFIG_PAGE_SHIFT stored in /boot/config-`uname -r` on RHEL
> systems (maybe all systems?).  As a result, tests that make use of
> _get_page_size() were doing the wrong thing.  For example, throtl/002
> used it to calculate I/O sizes for direct IO.  Those sizes ended up not
> being a multiple of the logical block size, and hence throtl/002 was
> failing.
> 
> Fixes: 8eca9fa ("common/rc, scsi/011, zbd/010: introduce _page_size_equals() helper")
> Signed-off-by: Jeff Moyer <jmoyer@redhat.com>

Thanks for finding this out. When I applied the commit in the Fixes tag, I had
checked my Fedora system's /boot/config-* files and had found CONFIG_PAGE_SHIFT
defined. I wanted to avoid the dependency to getconf, and chose the way to rely
on CONFIG_PAGE_SHIFT. But that is not an option for other distros. Today I
checked my Debian system, and CONFIG_PAGE_SHIFT was not defined either. Now I
see that we should not use CONFIG_PAGE_SHIFT.

> 
> diff --git a/common/rc b/common/rc
> index 20f7c7a..d60a125 100644
> --- a/common/rc
> +++ b/common/rc
> @@ -562,13 +562,8 @@ _have_systemctl_unit() {
>  	return 0
>  }
>  
> -# Get system page size from kernel conguration
>  _get_page_size() {
> -	local page_shift
> -
> -	page_shift=$(_get_kernel_option PAGE_SHIFT)
> -
> -	echo $((1<< page_shift))
> +	getconf PAGE_SIZE
>  }
>  
>  # Check if the system page size matches the required size (in bytes).
> 

The patch above should work, but it creates a new dependeny on the tool getconf.
There are 6 test cases that require page size and getconf. Then, we need to
check that getconf command is available for the test cases.

  $ git grep _page_size
  common/rc:_get_page_size() {
  common/rc:# Example: _page_size_equals 4096
  common/rc:_page_size_equals() {
  common/rc:      current_size=$(_get_page_size)
  tests/scsi/011: _page_size_equals 4096
  tests/throtl/002:       page_size=$(_get_page_size)
  tests/throtl/003:       page_size=$(_get_page_size)
  tests/throtl/007:       page_size=$(_get_page_size)
  tests/zbd/010:  _page_size_equals 4096
  tests/zbd/014:  page_size=$(_get_page_size)

Havind said that, I think now Linux eco-system is in the phase to add variety
of page sizes, and I expect more test cases that depend on page sizes will be
added in near future. So, this could be the good timing to add getconf to the
blktests minimal requirement list described in README.md. This means that
blktests users will need to install glibc-common package for Fedora, or
libc-bin package for Debian.

This is a rather fundamental change, so I would like to ask opinions from
other blktests users, especially Omar and Chaitanya. What do you think about
the idea to add getconf to the requirement list?

^ permalink raw reply

* Re: [PATCH v4] loop: Fix NULL pointer dereference in lo_rw_aio()
From: Tetsuo Handa @ 2026-06-19 14:33 UTC (permalink / raw)
  To: Al Viro
  Cc: Jens Axboe, Bart Van Assche, Christoph Hellwig, Damien Le Moal,
	Ming Lei, linux-block, LKML, Andrew Morton, Linus Torvalds,
	linux-btrfs, David Sterba, linux-fsdevel, Christian Brauner,
	Hillf Danton
In-Reply-To: <9f8b5ab0-efbc-4cf3-a1f8-b43377416946@I-love.SAKURA.ne.jp>

Sending this commit to linux.git will be the fastest way to identify who is issuing
I/O requests too late. Therefore, I want to get a conclusion on xfs/259 breakage.
Al, can you get the same result?

On 2026/06/13 20:00, Tetsuo Handa wrote:
> On 2026/06/10 2:50, Al Viro wrote:
>> Still breaks xfs/259, same as the version in next-20260605...
> 
> I installed xfstests-dev and reproduced a "umount: /home/test: target is busy." problem which Al Viro is
> experiencing with https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?h=next-20260608&id=fb1d5846e99c8aa4ce8da7e6ee7643b01da25b8c .
> 
(...snipped...)
> 
> I initially suspected that the cause of "target is busy" error is that fput() from
> __loop_clr_fd() does not wait for completion before "losetup -d" completes. But a
> debug printk() patch shown below indicated a tendency:
> 
>   (a) __loop_clr_fd() is called by "udev-worker" rather than "losetup" when this problem happens
> 
>   (b) propagate_mount_busy()!=0 when do_umount() fails with -EBUSY
> 
> --------------------------------------------------------------------------------
> diff --git a/drivers/block/loop.c b/drivers/block/loop.c
> index c3b607a3ddc4..7408f314a1fa 100644
> --- a/drivers/block/loop.c
> +++ b/drivers/block/loop.c
> @@ -1763,6 +1763,8 @@ static void lo_release(struct gendisk *disk)
>  	mutex_unlock(&lo->lo_mutex);
>  
>  	if (need_clear) {
> +		printk("Flush: task=%s[%d] dev=loop%d state=%d\n",
> +		       current->comm, current->pid, lo->lo_number, lo->lo_state);
>  		/*
>  		 * Temporarily release disk->open_mutex in order to flush pending I/O
>  		 * requests before clearing the backing device.
> @@ -1813,6 +1815,8 @@ static void lo_release(struct gendisk *disk)
>  		mutex_lock(&lo->lo_disk->open_mutex);
>  		if (WARN_ON(data_race(READ_ONCE(lo->lo_state)) != Lo_rundown))
>  			return;
> +		printk("Teardown: task=%s[%d] dev=loop%d state=%d\n",
> +		       current->comm, current->pid, lo->lo_number, lo->lo_state);
>  		__loop_clr_fd(lo);
>  	}
>  }
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 09ab7fc72f86..9710460fb449 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -1893,6 +1893,8 @@ static int do_umount(struct mount *mnt, int flags)
>  		 */
>  		lock_mount_hash();
>  		if (!list_empty(&mnt->mnt_mounts) || mnt_get_count(mnt) != 2) {
> +			printk("%s: task=%s[%d] !list_empty(&mnt->mnt_mounts)=%d mnt_get_count(mnt)=%d\n", __func__,
> +			       current->comm, current->pid, !list_empty(&mnt->mnt_mounts), mnt_get_count(mnt));
>  			unlock_mount_hash();
>  			return -EBUSY;
>  		}
> @@ -1960,6 +1962,9 @@ static int do_umount(struct mount *mnt, int flags)
>  		if (!propagate_mount_busy(mnt, 2)) {
>  			umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
>  			retval = 0;
> +		} else {
> +			printk("%s: task=%s[%d] propagate_mount_busy()!=0\n", __func__,
> +			       current->comm, current->pid);
>  		}
>  	}
>  out:
> --------------------------------------------------------------------------------


^ permalink raw reply

* Re: [PATCH v3] block: assign caller-specific lockdep class to disk->open_mutex
From: Andreas Hindborg @ 2026-06-19 13:16 UTC (permalink / raw)
  To: Tetsuo Handa, Christoph Hellwig, Bart Van Assche, Jens Axboe
  Cc: linux-block, LKML, Andrew Morton, Ming Lei, Damien Le Moal,
	Qu Wenruo, Hillf Danton, Miguel Ojeda
In-Reply-To: <87y0gaekyb.fsf@t14s.mail-host-address-is-not-set>

Andreas Hindborg <a.hindborg@kernel.org> writes:

> "Tetsuo Handa" <penguin-kernel@I-love.SAKURA.ne.jp> writes:
>
>> On 2026/06/19 18:49, Andreas Hindborg wrote:
>>>> My understanding is that we don't have infrastructure for lock class keys
>>>> that can be applied to
>>>>
>>>>   +struct gendisk_lkclass {
>>>>   +	struct lock_class_key bio_lkclass;
>>>>   +	struct lock_class_key open_mutex_lkclass;
>>>>   +};
>>>>
>>>>   -	static struct lock_class_key __key;
>>>>   +	static struct gendisk_lkclass __key;
>>>>
>>>> change. Alternative approach is welcomed if you have one.
>>>
>>> Sorry, I did not pay enough attention. I would suggest this approach:
>>
>> I'm OK with your module-specific lockdep class approach
>>
>>> @@ -164,14 +127,21 @@ pub fn build<T: Operations>(
>>>              lim.features = bindings::BLK_FEAT_ROTATIONAL;
>>>          }
>>>
>>> -        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
>>> +        let keys = KBox::pin_init(
>>> +            Opaque::ffi_init(|ptr: *mut bindings::gendisk_lkclass| {
>>> +                // SAFETY: `ptr` is valid for writes
>>> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).bio_lkclass) };
>>> +                // SAFETY: `ptr` is valid for writes
>>> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).open_mutex_lkclass) };
>>> +            }),
>>> +            GFP_KERNEL,
>>> +        )?;
>>> +
>>> +        // SAFETY:
>>> +        // - `tagset.raw_tag_set()` points to a valid and initialized tag set.
>>> +        // - We keep `keys` alive for the lifetime of the returned gendisk.
>>>          let gendisk = from_err_ptr(unsafe {
>>> -            bindings::__blk_mq_alloc_disk(
>>> -                tagset.raw_tag_set(),
>>> -                &mut lim,
>>> -                data,
>>> -                lkclass.as_ptr(),
>>> -            )
>>> +            bindings::__blk_mq_alloc_disk(tagset.raw_tag_set(), &mut lim, data, keys.get())
>>>          })?;
>>>
>>>          const TABLE: bindings::block_device_operations = bindings::block_device_operations {
>>
>> if we can assume that there is (up to) only one
>>
>>   let mut disk = gen_disk::GenDiskBuilder::new().capacity_sectors(4096).build(fmt!("myblk"), tagset, ())?;
>>
>> call for each rust module.
>
> With the approach I suggest here, we get a new set of keys for each
> invocation of `GenDiskBuilder::build`. No statics involved, keys are
> allocated dynamically.
>
> The current code makes one key for to be shared by _all_ callers, which
> is probably a bad idea.
>
>>
>> By the way, are you aware that rust-enabled linux-next build is recently failing
>> ( https://syzkaller.appspot.com/bug?extid=1f14a35d0c73d31555e4 ) ?
>
> I was not aware, thanks for pointing that out. I actually did build
> linux-next for both June 16 and June 18 and I did not see any issue. It
> looks like a tooling problem to me. I'll see if I can figure out what it
> is.

I think this is [1]. We saw it for lkp when they used bindgen 0.72.0
with clang 22 I think. Probably syzkaller needs to bump bindgen version
to 0.72.2.

Cc: Miguel Ojeda <ojeda@kernel.org>

Not sure who to Cc on syzkaller side.

Best regards,
Andreas Hindborg


[1] https://github.com/rust-lang/rust-bindgen/issues/3264


^ permalink raw reply

* Re: [PATCH v3] block: assign caller-specific lockdep class to disk->open_mutex
From: Andreas Hindborg @ 2026-06-19 13:09 UTC (permalink / raw)
  To: Tetsuo Handa, Christoph Hellwig, Bart Van Assche, Jens Axboe
  Cc: linux-block, LKML, Andrew Morton, Ming Lei, Damien Le Moal,
	Qu Wenruo, Hillf Danton, Miguel Ojeda
In-Reply-To: <4d405942-9932-48b0-bdc0-9744d48fe699@I-love.SAKURA.ne.jp>

"Tetsuo Handa" <penguin-kernel@I-love.SAKURA.ne.jp> writes:

> On 2026/06/19 18:49, Andreas Hindborg wrote:
>>> My understanding is that we don't have infrastructure for lock class keys
>>> that can be applied to
>>>
>>>   +struct gendisk_lkclass {
>>>   +	struct lock_class_key bio_lkclass;
>>>   +	struct lock_class_key open_mutex_lkclass;
>>>   +};
>>>
>>>   -	static struct lock_class_key __key;
>>>   +	static struct gendisk_lkclass __key;
>>>
>>> change. Alternative approach is welcomed if you have one.
>>
>> Sorry, I did not pay enough attention. I would suggest this approach:
>
> I'm OK with your module-specific lockdep class approach
>
>> @@ -164,14 +127,21 @@ pub fn build<T: Operations>(
>>              lim.features = bindings::BLK_FEAT_ROTATIONAL;
>>          }
>>
>> -        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
>> +        let keys = KBox::pin_init(
>> +            Opaque::ffi_init(|ptr: *mut bindings::gendisk_lkclass| {
>> +                // SAFETY: `ptr` is valid for writes
>> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).bio_lkclass) };
>> +                // SAFETY: `ptr` is valid for writes
>> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).open_mutex_lkclass) };
>> +            }),
>> +            GFP_KERNEL,
>> +        )?;
>> +
>> +        // SAFETY:
>> +        // - `tagset.raw_tag_set()` points to a valid and initialized tag set.
>> +        // - We keep `keys` alive for the lifetime of the returned gendisk.
>>          let gendisk = from_err_ptr(unsafe {
>> -            bindings::__blk_mq_alloc_disk(
>> -                tagset.raw_tag_set(),
>> -                &mut lim,
>> -                data,
>> -                lkclass.as_ptr(),
>> -            )
>> +            bindings::__blk_mq_alloc_disk(tagset.raw_tag_set(), &mut lim, data, keys.get())
>>          })?;
>>
>>          const TABLE: bindings::block_device_operations = bindings::block_device_operations {
>
> if we can assume that there is (up to) only one
>
>   let mut disk = gen_disk::GenDiskBuilder::new().capacity_sectors(4096).build(fmt!("myblk"), tagset, ())?;
>
> call for each rust module.

With the approach I suggest here, we get a new set of keys for each
invocation of `GenDiskBuilder::build`. No statics involved, keys are
allocated dynamically.

The current code makes one key for to be shared by _all_ callers, which
is probably a bad idea.

>
> By the way, are you aware that rust-enabled linux-next build is recently failing
> ( https://syzkaller.appspot.com/bug?extid=1f14a35d0c73d31555e4 ) ?

I was not aware, thanks for pointing that out. I actually did build
linux-next for both June 16 and June 18 and I did not see any issue. It
looks like a tooling problem to me. I'll see if I can figure out what it
is.


Best regards,
Andreas Hindborg



^ permalink raw reply

* Re: [PATCH v3] block: assign caller-specific lockdep class to disk->open_mutex
From: Tetsuo Handa @ 2026-06-19 12:19 UTC (permalink / raw)
  To: Andreas Hindborg, Christoph Hellwig, Bart Van Assche, Jens Axboe
  Cc: linux-block, LKML, Andrew Morton, Ming Lei, Damien Le Moal,
	Qu Wenruo, Hillf Danton, Miguel Ojeda
In-Reply-To: <87a4sqg8qh.fsf@t14s.mail-host-address-is-not-set>

On 2026/06/19 18:49, Andreas Hindborg wrote:
>> My understanding is that we don't have infrastructure for lock class keys
>> that can be applied to
>>
>>   +struct gendisk_lkclass {
>>   +	struct lock_class_key bio_lkclass;
>>   +	struct lock_class_key open_mutex_lkclass;
>>   +};
>>
>>   -	static struct lock_class_key __key;
>>   +	static struct gendisk_lkclass __key;
>>
>> change. Alternative approach is welcomed if you have one.
> 
> Sorry, I did not pay enough attention. I would suggest this approach:

I'm OK with your module-specific lockdep class approach

> @@ -164,14 +127,21 @@ pub fn build<T: Operations>(
>              lim.features = bindings::BLK_FEAT_ROTATIONAL;
>          }
>  
> -        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
> +        let keys = KBox::pin_init(
> +            Opaque::ffi_init(|ptr: *mut bindings::gendisk_lkclass| {
> +                // SAFETY: `ptr` is valid for writes
> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).bio_lkclass) };
> +                // SAFETY: `ptr` is valid for writes
> +                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).open_mutex_lkclass) };
> +            }),
> +            GFP_KERNEL,
> +        )?;
> +
> +        // SAFETY:
> +        // - `tagset.raw_tag_set()` points to a valid and initialized tag set.
> +        // - We keep `keys` alive for the lifetime of the returned gendisk.
>          let gendisk = from_err_ptr(unsafe {
> -            bindings::__blk_mq_alloc_disk(
> -                tagset.raw_tag_set(),
> -                &mut lim,
> -                data,
> -                lkclass.as_ptr(),
> -            )
> +            bindings::__blk_mq_alloc_disk(tagset.raw_tag_set(), &mut lim, data, keys.get())
>          })?;
>  
>          const TABLE: bindings::block_device_operations = bindings::block_device_operations {

if we can assume that there is (up to) only one

  let mut disk = gen_disk::GenDiskBuilder::new().capacity_sectors(4096).build(fmt!("myblk"), tagset, ())?;

call for each rust module.

By the way, are you aware that rust-enabled linux-next build is recently failing
( https://syzkaller.appspot.com/bug?extid=1f14a35d0c73d31555e4 ) ?


^ permalink raw reply

* Re: [PATCH v3] block: assign caller-specific lockdep class to disk->open_mutex
From: Andreas Hindborg @ 2026-06-19  9:49 UTC (permalink / raw)
  To: Tetsuo Handa, Christoph Hellwig, Bart Van Assche, Jens Axboe
  Cc: linux-block, LKML, Andrew Morton, Ming Lei, Damien Le Moal,
	Qu Wenruo, Hillf Danton, Miguel Ojeda
In-Reply-To: <9ebc39a8-6025-40e3-a378-ba26bf8d73ae@I-love.SAKURA.ne.jp>

Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> writes:

> On 2026/06/05 16:54, Andreas Hindborg wrote:
>> Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> writes:
>> 
>>> The block core currently allocates a single monolithic lockdep key for
>>> disk->open_mutex across all callers. This single key conflates locking
>>> hierarchies between independent block streams. For example, if a stacked
>>> driver like loop flushes its internal workqueues inside lo_release() while
>>> holding its own open_mutex, lockdep views this as a potential ABBA deadlock
>>> against the underlying storage stack, leading to numerous circular
>>> dependency splats.
>>>
>>> To structurally reduce false positives, this patch splits the global
>>> monolithic lock class into distinct, per-caller instances during disk
>>> allocation. This is done by replacing "struct lock_class_key" with
>>> "struct gendisk_lkclass", which contains two instances of
>>> "struct lock_class_key" for the legacy "(bio completion)" map and
>>> disk->open_mutex respectively.
>>>
>>> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
>> 
>> For the Rust part, we have existing infrastructure for lock class keys
>> [1]. Please take a look how we generate lock class keys elsewhere [2].
>
> My understanding is that we don't have infrastructure for lock class keys
> that can be applied to
>
>   +struct gendisk_lkclass {
>   +	struct lock_class_key bio_lkclass;
>   +	struct lock_class_key open_mutex_lkclass;
>   +};
>
>   -	static struct lock_class_key __key;
>   +	static struct gendisk_lkclass __key;
>
> change. Alternative approach is welcomed if you have one.

Sorry, I did not pay enough attention. I would suggest this approach:

diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 21557a1ce866..13048cea8bb0 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -68,7 +68,7 @@ fn new(
             .logical_block_size(block_size)?
             .physical_block_size(block_size)?
             .rotational(rotational)
-            .build(fmt!("{}", name.to_str()?), tagset, queue_data, kernel::my_gendisk_lkclass!())
+            .build(fmt!("{}", name.to_str()?), tagset, queue_data)
     }
 }
 
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 10f22b200567..1fd0d54dd549 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -88,7 +88,7 @@
 //!     Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
 //! let mut disk = gen_disk::GenDiskBuilder::new()
 //!     .capacity_sectors(4096)
-//!     .build(fmt!("myblk"), tagset, (), kernel::my_gendisk_lkclass!())?;
+//!     .build(fmt!("myblk"), tagset, ())?;
 //!
 //! # Ok::<(), kernel::error::Error>(())
 //! ```
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index 8c452e90fe9d..58f87a184407 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -24,6 +24,7 @@
     sync::Arc,
     types::{
         ForeignOwnable,
+        Opaque,
         ScopeGuard, //
     }, //
 };
@@ -49,43 +50,6 @@ fn default() -> Self {
     }
 }
 
-/// A wrapper type for safely passing "struct gendisk_lkclass" argument.
-///
-/// This type can only be instantiated via the [`my_gendisk_lkclass!`] macro.
-pub struct GenDiskLockClass(pub(crate) *mut bindings::gendisk_lkclass);
-
-impl GenDiskLockClass {
-    /// Retrieve the underlying raw pointer.
-    pub(crate) fn as_ptr(&self) -> *mut bindings::gendisk_lkclass {
-        self.0
-    }
-}
-
-#[doc(hidden)]
-pub mod __internal {
-    use super::*;
-
-    /// Internal constructor used ONLY by the `my_gendisk_lkclass!` macro.
-    ///
-    /// SAFETY: `ptr` must point to a valid static `gendisk_lkclass` instance.
-    pub const unsafe fn new_lock_class(ptr: *mut bindings::gendisk_lkclass) -> GenDiskLockClass {
-        GenDiskLockClass(ptr)
-    }
-}
-
-/// Helper macro to generate a unique caller-local static lock class struct
-#[macro_export]
-macro_rules! my_gendisk_lkclass {
-    () => {{
-        static mut LKCLASS: $crate::bindings::gendisk_lkclass = $crate::bindings::gendisk_lkclass {
-            bio_lkclass: const { unsafe { ::core::mem::zeroed() } },
-            open_mutex_lkclass: const { unsafe { ::core::mem::zeroed() } },
-        };
-
-        unsafe { $crate::block::mq::gen_disk::__internal::new_lock_class(&raw mut LKCLASS) }
-    }};
-}
-
 impl GenDiskBuilder {
     /// Create a new instance.
     pub fn new() -> Self {
@@ -148,7 +112,6 @@ pub fn build<T: Operations>(
         name: fmt::Arguments<'_>,
         tagset: Arc<TagSet<T>>,
         queue_data: T::QueueData,
-        lkclass: GenDiskLockClass,
     ) -> Result<GenDisk<T>> {
         let data = queue_data.into_foreign();
         let recover_data = ScopeGuard::new(|| {
@@ -164,14 +127,21 @@ pub fn build<T: Operations>(
             lim.features = bindings::BLK_FEAT_ROTATIONAL;
         }
 
-        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
+        let keys = KBox::pin_init(
+            Opaque::ffi_init(|ptr: *mut bindings::gendisk_lkclass| {
+                // SAFETY: `ptr` is valid for writes
+                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).bio_lkclass) };
+                // SAFETY: `ptr` is valid for writes
+                unsafe { bindings::lockdep_register_key(&raw mut (*ptr).open_mutex_lkclass) };
+            }),
+            GFP_KERNEL,
+        )?;
+
+        // SAFETY:
+        // - `tagset.raw_tag_set()` points to a valid and initialized tag set.
+        // - We keep `keys` alive for the lifetime of the returned gendisk.
         let gendisk = from_err_ptr(unsafe {
-            bindings::__blk_mq_alloc_disk(
-                tagset.raw_tag_set(),
-                &mut lim,
-                data,
-                lkclass.as_ptr(),
-            )
+            bindings::__blk_mq_alloc_disk(tagset.raw_tag_set(), &mut lim, data, keys.get())
         })?;
 
         const TABLE: bindings::block_device_operations = bindings::block_device_operations {
@@ -230,6 +200,7 @@ pub fn build<T: Operations>(
         Ok(GenDisk {
             _tagset: tagset,
             gendisk,
+            _lock_class_keys: keys,
         })
     }
 }
@@ -245,6 +216,7 @@ pub fn build<T: Operations>(
 pub struct GenDisk<T: Operations> {
     _tagset: Arc<TagSet<T>>,
     gendisk: *mut bindings::gendisk,
+    _lock_class_keys: Pin<KBox<Opaque<bindings::gendisk_lkclass>>>,
 }
 
 // SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
---


Best regards,
Andreas Hindborg




^ permalink raw reply related

* [PATCH v4] rust: configfs: add procedural macro for declaring configfs attributes
From: Malte Wechter @ 2026-06-19  9:10 UTC (permalink / raw)
  To: Andreas Hindborg, Breno Leitao, Miguel Ojeda, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Jens Axboe, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin
  Cc: linux-kernel, rust-for-linux, linux-block, linux-modules,
	Malte Wechter

Implement `configfs_attrs!` as a procedural macro using `syn`, this
improves readability and maintainability. Remove the old macro and
replace all uses with the new macro. Add the new macro implementation
file to MAINTAINERS.

Signed-off-by: Malte Wechter <maltewechter@gmail.com>
---
Changes in v4:
- Update link path to configfs_attr macro in configfs.rs
- Fix doc strings for configfs_attr in macros/lib.rs
- Fix doc strings for parse_ordered_fields in macros/helpers.rs
- Update title prefix to `rust: configfs:`
- Link to v3: https://lore.kernel.org/r/20260612-configfs-syn-v3-1-3292fbc5cc32@gmail.com

Changes in v3:
- Remove 'make_static_ident' function, make names for static variables simpler
- Move 'parse_ordered_fields' macro from module.rs into helpers
- Use 'parse_ordered_fields' macro for parsing instead of doing it ad-hoc
- Link to v2: https://lore.kernel.org/r/20260603-configfs-syn-v2-1-cb58489c2647@gmail.com

Changes in v2:
- Add a try_parse helper function to macros/helpers.rs
- Fix bug where 'child' configuration gets dropped if trailing comma is missing (sashiko)
- Link to v1: https://lore.kernel.org/r/20260520-configfs-syn-v1-1-6c5b80a9cef2@gmail.com
---
 MAINTAINERS                     |   1 +
 drivers/block/rnull/configfs.rs |   2 +-
 rust/kernel/configfs.rs         | 263 +---------------------------------------
 rust/macros/configfs_attrs.rs   | 135 +++++++++++++++++++++
 rust/macros/helpers.rs          | 139 +++++++++++++++++++++
 rust/macros/lib.rs              |  87 +++++++++++++
 rust/macros/module.rs           | 137 ---------------------
 samples/rust/rust_configfs.rs   |   2 +-
 8 files changed, 370 insertions(+), 396 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..45f7a1ec93b4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6464,6 +6464,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git config
 F:	fs/configfs/
 F:	include/linux/configfs.h
 F:	rust/kernel/configfs.rs
+F:	rust/macros/configfs_attrs.rs
 F:	samples/configfs/
 F:	samples/rust/rust_configfs.rs
 
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 7c2eb5c0b722..f28ec69d7984 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -4,8 +4,8 @@
 use kernel::{
     block::mq::gen_disk::{GenDisk, GenDiskBuilder},
     configfs::{self, AttributeOperations},
-    configfs_attrs,
     fmt::{self, Write as _},
+    macros::configfs_attrs,
     new_mutex,
     page::PAGE_SIZE,
     prelude::*,
diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs
index 2339c6467325..a8995a418136 100644
--- a/rust/kernel/configfs.rs
+++ b/rust/kernel/configfs.rs
@@ -21,7 +21,7 @@
 //!
 //! ```ignore
 //! use kernel::alloc::flags;
-//! use kernel::configfs_attrs;
+//! use macros::configfs_attrs;
 //! use kernel::configfs;
 //! use kernel::new_mutex;
 //! use kernel::page::PAGE_SIZE;
@@ -240,7 +240,7 @@ unsafe fn container_of(group: *const bindings::config_group) -> *const Self {
 /// A configfs group.
 ///
 /// To add a subgroup to configfs, pass this type as `ctype` to
-/// [`crate::configfs_attrs`] when creating a group in [`GroupOperations::make_group`].
+/// [`macros::configfs_attrs`] when creating a group in [`GroupOperations::make_group`].
 #[pin_data]
 pub struct Group<Data> {
     #[pin]
@@ -637,7 +637,7 @@ pub const fn new(name: &'static CStr) -> Self {
 /// implement `HasGroup<Data>`. The trait must be implemented once for each
 /// attribute of the group. The constant type parameter `ID` maps the
 /// implementation to a specific `Attribute`. `ID` must be passed when declaring
-/// attributes via the [`kernel::configfs_attrs`] macro, to tie
+/// attributes via the [`macros::configfs_attrs`] macro, to tie
 /// `AttributeOperations` implementations to concrete named attributes.
 #[vtable]
 pub trait AttributeOperations<const ID: u64 = 0> {
@@ -669,13 +669,13 @@ fn store(_data: &Self::Data, _page: &[u8]) -> Result {
 /// This type is used to construct a new [`ItemType`]. It represents a list of
 /// [`Attribute`] that will appear in the directory representing a [`Group`].
 /// Users should not directly instantiate this type, rather they should use the
-/// [`kernel::configfs_attrs`] macro to declare a static set of attributes for a
+/// [`macros::configfs_attrs`] macro to declare a static set of attributes for a
 /// group.
 ///
 /// # Note
 ///
 /// Instances of this type are constructed statically at compile by the
-/// [`kernel::configfs_attrs`] macro.
+/// [`macros::configfs_attrs`] macro.
 #[repr(transparent)]
 pub struct AttributeList<const N: usize, Data>(
     /// Null terminated Array of pointers to [`Attribute`]. The type is [`c_void`]
@@ -724,7 +724,7 @@ impl<const N: usize, Data> AttributeList<N, Data> {
 /// [`Subsystem`].
 ///
 /// Users should not directly instantiate objects of this type. Rather, they
-/// should use the [`kernel::configfs_attrs`] macro to statically declare the
+/// should use the [`macros::configfs_attrs`] macro to statically declare the
 /// shape of a [`Group`] or [`Subsystem`].
 #[pin_data]
 pub struct ItemType<Container, Data> {
@@ -791,254 +791,3 @@ fn as_ptr(&self) -> *const bindings::config_item_type {
         self.item_type.get()
     }
 }
-
-/// Define a list of configfs attributes statically.
-///
-/// Invoking the macro in the following manner:
-///
-/// ```ignore
-/// let item_type = configfs_attrs! {
-///     container: configfs::Subsystem<Configuration>,
-///     data: Configuration,
-///     child: Child,
-///     attributes: [
-///         message: 0,
-///         bar: 1,
-///     ],
-/// };
-/// ```
-///
-/// Expands the following output:
-///
-/// ```ignore
-/// let item_type = {
-///     static CONFIGURATION_MESSAGE_ATTR: kernel::configfs::Attribute<
-///         0,
-///         Configuration,
-///         Configuration,
-///     > = unsafe {
-///         kernel::configfs::Attribute::new({
-///             const S: &str = "message\u{0}";
-///             const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
-///                 S.as_bytes()
-///             ) {
-///                 Ok(v) => v,
-///                 Err(_) => {
-///                     core::panicking::panic_fmt(core::const_format_args!(
-///                         "string contains interior NUL"
-///                     ));
-///                 }
-///             };
-///             C
-///         })
-///     };
-///
-///     static CONFIGURATION_BAR_ATTR: kernel::configfs::Attribute<
-///             1,
-///             Configuration,
-///             Configuration
-///     > = unsafe {
-///         kernel::configfs::Attribute::new({
-///             const S: &str = "bar\u{0}";
-///             const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
-///                 S.as_bytes()
-///             ) {
-///                 Ok(v) => v,
-///                 Err(_) => {
-///                     core::panicking::panic_fmt(core::const_format_args!(
-///                         "string contains interior NUL"
-///                     ));
-///                 }
-///             };
-///             C
-///         })
-///     };
-///
-///     const N: usize = (1usize + (1usize + 0usize)) + 1usize;
-///
-///     static CONFIGURATION_ATTRS: kernel::configfs::AttributeList<N, Configuration> =
-///         unsafe { kernel::configfs::AttributeList::new() };
-///
-///     {
-///         const N: usize = 0usize;
-///         unsafe { CONFIGURATION_ATTRS.add::<N, 0, _>(&CONFIGURATION_MESSAGE_ATTR) };
-///     }
-///
-///     {
-///         const N: usize = (1usize + 0usize);
-///         unsafe { CONFIGURATION_ATTRS.add::<N, 1, _>(&CONFIGURATION_BAR_ATTR) };
-///     }
-///
-///     static CONFIGURATION_TPE:
-///       kernel::configfs::ItemType<configfs::Subsystem<Configuration> ,Configuration>
-///         = kernel::configfs::ItemType::<
-///                 configfs::Subsystem<Configuration>,
-///                 Configuration
-///                 >::new_with_child_ctor::<N,Child>(
-///             &THIS_MODULE,
-///             &CONFIGURATION_ATTRS
-///         );
-///
-///     &CONFIGURATION_TPE
-/// }
-/// ```
-#[macro_export]
-macro_rules! configfs_attrs {
-    (
-        container: $container:ty,
-        data: $data:ty,
-        attributes: [
-            $($name:ident: $attr:literal),* $(,)?
-        ] $(,)?
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child(),
-            @no_child(x),
-            @attrs($($name $attr)*),
-            @eat($($name $attr,)*),
-            @assign(),
-            @cnt(0usize),
-        )
-    };
-    (
-        container: $container:ty,
-        data: $data:ty,
-        child: $child:ty,
-        attributes: [
-            $($name:ident: $attr:literal),* $(,)?
-        ] $(,)?
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child($child),
-            @no_child(),
-            @attrs($($name $attr)*),
-            @eat($($name $attr,)*),
-            @assign(),
-            @cnt(0usize),
-        )
-    };
-    (count:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($aname:ident $aattr:literal)*),
-     @eat($name:ident $attr:literal, $($rname:ident $rattr:literal,)*),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child($($child)?),
-            @no_child($($no_child)?),
-            @attrs($($aname $aattr)*),
-            @eat($($rname $rattr,)*),
-            @assign($($assign)* {
-                const N: usize = $cnt;
-                // The following macro text expands to a call to `Attribute::add`.
-
-                // SAFETY: By design of this macro, the name of the variable we
-                // invoke the `add` method on below, is not visible outside of
-                // the macro expansion. The macro does not operate concurrently
-                // on this variable, and thus we have exclusive access to the
-                // variable.
-                unsafe {
-                    $crate::macros::paste!(
-                        [< $data:upper _ATTRS >]
-                            .add::<N, $attr, _>(&[< $data:upper _ $name:upper _ATTR >])
-                    )
-                };
-            }),
-            @cnt(1usize + $cnt),
-        )
-    };
-    (count:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($aname:ident $aattr:literal)*),
-     @eat(),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) =>
-    {
-        $crate::configfs_attrs!(
-            final:
-            @container($container),
-            @data($data),
-            @child($($child)?),
-            @no_child($($no_child)?),
-            @attrs($($aname $aattr)*),
-            @assign($($assign)*),
-            @cnt($cnt),
-        )
-    };
-    (final:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($name:ident $attr:literal)*),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) =>
-    {
-        $crate::macros::paste!{
-            {
-                $(
-                    // SAFETY: We are expanding `configfs_attrs`.
-                    static [< $data:upper _ $name:upper _ATTR >]:
-                        $crate::configfs::Attribute<$attr, $data, $data> =
-                            unsafe {
-                                $crate::configfs::Attribute::new(
-                                    $crate::c_str!(::core::stringify!($name)),
-                                )
-                            };
-                )*
-
-
-                // We need space for a null terminator.
-                const N: usize = $cnt + 1usize;
-
-                // SAFETY: We are expanding `configfs_attrs`.
-                static [< $data:upper _ATTRS >]:
-                $crate::configfs::AttributeList<N, $data> =
-                    unsafe { $crate::configfs::AttributeList::new() };
-
-                $($assign)*
-
-                $(
-                    const [<$no_child:upper>]: bool = true;
-
-                    static [< $data:upper _TPE >] : $crate::configfs::ItemType<$container, $data>  =
-                        $crate::configfs::ItemType::<$container, $data>::new::<N>(
-                            &THIS_MODULE, &[<$ data:upper _ATTRS >]
-                        );
-                )?
-
-                $(
-                    static [< $data:upper _TPE >]:
-                        $crate::configfs::ItemType<$container, $data>  =
-                            $crate::configfs::ItemType::<$container, $data>::
-                            new_with_child_ctor::<N, $child>(
-                                &THIS_MODULE, &[<$ data:upper _ATTRS >]
-                            );
-                )?
-
-                & [< $data:upper _TPE >]
-            }
-        }
-    };
-
-}
-
-pub use crate::configfs_attrs;
diff --git a/rust/macros/configfs_attrs.rs b/rust/macros/configfs_attrs.rs
new file mode 100644
index 000000000000..81037bc38188
--- /dev/null
+++ b/rust/macros/configfs_attrs.rs
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use quote::{
+    format_ident,
+    quote, //
+};
+
+use syn::{
+    bracketed,
+    ext::IdentExt,
+    parse::{
+        Parse,
+        ParseStream, //
+    },
+    punctuated::Punctuated,
+    spanned::Spanned,
+    Error,
+    Ident,
+    LitInt,
+    Token,
+    Type, //
+};
+
+use crate::helpers::parse_ordered_fields;
+
+pub(crate) struct ConfigfsAttrs {
+    container: Type,
+    data: Type,
+    child: Option<Type>,
+    attributes: Vec<(Ident, LitInt)>,
+}
+
+fn parse_attribute_field(stream: ParseStream<'_>) -> syn::Result<(Ident, LitInt)> {
+    let id = stream.parse::<syn::Ident>()?;
+    let _colon = stream.parse::<Token![:]>()?;
+    let v = stream.parse::<LitInt>()?;
+    Ok((id, v))
+}
+
+fn parse_attributes(stream: ParseStream<'_>) -> syn::Result<Vec<(Ident, LitInt)>> {
+    let attr_stream;
+    let _bracket = bracketed!(attr_stream in stream);
+    let attributes = Punctuated::<(Ident, LitInt), Token![,]>::parse_terminated_with(
+        &attr_stream,
+        parse_attribute_field,
+    )?;
+    Ok(attributes.into_iter().collect::<Vec<_>>())
+}
+
+impl Parse for ConfigfsAttrs {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        parse_ordered_fields!(
+            from input;
+            container [required] => (input.parse::<Type>())?,
+            data [required] => (input.parse::<Type>())?,
+            child => (input.parse::<Type>())?,
+            attributes [required] => parse_attributes(input)?,
+        );
+
+        Ok(ConfigfsAttrs {
+            container,
+            data,
+            child,
+            attributes,
+        })
+    }
+}
+
+pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
+    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
+
+    let data_tp_ident = Ident::new("DATA_TPE", cfs_attrs.data.span());
+    let data_attr_ident = Ident::new("DATA_ATTR_LIST", cfs_attrs.data.span());
+
+    let n = cfs_attrs.attributes.len() + 1;
+
+    let attr_list = quote! {
+        static #data_attr_ident: kernel::configfs::AttributeList<#n, #data_ty> =
+            // SAFETY: We are expanding `configfs_attrs`.
+            unsafe { kernel::configfs::AttributeList::new() };
+    };
+
+    let mut attrs = Vec::new();
+    for (attr_idx, (name, id)) in cfs_attrs.attributes.iter().enumerate() {
+        let name_with_attr = format_ident!("{}_ATTR_{}", name.to_string().to_uppercase(), attr_idx);
+
+        let id: u64 = match id.base10_parse::<u64>() {
+            Ok(v) => v,
+            Err(_) => {
+                return syn::Error::new(id.span(), "Could not parse attribute ID as a u64")
+                    .to_compile_error();
+            }
+        };
+
+        attrs.push(quote! {
+        static #name_with_attr: kernel::configfs::Attribute<#id, #data_ty, #data_ty> =
+            // SAFETY: We are expanding `configfs_attrs`.
+            unsafe {
+              kernel::configfs::Attribute::new(kernel::c_str!(::core::stringify!(#name)))
+            };
+
+          // SAFETY: By design of this macro, the name of the variable we
+          // invoke the `add` method on below, is not visible outside of
+          // the macro expansion. The macro does not operate concurrently
+          // on this variable, and thus we have exclusive access to the
+          // variable.
+          unsafe { #data_attr_ident.add::<#attr_idx, #id, _>(&#name_with_attr) }
+        });
+    }
+
+    let has_child_code = if let Some(child) = cfs_attrs.child {
+        quote! { new_with_child_ctor::<#n, #child>}
+    } else {
+        quote! { new::<#n> }
+    };
+
+    let data_type = quote! {
+        {
+            static #data_tp_ident:
+            kernel::configfs::ItemType<#container_ty, #data_ty> =
+                kernel::configfs::ItemType::<#container_ty, #data_ty>::#has_child_code(
+                    &THIS_MODULE, &#data_attr_ident
+                );
+            &#data_tp_ident
+        }
+    };
+
+    quote! {
+        {
+            #attr_list
+            #(#attrs)*
+            #data_type
+        }
+    }
+}
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index d18fbf4daa0a..305dcbddf797 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -58,3 +58,142 @@ pub(crate) fn file() -> String {
 pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
     attr.iter().filter(|a| a.path().is_ident("cfg"))
 }
+
+/// Parse fields that are required to use a specific order.
+///
+/// As fields must follow a specific order, we *could* just parse fields one by one by peeking.
+/// However the error message generated when implementing that way is not very friendly.
+///
+/// So instead we parse fields in an arbitrary order, but only enforce the ordering after parsing,
+/// and if the wrong order is used, the proper order is communicated to the user with error message.
+///
+/// Usage looks like this:
+/// ```ignore
+/// parse_ordered_fields! {
+///     from input;
+///
+///     // This will extract `foo: <field>` into a variable named `foo`.
+///     // The variable will have type `Option<_>`.
+///     foo => <expression that parses the field>,
+///
+///     // If you need the variable name to be different than the key name.
+///     // This extracts `baz: <field>` into a variable named `bar`.
+///     // You might want this if `baz` is a keyword.
+///     baz as bar => <expression that parse the field>,
+///
+///     // You can mark a key as required, and the variable will no longer be `Option`.
+///     // foobar will be of type `Expr` instead of `Option<Expr>`.
+///     foobar [required] => input.parse::<Expr>()?,
+/// }
+/// ```
+macro_rules! parse_ordered_fields {
+    (@gen
+        [$input:expr]
+        [$([$name:ident; $key:ident; $parser:expr])*]
+        [$([$req_name:ident; $req_key:ident])*]
+    ) => {
+        $(let mut $name = None;)*
+
+        const EXPECTED_KEYS: &[&str] = &[$(stringify!($key),)*];
+        const REQUIRED_KEYS: &[&str] = &[$(stringify!($req_key),)*];
+
+        let span = $input.span();
+        let mut seen_keys = Vec::new();
+
+        while !$input.is_empty() {
+            let key = $input.call(Ident::parse_any)?;
+
+            if seen_keys.contains(&key) {
+                Err(Error::new_spanned(
+                    &key,
+                    format!(r#"duplicated key "{key}". Keys can only be specified once."#),
+                ))?
+            }
+
+            $input.parse::<Token![:]>()?;
+
+            match &*key.to_string() {
+                $(
+                    stringify!($key) => $name = Some($parser),
+                )*
+                _ => {
+                    Err(Error::new_spanned(
+                        &key,
+                        format!(r#"unknown key "{key}". Valid keys are: {EXPECTED_KEYS:?}."#),
+                    ))?
+                }
+            }
+
+            $input.parse::<Token![,]>()?;
+            seen_keys.push(key);
+        }
+
+        for key in REQUIRED_KEYS {
+            if !seen_keys.iter().any(|e| e == key) {
+                Err(Error::new(span, format!(r#"missing required key "{key}""#)))?
+            }
+        }
+
+        let mut ordered_keys: Vec<&str> = Vec::new();
+        for key in EXPECTED_KEYS {
+            if seen_keys.iter().any(|e| e == key) {
+                ordered_keys.push(key);
+            }
+        }
+
+        if seen_keys != ordered_keys {
+            Err(Error::new(
+                span,
+                format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
+            ))?
+        }
+
+        $(let $req_name = $req_name.expect("required field");)*
+    };
+
+    // Handle required fields.
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $key:ident as $name:ident [required] => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)* [$name; $key]] $($rest)*
+        )
+    };
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $name:ident [required] => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)* [$name; $name]] $($rest)*
+        )
+    };
+
+    // Handle optional fields.
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $key:ident as $name:ident => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)*] $($rest)*
+        )
+    };
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $name:ident => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)*] $($rest)*
+        )
+    };
+
+    (from $input:expr; $($tok:tt)*) => {
+        parse_ordered_fields!(@gen [$input] [] [] $($tok)*)
+    }
+}
+
+pub(crate) use parse_ordered_fields;
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 2cfd59e0f9e7..ebb41e80ecc7 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -15,6 +15,8 @@
 #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
 
 mod concat_idents;
+#[cfg(CONFIG_CONFIGFS_FS)]
+mod configfs_attrs;
 mod export;
 mod fmt;
 mod helpers;
@@ -489,3 +491,88 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
         .unwrap_or_else(|e| e.into_compile_error())
         .into()
 }
+
+/// Define a list of configfs attributes statically.
+///
+/// # Examples
+///
+/// ```ignore
+/// let item_type = configfs_attrs! {
+///     container: configfs::Subsystem<Configuration>,
+///     data: Configuration,
+///     child: Child,
+///     attributes: [
+///         message: 0,
+///         bar: 1,
+///     ],
+/// };
+/// ```
+///
+/// Expands the following output:
+///
+/// ```ignore
+/// let item_type = {
+///         static DATA_ATTR_LIST: kernel::configfs::AttributeList<
+///             3usize,
+///             Configuration,
+///         > = unsafe { kernel::configfs::AttributeList::new() };
+///         static MESSAGE_ATTR_0: kernel::configfs::Attribute<
+///             0u64,
+///             Configuration,
+///             Configuration,
+///         > = unsafe {
+///             kernel::configfs::Attribute::new({
+///                 const S: &str = "message\u{0}";
+///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
+///                     S.as_bytes(),
+///                 ) {
+///                     Ok(v) => v,
+///                     Err(_) => {
+///                         ::core::panicking::panic_fmt(
+///                             format_args!("string contains interior NUL"),
+///                         );
+///                     }
+///                 };
+///                 C
+///             })
+///         };
+///         unsafe { DATA_ATTR_LIST.add::<0usize, 0u64, _>(&MESSAGE_ATTR_0) }
+///         static BAR_ATTR_1: kernel::configfs::Attribute<
+///             1u64,
+///             Configuration,
+///             Configuration,
+///         > = unsafe {
+///             kernel::configfs::Attribute::new({
+///                 const S: &str = "bar\u{0}";
+///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
+///                     S.as_bytes(),
+///                 ) {
+///                     Ok(v) => v,
+///                     Err(_) => {
+///                         ::core::panicking::panic_fmt(
+///                             format_args!("string contains interior NUL"),
+///                         );
+///                     }
+///                 };
+///                 C
+///             })
+///         };
+///         unsafe { DATA_ATTR_LIST.add::<1usize, 1u64, _>(&BAR_ATTR_1) }
+///         {
+///             static DATA_TPE: kernel::configfs::ItemType<
+///                 Subsystem<Configuration>,
+///                 Configuration,
+///             > = kernel::configfs::ItemType::<
+///                 Subsystem<Configuration>,
+///                 Configuration,
+///             >::new_with_child_ctor::<3usize, Child>(&THIS_MODULE, &DATA_ATTR_LIST);
+///             &DATA_TPE
+///         }
+///     };
+/// ```
+#[cfg(CONFIG_CONFIGFS_FS)]
+#[proc_macro]
+pub fn configfs_attrs(input: TokenStream) -> TokenStream {
+    configfs_attrs::configfs_attrs(parse_macro_input!(input as configfs_attrs::ConfigfsAttrs))
+        .into()
+}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 06c18e207508..7ff6ad09b1a2 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -196,143 +196,6 @@ fn param_ops_path(param_type: &str) -> Path {
     }
 }
 
-/// Parse fields that are required to use a specific order.
-///
-/// As fields must follow a specific order, we *could* just parse fields one by one by peeking.
-/// However the error message generated when implementing that way is not very friendly.
-///
-/// So instead we parse fields in an arbitrary order, but only enforce the ordering after parsing,
-/// and if the wrong order is used, the proper order is communicated to the user with error message.
-///
-/// Usage looks like this:
-/// ```ignore
-/// parse_ordered_fields! {
-///     from input;
-///
-///     // This will extract "foo: <field>" into a variable named "foo".
-///     // The variable will have type `Option<_>`.
-///     foo => <expression that parses the field>,
-///
-///     // If you need the variable name to be different than the key name.
-///     // This extracts "baz: <field>" into a variable named "bar".
-///     // You might want this if "baz" is a keyword.
-///     baz as bar => <expression that parse the field>,
-///
-///     // You can mark a key as required, and the variable will no longer be `Option`.
-///     // foobar will be of type `Expr` instead of `Option<Expr>`.
-///     foobar [required] => input.parse::<Expr>()?,
-/// }
-/// ```
-macro_rules! parse_ordered_fields {
-    (@gen
-        [$input:expr]
-        [$([$name:ident; $key:ident; $parser:expr])*]
-        [$([$req_name:ident; $req_key:ident])*]
-    ) => {
-        $(let mut $name = None;)*
-
-        const EXPECTED_KEYS: &[&str] = &[$(stringify!($key),)*];
-        const REQUIRED_KEYS: &[&str] = &[$(stringify!($req_key),)*];
-
-        let span = $input.span();
-        let mut seen_keys = Vec::new();
-
-        while !$input.is_empty() {
-            let key = $input.call(Ident::parse_any)?;
-
-            if seen_keys.contains(&key) {
-                Err(Error::new_spanned(
-                    &key,
-                    format!(r#"duplicated key "{key}". Keys can only be specified once."#),
-                ))?
-            }
-
-            $input.parse::<Token![:]>()?;
-
-            match &*key.to_string() {
-                $(
-                    stringify!($key) => $name = Some($parser),
-                )*
-                _ => {
-                    Err(Error::new_spanned(
-                        &key,
-                        format!(r#"unknown key "{key}". Valid keys are: {EXPECTED_KEYS:?}."#),
-                    ))?
-                }
-            }
-
-            $input.parse::<Token![,]>()?;
-            seen_keys.push(key);
-        }
-
-        for key in REQUIRED_KEYS {
-            if !seen_keys.iter().any(|e| e == key) {
-                Err(Error::new(span, format!(r#"missing required key "{key}""#)))?
-            }
-        }
-
-        let mut ordered_keys: Vec<&str> = Vec::new();
-        for key in EXPECTED_KEYS {
-            if seen_keys.iter().any(|e| e == key) {
-                ordered_keys.push(key);
-            }
-        }
-
-        if seen_keys != ordered_keys {
-            Err(Error::new(
-                span,
-                format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
-            ))?
-        }
-
-        $(let $req_name = $req_name.expect("required field");)*
-    };
-
-    // Handle required fields.
-    (@gen
-        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
-        $key:ident as $name:ident [required] => $parser:expr,
-        $($rest:tt)*
-    ) => {
-        parse_ordered_fields!(
-            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)* [$name; $key]] $($rest)*
-        )
-    };
-    (@gen
-        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
-        $name:ident [required] => $parser:expr,
-        $($rest:tt)*
-    ) => {
-        parse_ordered_fields!(
-            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)* [$name; $name]] $($rest)*
-        )
-    };
-
-    // Handle optional fields.
-    (@gen
-        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
-        $key:ident as $name:ident => $parser:expr,
-        $($rest:tt)*
-    ) => {
-        parse_ordered_fields!(
-            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)*] $($rest)*
-        )
-    };
-    (@gen
-        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
-        $name:ident => $parser:expr,
-        $($rest:tt)*
-    ) => {
-        parse_ordered_fields!(
-            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)*] $($rest)*
-        )
-    };
-
-    (from $input:expr; $($tok:tt)*) => {
-        parse_ordered_fields!(@gen [$input] [] [] $($tok)*)
-    }
-}
-
 struct Parameter {
     name: Ident,
     ptype: Ident,
diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
index a1bd9db6010d..876462f7789d 100644
--- a/samples/rust/rust_configfs.rs
+++ b/samples/rust/rust_configfs.rs
@@ -4,7 +4,7 @@
 
 use kernel::alloc::flags;
 use kernel::configfs;
-use kernel::configfs::configfs_attrs;
+use kernel::macros::configfs_attrs;
 use kernel::new_mutex;
 use kernel::page::PAGE_SIZE;
 use kernel::prelude::*;

---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260417-configfs-syn-191e07130027

Best regards,
-- 
Malte Wechter <maltewechter@gmail.com>


^ permalink raw reply related

* Re: WARNING: at floppy_interrupt, CPU: swapper/NUM/NUM
From: Denis Efremov (Oracle) @ 2026-06-19  6:43 UTC (permalink / raw)
  To: sanan.hasanou, axboe, linux-block, linux-kernel; +Cc: syzkaller, contact
In-Reply-To: <6a34707b.25ac79d9.2b1a46.0a67@mx.google.com>

Hello,

Thank you for the report. This is a known warning that only happens in a virtualized
environment. You may want to add this piece of a config to your modified syzkaller
dashboard/config/linux/bits/unmaintained.yml

Thanks,
Denis

On 19/06/2026 02:26, sanan.hasanou@gmail.com wrote:
> Good day, dear maintainers,
> 
> We found a bug using a modified version of syzkaller.
> 
> Kernel Branch: 7.0-rc1
> Kernel Config: <https://drive.google.com/open?id=173DLEAEPKPhhR1TcqofdnkLpdoK7PMFl>
> Unfortunately, we don't have any reproducer for this bug yet.
> Thank you!
> 
> Best regards,
> Sanan Hasanov
> 
> ------------[ cut here ]------------
> WARNING: at schedule_bh drivers/block/floppy.c:1000 [inline], CPU#0: swapper/0/1
> WARNING: at floppy_interrupt+0x51b/0x560 drivers/block/floppy.c:1766, CPU#0: swapper/0/1
> Modules linked in:
> CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.0.0-rc1 #1 PREEMPT(full) 
> Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> RIP: 0010:schedule_bh drivers/block/floppy.c:1000 [inline]
> RIP: 0010:floppy_interrupt+0x51b/0x560 drivers/block/floppy.c:1766
> Code: 35 3a c8 54 0c 48 c7 c7 80 fa 4b 8c 48 c7 c2 c0 f7 4b 8c 48 c7 c1 40 f9 4b 8c e8 a0 4a 3b fb e9 af fe ff ff e8 66 d9 d5 fb 90 <0f> 0b 90 e9 e8 fc ff ff 44 89 f9 80 e1 07 38 c1 0f 8c 27 fc ff ff
> RSP: 0018:ffffc90000007af8 EFLAGS: 00010006
> RAX: ffffffff85ec786a RBX: ffffffff85ecf380 RCX: ffff888016aeba80
> RDX: 0000000000010100 RSI: 0000000000000001 RDI: 0000000000000000
> RBP: 0000000000000000 R08: ffffffff8f3e2467 R09: 1ffffffff1e7c48c
> R10: dffffc0000000000 R11: fffffbfff1e7c48d R12: dffffc0000000000
> R13: 0000000000000000 R14: 0000000002000011 R15: 0000000000000000
> FS:  0000000000000000(0000) GS:ffff8880d98df000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: ffff888012801000 CR3: 000000000e6ff000 CR4: 00000000000006f0
> Call Trace:
>  <IRQ>
>  __handle_irq_event_percpu+0x1d9/0x5d0 kernel/irq/handle.c:209
>  handle_irq_event_percpu kernel/irq/handle.c:246 [inline]
>  handle_irq_event+0x90/0x1e0 kernel/irq/handle.c:263
>  handle_edge_irq+0x239/0x9e0 kernel/irq/chip.c:855
>  generic_handle_irq_desc include/linux/irqdesc.h:186 [inline]
>  handle_irq arch/x86/kernel/irq.c:262 [inline]
>  call_irq_handler arch/x86/kernel/irq.c:286 [inline]
>  __common_interrupt+0xc5/0x170 arch/x86/kernel/irq.c:333
>  common_interrupt+0x4a/0xc0 arch/x86/kernel/irq.c:326
>  asm_common_interrupt+0x26/0x40 arch/x86/include/asm/idtentry.h:688
> RIP: 0010:__raw_spin_unlock_irq include/linux/spinlock_api_smp.h:188 [inline]
> RIP: 0010:_raw_spin_unlock_irq+0x19/0x30 kernel/locking/spinlock.c:202
> Code: 00 02 00 00 75 db eb da e8 74 c0 a8 f5 5b c3 66 90 f3 0f 1e fa 0f 1f 44 00 00 e8 f2 b4 12 f6 e8 4d 86 41 f6 fb bf 01 00 00 00 <e8> d2 2a 07 f6 65 8b 05 8b 59 88 06 85 c0 74 01 c3 e8 41 c0 a8 f5
> RSP: 0018:ffffc90000007d58 EFLAGS: 00000246
> RAX: 0000000000000001 RBX: ffffffff85358ab0 RCX: 0000000000000000
> RDX: 0000000000000000 RSI: 0000000000000004 RDI: 0000000000000001
> RBP: ffffc90000007ef8 R08: ffff88806ba2f683 R09: 1ffff1100d745ed0
> R10: dffffc0000000000 R11: ffffed100d745ed1 R12: ffff88801d085478
> R13: dffffc0000000000 R14: ffff88806ba2f680 R15: ffff88806ba2f698
>  expire_timers kernel/time/timer.c:1798 [inline]
>  __run_timers kernel/time/timer.c:2373 [inline]
>  __run_timer_base+0x700/0xa30 kernel/time/timer.c:2385
>  run_timer_base kernel/time/timer.c:2394 [inline]
>  run_timer_softirq+0xbc/0x190 kernel/time/timer.c:2404
>  handle_softirqs+0x1ed/0x700 kernel/softirq.c:622
>  __do_softirq kernel/softirq.c:656 [inline]
>  invoke_softirq kernel/softirq.c:496 [inline]
>  __irq_exit_rcu+0x8e/0x270 kernel/softirq.c:723
>  irq_exit_rcu+0xe/0x30 kernel/softirq.c:739
>  instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1056 [inline]
>  sysvec_apic_timer_interrupt+0x92/0xb0 arch/x86/kernel/apic/apic.c:1056
>  </IRQ>
>  <TASK>
>  asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
> RIP: 0010:clear_pages arch/x86/include/asm/page_64.h:103 [inline]
> RIP: 0010:clear_page arch/x86/include/asm/page_64.h:114 [inline]
> RIP: 0010:clear_highpage_kasan_tagged include/linux/highmem.h:344 [inline]
> RIP: 0010:kernel_init_pages mm/page_alloc.c:1265 [inline]
> RIP: 0010:post_alloc_hook+0x3ff/0x480 mm/page_alloc.c:1887
> Code: 03 49 c7 c7 20 2e 43 8e 49 c1 ef 03 eb 2f 48 8b 3d c6 74 21 0c 49 c1 e5 06 4c 29 ef 4c 01 e7 b9 00 10 00 00 31 c0 48 c1 e9 03 <f3> 48 ab 49 81 c4 00 10 00 00 49 ff ce 0f 84 31 fd ff ff 48 b8 00
> RSP: 0018:ffffc9000001eed8 EFLAGS: 00000216
> RAX: 0000000000000000 RBX: 1ffffffff1c865c6 RCX: 0000000000000200
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff88801dc20000
> RBP: 0000000000000003 R08: ffffffff9049fd6f R09: 0000000000000000
> R10: ffffed1003b84000 R11: fffffbfff2093fae R12: fffa80001dc20000
> R13: fffa800000000000 R14: 0000000000000008 R15: 1ffffffff1c865c4
>  prep_new_page mm/page_alloc.c:1897 [inline]
>  get_page_from_freelist+0x2240/0x2330 mm/page_alloc.c:3962
>  __alloc_frozen_pages_noprof+0x20e/0x3d0 mm/page_alloc.c:5250
>  __alloc_pages_noprof+0xf/0x30 mm/page_alloc.c:5284
>  vm_area_alloc_pages mm/vmalloc.c:-1 [inline]
>  __vmalloc_area_node mm/vmalloc.c:3876 [inline]
>  __vmalloc_node_range_noprof+0x79f/0x1580 mm/vmalloc.c:4064
>  __vmalloc_node_noprof mm/vmalloc.c:4124 [inline]
>  vzalloc_noprof+0xdf/0x120 mm/vmalloc.c:4202
>  allocate_partitions block/partitions/core.c:101 [inline]
>  check_partition block/partitions/core.c:123 [inline]
>  blk_add_partitions block/partitions/core.c:590 [inline]
>  bdev_disk_changed+0x628/0x1810 block/partitions/core.c:694
>  blkdev_get_whole+0x37e/0x500 block/bdev.c:764
>  bdev_open+0x35b/0xdc0 block/bdev.c:973
>  bdev_file_open_by_dev+0x1c3/0x240 block/bdev.c:1075
>  disk_scan_partitions+0x1be/0x2c0 block/genhd.c:387
>  add_disk_final block/genhd.c:416 [inline]
>  add_disk_fwnode+0x31e/0x470 block/genhd.c:610
>  add_disk include/linux/blkdev.h:785 [inline]
>  brd_alloc+0x5de/0x810 drivers/block/brd.c:340
>  brd_init+0xc6/0x120 drivers/block/brd.c:420
>  do_one_initcall+0x1a1/0x530 init/main.c:1382
>  do_initcall_level+0x117/0x1a0 init/main.c:1444
>  do_initcalls+0xe1/0x150 init/main.c:1460
>  kernel_init_freeable+0x207/0x310 init/main.c:1692
>  kernel_init+0x22/0x1d0 init/main.c:1582
>  ret_from_fork+0x608/0xc40 arch/x86/kernel/process.c:158
>  ret_from_fork_asm+0x11/0x20 arch/x86/entry/entry_64.S:245
>  </TASK>
> ----------------
> Code disassembly (best guess):
>    0:	00 02                	add    %al,(%rdx)
>    2:	00 00                	add    %al,(%rax)
>    4:	75 db                	jne    0xffffffe1
>    6:	eb da                	jmp    0xffffffe2
>    8:	e8 74 c0 a8 f5       	call   0xf5a8c081
>    d:	5b                   	pop    %rbx
>    e:	c3                   	ret
>    f:	66 90                	xchg   %ax,%ax
>   11:	f3 0f 1e fa          	endbr64
>   15:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
>   1a:	e8 f2 b4 12 f6       	call   0xf612b511
>   1f:	e8 4d 86 41 f6       	call   0xf6418671
>   24:	fb                   	sti
>   25:	bf 01 00 00 00       	mov    $0x1,%edi
> * 2a:	e8 d2 2a 07 f6       	call   0xf6072b01 <-- trapping instruction
>   2f:	65 8b 05 8b 59 88 06 	mov    %gs:0x688598b(%rip),%eax        # 0x68859c1
>   36:	85 c0                	test   %eax,%eax
>   38:	74 01                	je     0x3b
>   3a:	c3                   	ret
>   3b:	e8 41 c0 a8 f5       	call   0xf5a8c081
> 
> <<<<<<<<<<<<<<< tail report >>>>>>>>>>>>>>>


^ permalink raw reply

* Re: [PATCH v2 4/5] rust: block: mq: use vertical import style
From: Miguel Ojeda @ 2026-06-19  6:29 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Jens Axboe, Alvin Sun, Arnd Bergmann, Greg Kroah-Hartman,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	rust-for-linux, linux-block
In-Reply-To: <87v7bgf8f3.fsf@t14s.mail-host-address-is-not-set>

On Thu, Jun 18, 2026 at 12:32 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>
> Acked-by: Andreas Hindborg <a.hindborg@kernel.org>
>
> Cc: Jens Axboe <axboe@kernel.dk>

There is a v3 (may be exactly the same):

  https://lore.kernel.org/rust-for-linux/20260521-miscdev-use-format-v3-4-56240ca70d0c@linux.dev/

Cheers,
Miguel

^ permalink raw reply

* Re: [PATCH 1/2 blktests] src/miniublk: switch to ioctl-encoded ublk commands
From: Shin'ichiro Kawasaki @ 2026-06-19  3:26 UTC (permalink / raw)
  To: Sebastian Chlad; +Cc: linux-block, Sebastian Chlad
In-Reply-To: <20260617072516.6238-2-sebastian.chlad@suse.com>

Hi Sebastian,

Thanks for the patches. I agree that this direction is good: it's the better
shift away from the legacy interface.

One point I noticed is that src/miniublk.c can no longer be built with the
kernel headers of the LTS kernel version v6.1.y, probably (v5.15.y does not have
ublk and v6.6.y supports the new interface). This is a rather small window, and
may be acceptable but I wonder what you think about it

If we drop the miniublk build with v6.1.y kernel headers, it might be the better
to check before building miniublk. I quickly created a Makefile change [1] for
that purpose.

Also, please find a comment in line below.

On Jun 17, 2026 / 09:25, Sebastian Chlad wrote:
> Kernels built without CONFIG_BLKDEV_UBLK_LEGACY_OPCODES reject the
> legacy raw UBLK_CMD_* and UBLK_IO_* opcodes. Switch miniublk to use
> the ioctl-encoded UBLK_U_CMD_* and UBLK_U_IO_* variants defined in
> linux/ublk_cmd.h instead.
> 
> For IO commands, the ioctl-encoded opcode is used for submission while
> _IOC_NR() extracts the raw NR bits for build_user_data(), keeping the
> user_data tag encoding intact.
> 
> Signed-off-by: Sebastian Chlad <sebastian.chlad@suse.com>
> ---
>  src/miniublk.c | 30 +++++++++++++++---------------
>  1 file changed, 15 insertions(+), 15 deletions(-)
> 
> diff --git a/src/miniublk.c b/src/miniublk.c
> index f98f850..5a35ca7 100644
> --- a/src/miniublk.c
> +++ b/src/miniublk.c
[...]
> @@ -624,9 +624,9 @@ static int ublk_queue_io_cmd(struct ublk_queue *q,
>  		return 0;
>  
>  	if (io->flags & UBLKSRV_NEED_COMMIT_RQ_COMP)
> -		cmd_op = UBLK_IO_COMMIT_AND_FETCH_REQ;
> -	else if (io->flags & UBLKSRV_NEED_FETCH_RQ)
> -		cmd_op = UBLK_IO_FETCH_REQ;
> +		cmd_op = UBLK_U_IO_COMMIT_AND_FETCH_REQ;
> +	else
> +		cmd_op = UBLK_U_IO_FETCH_REQ;

The hunk above changes the "else if" part, is this intentional?


[1]

diff --git a/src/Makefile b/src/Makefile
index d8833bf..adfe3ef 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -8,6 +8,10 @@ HAVE_C_MACRO = $(shell if echo "$(H)include <$(1)>" |	\
 		$(CC) $(CFLAGS) -E - 2>&1 /dev/null | grep $(2) > /dev/null 2>&1; \
 		then echo 1;else echo 0; fi)
 
+HAVE_C_DEF = $(shell if echo -e "$(H)include <$(1)>\n#ifdef $(2)\nHAVE_$(2)\n#endif" | \
+		$(CC) $(CFLAGS) -E - 2>&1 /dev/null | grep HAVE_$(2) > /dev/null 2>&1; \
+		then echo 1;else echo 0; fi)
+
 C_TARGETS := \
 	dio-offsets \
 	loblksize \
@@ -27,6 +31,7 @@ C_UBLK_TARGETS := miniublk
 
 HAVE_LIBURING := $(call HAVE_C_MACRO,liburing.h,IORING_OP_URING_CMD)
 HAVE_UBLK_HEADER := $(call HAVE_C_HEADER,linux/ublk_cmd.h,1)
+HAVE_NEW_UBLK_INTF := $(call HAVE_C_DEF,linux/ublk_cmd.h,UBLK_U_CMD_START_DEV)
 
 CXX_TARGETS := \
 	discontiguous-io
@@ -37,8 +42,12 @@ SYZKALLER_TARGETS := \
 TARGETS := $(C_TARGETS) $(CXX_TARGETS) $(SYZKALLER_TARGETS)
 
 ifeq ($(HAVE_UBLK_HEADER), 1)
+ifeq ($(HAVE_NEW_UBLK_INTF), 1)
 C_URING_TARGETS += $(C_UBLK_TARGETS)
 else
+$(info Skip $(C_UBLK_TARGETS) build due to missing new ublk interface(v6.4+))
+endif
+else
 $(info Skip $(C_UBLK_TARGETS) build due to missing kernel header(v6.0+))
 endif
 


^ permalink raw reply related

* Re: [PATCH 2/2] dm-raid1: don't fail the mirror for invalid I/O errors
From: Vjaceslavs Klimovs @ 2026-06-19  2:07 UTC (permalink / raw)
  To: Dr. David Alan Gilbert
  Cc: Keith Busch, regressions, Keith Busch, dm-devel, linux-block,
	mpatocka
In-Reply-To: <ajLSaiKXWIekcA97@gallifrey>

On Tue, Jun 16, 2026 at 08:05:53AM -0700, Keith Busch wrote:
> For DM_IO_BIO requests, do_region() built each destination bio by walking
> the source bio's biovec and re-adding the pages one at a time, tracking
> the remaining transfer in sectors.

Thanks Keith, this clears the dm-mirror hang for me.

I reproduced the original regression in a nested-QEMU harness: an LVM
legacy mirror LV (lvcreate --type mirror -m1, core log) on virtio-blk
PVs, used as the backing disk of a nested guest that issues misaligned
O_DIRECT reads via virtio-blk with cache=none,aio=native.

Without this series, on v7.1-rc7 (424280953322), the mirror read path
splats and then wedges:

  device-mapper: raid1: Mirror read failed from 252:0. Trying
alternative device.
  WARNING: block/bio.c:1044 at bio_add_page+0x108/0x200, CPU#1: kworker/1:1
  Workqueue: kmirrord do_mirror
  RIP: 0010:bio_add_page+0x108/0x200

kmirrord then stays stuck in do_mirror and the I/O never completes (the
guest hangs indefinitely).

With 1/2 and 2/2 applied on the same base, the WARN is gone, kmirrord no
longer wedges, the array is not degraded, and the misaligned O_DIRECT
read now completes normally instead of hanging. The --type raid1 (dm-raid
on md/raid1) variant of the same test is also clean.


On Wed, Jun 17, 2026 at 9:59 AM Dr. David Alan Gilbert
<linux@treblig.org> wrote:
>
> * Keith Busch (kbusch@kernel.org) wrote:
> > On Wed, Jun 17, 2026 at 04:44:35PM +0000, Dr. David Alan Gilbert wrote:
> > > (It's a bit scary you're having to go around quite
> > > a few places and make similar fixes; I assume there
> > > are others that do similar things).
> >
> > Yes, I understand that. I'm looking into a common way to validate this.
> > The md raid doesn't have this problem because they always call
> > bio_split_to_limits() first, but that's not an optimal thing to do for
> > dm raid in the normal read/write path, so perhaps a common checker needs
> > to happen generically in the block layer. Yeah, I know I removed the
> > previous higher level validation ... I'll try find something less costly
> > than what we had before.
>
> OK, thanks again
> (and to Thomas for gluing my query to those other two which got this
> moving!)
>
> Dave.
> --
>  -----Open up your eyes, open up your mind, open up your code -------
> / Dr. David Alan Gilbert    |       Running GNU/Linux       | Happy  \
> \        dave @ treblig.org |                               | In Hex /
>  \ _________________________|_____ http://www.treblig.org   |_______/

^ permalink raw reply

* WARNING: at floppy_interrupt, CPU: swapper/NUM/NUM
From: sanan.hasanou @ 2026-06-18 22:26 UTC (permalink / raw)
  To: efremov, axboe, linux-block, linux-kernel; +Cc: syzkaller, contact

Good day, dear maintainers,

We found a bug using a modified version of syzkaller.

Kernel Branch: 7.0-rc1
Kernel Config: <https://drive.google.com/open?id=173DLEAEPKPhhR1TcqofdnkLpdoK7PMFl>
Unfortunately, we don't have any reproducer for this bug yet.
Thank you!

Best regards,
Sanan Hasanov

------------[ cut here ]------------
WARNING: at schedule_bh drivers/block/floppy.c:1000 [inline], CPU#0: swapper/0/1
WARNING: at floppy_interrupt+0x51b/0x560 drivers/block/floppy.c:1766, CPU#0: swapper/0/1
Modules linked in:
CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.0.0-rc1 #1 PREEMPT(full) 
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:schedule_bh drivers/block/floppy.c:1000 [inline]
RIP: 0010:floppy_interrupt+0x51b/0x560 drivers/block/floppy.c:1766
Code: 35 3a c8 54 0c 48 c7 c7 80 fa 4b 8c 48 c7 c2 c0 f7 4b 8c 48 c7 c1 40 f9 4b 8c e8 a0 4a 3b fb e9 af fe ff ff e8 66 d9 d5 fb 90 <0f> 0b 90 e9 e8 fc ff ff 44 89 f9 80 e1 07 38 c1 0f 8c 27 fc ff ff
RSP: 0018:ffffc90000007af8 EFLAGS: 00010006
RAX: ffffffff85ec786a RBX: ffffffff85ecf380 RCX: ffff888016aeba80
RDX: 0000000000010100 RSI: 0000000000000001 RDI: 0000000000000000
RBP: 0000000000000000 R08: ffffffff8f3e2467 R09: 1ffffffff1e7c48c
R10: dffffc0000000000 R11: fffffbfff1e7c48d R12: dffffc0000000000
R13: 0000000000000000 R14: 0000000002000011 R15: 0000000000000000
FS:  0000000000000000(0000) GS:ffff8880d98df000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff888012801000 CR3: 000000000e6ff000 CR4: 00000000000006f0
Call Trace:
 <IRQ>
 __handle_irq_event_percpu+0x1d9/0x5d0 kernel/irq/handle.c:209
 handle_irq_event_percpu kernel/irq/handle.c:246 [inline]
 handle_irq_event+0x90/0x1e0 kernel/irq/handle.c:263
 handle_edge_irq+0x239/0x9e0 kernel/irq/chip.c:855
 generic_handle_irq_desc include/linux/irqdesc.h:186 [inline]
 handle_irq arch/x86/kernel/irq.c:262 [inline]
 call_irq_handler arch/x86/kernel/irq.c:286 [inline]
 __common_interrupt+0xc5/0x170 arch/x86/kernel/irq.c:333
 common_interrupt+0x4a/0xc0 arch/x86/kernel/irq.c:326
 asm_common_interrupt+0x26/0x40 arch/x86/include/asm/idtentry.h:688
RIP: 0010:__raw_spin_unlock_irq include/linux/spinlock_api_smp.h:188 [inline]
RIP: 0010:_raw_spin_unlock_irq+0x19/0x30 kernel/locking/spinlock.c:202
Code: 00 02 00 00 75 db eb da e8 74 c0 a8 f5 5b c3 66 90 f3 0f 1e fa 0f 1f 44 00 00 e8 f2 b4 12 f6 e8 4d 86 41 f6 fb bf 01 00 00 00 <e8> d2 2a 07 f6 65 8b 05 8b 59 88 06 85 c0 74 01 c3 e8 41 c0 a8 f5
RSP: 0018:ffffc90000007d58 EFLAGS: 00000246
RAX: 0000000000000001 RBX: ffffffff85358ab0 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000004 RDI: 0000000000000001
RBP: ffffc90000007ef8 R08: ffff88806ba2f683 R09: 1ffff1100d745ed0
R10: dffffc0000000000 R11: ffffed100d745ed1 R12: ffff88801d085478
R13: dffffc0000000000 R14: ffff88806ba2f680 R15: ffff88806ba2f698
 expire_timers kernel/time/timer.c:1798 [inline]
 __run_timers kernel/time/timer.c:2373 [inline]
 __run_timer_base+0x700/0xa30 kernel/time/timer.c:2385
 run_timer_base kernel/time/timer.c:2394 [inline]
 run_timer_softirq+0xbc/0x190 kernel/time/timer.c:2404
 handle_softirqs+0x1ed/0x700 kernel/softirq.c:622
 __do_softirq kernel/softirq.c:656 [inline]
 invoke_softirq kernel/softirq.c:496 [inline]
 __irq_exit_rcu+0x8e/0x270 kernel/softirq.c:723
 irq_exit_rcu+0xe/0x30 kernel/softirq.c:739
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1056 [inline]
 sysvec_apic_timer_interrupt+0x92/0xb0 arch/x86/kernel/apic/apic.c:1056
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
RIP: 0010:clear_pages arch/x86/include/asm/page_64.h:103 [inline]
RIP: 0010:clear_page arch/x86/include/asm/page_64.h:114 [inline]
RIP: 0010:clear_highpage_kasan_tagged include/linux/highmem.h:344 [inline]
RIP: 0010:kernel_init_pages mm/page_alloc.c:1265 [inline]
RIP: 0010:post_alloc_hook+0x3ff/0x480 mm/page_alloc.c:1887
Code: 03 49 c7 c7 20 2e 43 8e 49 c1 ef 03 eb 2f 48 8b 3d c6 74 21 0c 49 c1 e5 06 4c 29 ef 4c 01 e7 b9 00 10 00 00 31 c0 48 c1 e9 03 <f3> 48 ab 49 81 c4 00 10 00 00 49 ff ce 0f 84 31 fd ff ff 48 b8 00
RSP: 0018:ffffc9000001eed8 EFLAGS: 00000216
RAX: 0000000000000000 RBX: 1ffffffff1c865c6 RCX: 0000000000000200
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff88801dc20000
RBP: 0000000000000003 R08: ffffffff9049fd6f R09: 0000000000000000
R10: ffffed1003b84000 R11: fffffbfff2093fae R12: fffa80001dc20000
R13: fffa800000000000 R14: 0000000000000008 R15: 1ffffffff1c865c4
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x2240/0x2330 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x20e/0x3d0 mm/page_alloc.c:5250
 __alloc_pages_noprof+0xf/0x30 mm/page_alloc.c:5284
 vm_area_alloc_pages mm/vmalloc.c:-1 [inline]
 __vmalloc_area_node mm/vmalloc.c:3876 [inline]
 __vmalloc_node_range_noprof+0x79f/0x1580 mm/vmalloc.c:4064
 __vmalloc_node_noprof mm/vmalloc.c:4124 [inline]
 vzalloc_noprof+0xdf/0x120 mm/vmalloc.c:4202
 allocate_partitions block/partitions/core.c:101 [inline]
 check_partition block/partitions/core.c:123 [inline]
 blk_add_partitions block/partitions/core.c:590 [inline]
 bdev_disk_changed+0x628/0x1810 block/partitions/core.c:694
 blkdev_get_whole+0x37e/0x500 block/bdev.c:764
 bdev_open+0x35b/0xdc0 block/bdev.c:973
 bdev_file_open_by_dev+0x1c3/0x240 block/bdev.c:1075
 disk_scan_partitions+0x1be/0x2c0 block/genhd.c:387
 add_disk_final block/genhd.c:416 [inline]
 add_disk_fwnode+0x31e/0x470 block/genhd.c:610
 add_disk include/linux/blkdev.h:785 [inline]
 brd_alloc+0x5de/0x810 drivers/block/brd.c:340
 brd_init+0xc6/0x120 drivers/block/brd.c:420
 do_one_initcall+0x1a1/0x530 init/main.c:1382
 do_initcall_level+0x117/0x1a0 init/main.c:1444
 do_initcalls+0xe1/0x150 init/main.c:1460
 kernel_init_freeable+0x207/0x310 init/main.c:1692
 kernel_init+0x22/0x1d0 init/main.c:1582
 ret_from_fork+0x608/0xc40 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x11/0x20 arch/x86/entry/entry_64.S:245
 </TASK>
----------------
Code disassembly (best guess):
   0:	00 02                	add    %al,(%rdx)
   2:	00 00                	add    %al,(%rax)
   4:	75 db                	jne    0xffffffe1
   6:	eb da                	jmp    0xffffffe2
   8:	e8 74 c0 a8 f5       	call   0xf5a8c081
   d:	5b                   	pop    %rbx
   e:	c3                   	ret
   f:	66 90                	xchg   %ax,%ax
  11:	f3 0f 1e fa          	endbr64
  15:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  1a:	e8 f2 b4 12 f6       	call   0xf612b511
  1f:	e8 4d 86 41 f6       	call   0xf6418671
  24:	fb                   	sti
  25:	bf 01 00 00 00       	mov    $0x1,%edi
* 2a:	e8 d2 2a 07 f6       	call   0xf6072b01 <-- trapping instruction
  2f:	65 8b 05 8b 59 88 06 	mov    %gs:0x688598b(%rip),%eax        # 0x68859c1
  36:	85 c0                	test   %eax,%eax
  38:	74 01                	je     0x3b
  3a:	c3                   	ret
  3b:	e8 41 c0 a8 f5       	call   0xf5a8c081

<<<<<<<<<<<<<<< tail report >>>>>>>>>>>>>>>

^ permalink raw reply

* Re: [PATCH] virtio-blk: use little-endian types for the zoned fields
From: Stefan Hajnoczi @ 2026-06-18 15:18 UTC (permalink / raw)
  To: Michael Bommarito
  Cc: Michael S . Tsirkin, Jason Wang, Stefano Garzarella,
	Dmitry Fomichev, Damien Le Moal, Jens Axboe, Paolo Bonzini,
	virtualization, linux-block, linux-kernel
In-Reply-To: <20260617151727.4071754-1-michael.bommarito@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 2446 bytes --]

On Wed, Jun 17, 2026 at 11:17:27AM -0400, Michael Bommarito wrote:
> The zoned block-device fields in the virtio-blk header are typed
> __virtio{32,64}, so their endianness follows VIRTIO_F_VERSION_1. The
> zoned feature is only defined for VIRTIO 1.x devices, and the virtio
> specification defines all of its fields as little-endian. Commit
> b16a1756c716 ("virtio_blk: mark all zone fields LE") tagged them
> __le* for exactly this reason, but commit f1ba4e674feb ("virtio-blk:
> fix to match virtio spec") re-applied the reviewed version of the
> original zoned series -- which predated b16a1756 -- and silently
> restored the __virtio* typing together with the matching
> virtio*_to_cpu() / virtio_cread() accessors in the driver.
> 
> Restore the little-endian typing for the zoned configuration-space
> characteristics, the zone descriptor, the zone report header and the
> ZONE_APPEND in-header sector, and read them with le*_to_cpu() and
> virtio_cread_le() to match.
> 
> There is no functional change on any spec-compliant device: zoned
> requires VIRTIO_F_VERSION_1, and for a VERSION_1 device
> virtio*_to_cpu() is identical to le*_to_cpu(). The change makes the
> uapi types describe the actual wire format and removes a latent
> endianness mismatch for a (non-conformant) legacy device on a
> big-endian guest.
> 
> Fixes: f1ba4e674feb ("virtio-blk: fix to match virtio spec")
> Suggested-by: Michael S. Tsirkin <mst@redhat.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
> Testing:
>  - Builds with no new warnings; sparse endian-clean (C=2,
>    __CHECK_ENDIAN__, CONFIG_BLK_DEV_ZONED=y) both before and after.
>  - Booted under QEMU with a host-managed zoned device exposed through
>    virtio-blk. Zone revalidation, blkzone report and a sequential
>    write / write-pointer check return correct values; blktests zbd
>    device tests 001-006 (sysfs+ioctl, report zone, reset, write split,
>    write ordering, revalidate) pass, with results identical before and
>    after this change -- expected, since on a VIRTIO_F_VERSION_1 device
>    virtio*_to_cpu() == le*_to_cpu().
> 
>  drivers/block/virtio_blk.c      | 38 +++++++++++++++------------------
>  include/uapi/linux/virtio_blk.h | 18 ++++++++--------
>  2 files changed, 26 insertions(+), 30 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* [PATCH blktests] Fix _get_page_size()
From: Jeff Moyer @ 2026-06-18 14:41 UTC (permalink / raw)
  To: linux-block, shinichiro.kawasaki

There is no CONFIG_PAGE_SHIFT stored in /boot/config-`uname -r` on RHEL
systems (maybe all systems?).  As a result, tests that make use of
_get_page_size() were doing the wrong thing.  For example, throtl/002
used it to calculate I/O sizes for direct IO.  Those sizes ended up not
being a multiple of the logical block size, and hence throtl/002 was
failing.

Fixes: 8eca9fa ("common/rc, scsi/011, zbd/010: introduce _page_size_equals() helper")
Signed-off-by: Jeff Moyer <jmoyer@redhat.com>

diff --git a/common/rc b/common/rc
index 20f7c7a..d60a125 100644
--- a/common/rc
+++ b/common/rc
@@ -562,13 +562,8 @@ _have_systemctl_unit() {
 	return 0
 }
 
-# Get system page size from kernel conguration
 _get_page_size() {
-	local page_shift
-
-	page_shift=$(_get_kernel_option PAGE_SHIFT)
-
-	echo $((1<< page_shift))
+	getconf PAGE_SIZE
 }
 
 # Check if the system page size matches the required size (in bytes).


^ permalink raw reply related

* Re: [PATCH v6 1/4] block: add task-context bio completion infrastructure
From: Jan Kara @ 2026-06-18 14:26 UTC (permalink / raw)
  To: Tal Zussman
  Cc: Christoph Hellwig, Jan Kara, Jens Axboe, Matthew Wilcox (Oracle),
	Christian Brauner, Darrick J. Wong, Carlos Maiolino,
	Alexander Viro, Dave Chinner, Bart Van Assche, linux-block,
	linux-kernel, linux-xfs, linux-fsdevel, linux-mm, Gao Xiang
In-Reply-To: <cuyklvhnt37xwk3jlr2iyyxxkzvt4bsf5iqv2gag4ixdicnbk2@rfqgjlclwesz>

On Mon 01-06-26 13:04:41, Jan Kara wrote:
> On Fri 29-05-26 16:46:15, Tal Zussman wrote:
> > On 5/27/26 9:00 AM, Christoph Hellwig wrote:
> > > On Wed, May 27, 2026 at 11:42:28AM +0200, Jan Kara wrote:
> > >> > I ran some experiments with fio on both XFS and a raw block device. Five
> > >> > iterations each for 60s. Results below.
> > >> > 
> > >> > TLDR: Removing the delay doesn't significantly decrease user-visible
> > >> > latency or otherwise improve performance, but does significantly reduce
> > >> > throughput and increase context switches in some workloads (e.g. C).
> > >> > I think it makes sense to leave the delay as-is. Thoughts?
> > >> 
> > >> Thanks for the test! One question below:
> > > 
> > > Thanks from me as well!
> > > 
> > >> 
> > >> > Results:
> > >> > 
> > >> > Workloads (all `uncached=1`):
> > >> >   A: rw=write     bs=128k iodepth=1   ioengine=pvsync2     # XFS
> > >> >   B: rw=write     bs=128k iodepth=128 ioengine=io_uring    # XFS
> > >> >   C: rw=randwrite bs=4k   iodepth=32  ioengine=io_uring    # XFS
> > >> >   D: rw=rw 50/50  bs=64k  iodepth=32  ioengine=io_uring    # XFS
> > >> >   E: rw=write     bs=128k iodepth=128 ioengine=io_uring    # raw /dev/nvmeXn1
> > >> >   F: rw=write     bs=128k iodepth=128 numjobs=4
> > >> >      + vm.dirty_bytes=64MB, vm.dirty_background_bytes=32MB # XFS
> > >> > 
> > >> > Mean ± stddev across 5 iterations:
> > >> > 
> > >> >     metric                     delay=1           delay=0     delta
> > >> >     --------------------------------------------------------------
> > >> > 
> > >> >   A seq 128k qd1
> > >> >     BW (MB/s)                4333 ± 27         4374 ± 34     +0.9%
> > >> >     p99   (us)              36.2 ± 0.8        35.8 ± 0.4     -1.1%
> > >> >     p999  (us)               3260 ± 75         3228 ± 29     -1.0%
> > >> >     ctx-switches          184 k ± 59 k     3.68 M ± 65 k    +1903%
> > >> >     cs / io                0.09 ± 0.03       1.86 ± 0.03    +1888%
> > >> >     avg bios/run            80.4 ± 0.6         1.1 ± 0.0    -98.7%
> > >> 
> > >> So 1 jiffie delay is (with default HZ=1000) 1ms. That means for this load
> > >> the completion latency should be at least 1000us but your results show p99
> > >> latency of 36. What am I missing?
> > > 
> > > Yes, this looks a bit odd.  Unless there's multiple threads submitting
> > > and somehow the completions get batched this should complete one
> > > bio at a time and be the worst case for the delay scheme.
> > 
> > Sorry, I should've clarified - the latency here is the userspace-visible
> > I/O completion latency (i.e. fio's clat value).
> > 
> > I ran again and traced to get the actual time from __bio_complete_in_task()
> > to calling ->bi_end_io(). The results match the 1 jiffie delay now:
> > 
> >   metric                  delay=1  delay=0
> > 
> >   A seq 128k qd1
> >     fio clat p99             38us     36us
> >     bio cb p50             1.23ms    2.5us
> >     bio cb p99             4.13ms   1.44ms
> >     bio cb p999            5.01ms   2.63ms
> 
> So I'm clearly missing something fundamental as I don't see how can fio
> reported IO completion time be lower than the end_io callback latency...
> Ahh, it is the strange meaning of clat in fio in combination with sync
> engine where clat means: "how long after the syscall has returned the data
> is ready". Which for sync engine is immediately so the clat number is
> meaningless. I think reporting 'lat' numbers from fio would make more
> sense but whatever.
> 
> The bio cb latency indeed looks like what I'd roughly expect now. And
> notice how the median latency of IO completion is 1.23ms in delay=1 case
> and your throughput isn't abbysmal only because writes end up accumulating
> in the page cache and writeback infrastructure ends up submitting a lot of
> writeback IOs in parallel (you have ~80 bios to complete per run which
> amortizes the latency to decent level).
> 
> However if you'd have IO that were to use BIO_COMPLETE_IN_TASK
> infrastructure which doesn't have so many IOs in flight (like direct IO
> with lower queue depth which has to do extent conversion on completion),
> you would very much see the latency hit on your throughput as well. In the
> extreme case of qd=1 direct IO you'd reduce the throughput to ~4MB/s.
> 
> Now I'm not saying the delay is bad - it is a tradeoff with clear wins in
> CPU overhead your benchmarks are showing. I just wanted to point out
> there's also the cost side which your benchmarks don't show very clearly.
> So we might need to keep some stats showing how many IO completions we are
> offloading per second on each CPU and switch to delaying the work only once
> it crosses a threshold like 1000000/HZ per second or so (so we at most
> double the IO latency by delaying the end_io callback).

Any progress here? The patchset looks really promising so I'd love to have
it completed :)

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH v2 1/7] rust: module: add `THIS_MODULE` const to `ModuleMetadata` trait
From: Gary Guo @ 2026-06-18 14:16 UTC (permalink / raw)
  To: Alvin Sun, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Greg Kroah-Hartman,
	Rafael J. Wysocki, David Airlie, Simona Vetter, Daniel Almeida,
	Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar, Breno Leitao,
	Jens Axboe
  Cc: rust-for-linux, linux-modules, driver-core, dri-devel, nova-gpu,
	linux-kselftest, kunit-dev, linux-block
In-Reply-To: <20260521-fix-fops-owner-v2-1-fd99079c5a04@linux.dev>

On Thu May 21, 2026 at 8:52 AM BST, Alvin Sun wrote:
> Add a `THIS_MODULE` const to the `ModuleMetadata` trait so that
> modules can provide their `ThisModule` pointer usable in const
> contexts such as static file_operations.
>
> Move the `THIS_MODULE` static from the `module!` macro into the
> `ModuleMetadata` impl, and update `__init` to use
> `LocalModule::THIS_MODULE` instead.

Perhaps you could mention that this is made possible by const_refs_to_static
which is stable since the MSRV bump.

Best,
Gary

>
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
>  rust/kernel/lib.rs    |  3 +++
>  rust/macros/module.rs | 34 +++++++++++++++++-----------------
>  2 files changed, 20 insertions(+), 17 deletions(-)
>
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index b72b2fbe046d6..f0cf0705d9697 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -184,6 +184,9 @@ fn init(module: &'static ThisModule) -> impl pin_init::PinInit<Self, error::Erro
>  pub trait ModuleMetadata {
>      /// The name of the module as specified in the `module!` macro.
>      const NAME: &'static crate::str::CStr;
> +
> +    /// The module's `THIS_MODULE` pointer.
> +    const THIS_MODULE: ThisModule;
>  }
>  
>  /// Equivalent to `THIS_MODULE` in the C API.
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index 06c18e2075083..b6d7b3299fbf9 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -497,28 +497,28 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>          /// Used by the printing macros, e.g. [`info!`].
>          const __LOG_PREFIX: &[u8] = #name_cstr.to_bytes_with_nul();
>  
> -        // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
> -        // freed until the module is unloaded.
> -        #[cfg(MODULE)]
> -        static THIS_MODULE: ::kernel::ThisModule = unsafe {
> -            extern "C" {
> -                static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
> -            };
> -
> -            ::kernel::ThisModule::from_ptr(__this_module.get())
> -        };
> -
> -        #[cfg(not(MODULE))]
> -        static THIS_MODULE: ::kernel::ThisModule = unsafe {
> -            ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
> -        };
> -
>          /// The `LocalModule` type is the type of the module created by `module!`,
>          /// `module_pci_driver!`, `module_platform_driver!`, etc.
>          type LocalModule = #type_;
>  
>          impl ::kernel::ModuleMetadata for #type_ {
>              const NAME: &'static ::kernel::str::CStr = #name_cstr;
> +
> +            #[cfg(MODULE)]
> +            const THIS_MODULE: ::kernel::ThisModule = {
> +                extern "C" {
> +                    static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
> +                }
> +
> +                // SAFETY: `__this_module` is constructed by the kernel at load time
> +                // and lives until the module is unloaded.
> +                unsafe { ::kernel::ThisModule::from_ptr(__this_module.get()) }
> +            };
> +
> +            #[cfg(not(MODULE))]
> +            const THIS_MODULE: ::kernel::ThisModule = unsafe {
> +                ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
> +            };
>          }
>  
>          // Double nested modules, since then nobody can access the public items inside.
> @@ -616,7 +616,7 @@ pub extern "C" fn #ident_exit() {
>                  /// This function must only be called once.
>                  unsafe fn __init() -> ::kernel::ffi::c_int {
>                      let initer = <super::super::LocalModule as ::kernel::InPlaceModule>::init(
> -                        &super::super::THIS_MODULE
> +                        &<super::super::LocalModule as ::kernel::ModuleMetadata>::THIS_MODULE
>                      );
>                      // SAFETY: No data race, since `__MOD` can only be accessed by this module
>                      // and there only `__init` and `__exit` access it. These functions are only



^ permalink raw reply

* Re: [PATCH v2 4/7] rust: drm: set fops.owner from driver module pointer
From: Gary Guo @ 2026-06-18 14:15 UTC (permalink / raw)
  To: Alvin Sun, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Greg Kroah-Hartman,
	Rafael J. Wysocki, David Airlie, Simona Vetter, Daniel Almeida,
	Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar, Breno Leitao,
	Jens Axboe
  Cc: rust-for-linux, linux-modules, driver-core, dri-devel, nova-gpu,
	linux-kselftest, kunit-dev, linux-block
In-Reply-To: <20260521-fix-fops-owner-v2-4-fd99079c5a04@linux.dev>

On Thu May 21, 2026 at 8:52 AM BST, Alvin Sun wrote:
> Change `create_fops()` to accept an owner module pointer instead of
> hardcoding `null_mut()`, ensuring the kernel correctly tracks the
> module owning the DRM device's file operations.
>
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
>  rust/kernel/drm/device.rs  | 3 ++-
>  rust/kernel/drm/gem/mod.rs | 4 ++--
>  2 files changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
> index 403fc35353c74..53e44a780ae97 100644
> --- a/rust/kernel/drm/device.rs
> +++ b/rust/kernel/drm/device.rs
> @@ -111,7 +111,8 @@ impl<T: drm::Driver> Device<T> {
>          fops: &Self::GEM_FOPS,
>      };
>  
> -    const GEM_FOPS: bindings::file_operations = drm::gem::create_fops();
> +    const GEM_FOPS: bindings::file_operations =
> +        drm::gem::create_fops(<T::ThisModule as crate::ModuleMetadata>::THIS_MODULE.as_ptr());

I wonder if the assoc type should just be called `Owner` or `OwnerModule`?

Best.
Gary

>  
>      /// Create a new `drm::Device` for a `drm::Driver`.
>      pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<ARef<Self>> {
> diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
> index 01b5bd47a3332..9a203efc59116 100644
> --- a/rust/kernel/drm/gem/mod.rs
> +++ b/rust/kernel/drm/gem/mod.rs
> @@ -357,10 +357,10 @@ impl<T: DriverObject> AllocImpl for Object<T> {
>      };
>  }
>  
> -pub(super) const fn create_fops() -> bindings::file_operations {
> +pub(super) const fn create_fops(owner: *mut bindings::module) -> bindings::file_operations {
>      let mut fops: bindings::file_operations = pin_init::zeroed();
>  
> -    fops.owner = core::ptr::null_mut();
> +    fops.owner = owner;
>      fops.open = Some(bindings::drm_open);
>      fops.release = Some(bindings::drm_release);
>      fops.unlocked_ioctl = Some(bindings::drm_ioctl);



^ permalink raw reply

* Re: [PATCH v2 2/7] rust: macros: auto-insert ThisModule in #[vtable]
From: Gary Guo @ 2026-06-18 14:13 UTC (permalink / raw)
  To: Alvin Sun, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Greg Kroah-Hartman,
	Rafael J. Wysocki, David Airlie, Simona Vetter, Daniel Almeida,
	Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar, Breno Leitao,
	Jens Axboe
  Cc: rust-for-linux, linux-modules, driver-core, dri-devel, nova-gpu,
	linux-kselftest, kunit-dev, linux-block
In-Reply-To: <20260521-fix-fops-owner-v2-2-fd99079c5a04@linux.dev>

On Thu May 21, 2026 at 8:52 AM BST, Alvin Sun wrote:
> Auto-add `type ThisModule: ::kernel::ModuleMetadata;` as a required
> associated type on the trait side if not already defined, and
> auto-insert `type ThisModule = crate::LocalModule;` on the impl side
> if not explicitly provided, eliminating the need to manually declare
> and implement `ThisModule` in every vtable trait and impl.
>
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>

Suggested-by: Gary Guo <gary@garyguo.net>
Link: https://lore.kernel.org/all/DIMMWHUOLPSH.13JFRHDKDQJGO@garyguo.net

> ---
>  rust/macros/lib.rs    |  6 ++++++
>  rust/macros/vtable.rs | 38 +++++++++++++++++++++++++++++++++++++-
>  2 files changed, 43 insertions(+), 1 deletion(-)
>
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 2cfd59e0f9e7c..d35e45ea745c0 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -176,6 +176,12 @@ pub fn module(input: TokenStream) -> TokenStream {
>  ///
>  /// This macro should not be used when all functions are required.
>  ///
> +/// Additionally, this macro automatically handles the `ThisModule`
> +/// associated type: on the trait side, `type ThisModule: ModuleMetadata;`
> +/// is added as a required associated type if not already defined; on the
> +/// impl side, `type ThisModule = LocalModule;` is automatically inserted
> +/// if not explicitly defined.
> +///
>  /// # Examples
>  ///
>  /// ```
> diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
> index c6510b0c4ea1d..d3d0e9cbd7172 100644
> --- a/rust/macros/vtable.rs
> +++ b/rust/macros/vtable.rs
> @@ -23,6 +23,7 @@
>  
>  fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
>      let mut gen_items = Vec::new();
> +    let mut has_this_module = false;
>  
>      gen_items.push(parse_quote! {
>           /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
> @@ -30,6 +31,28 @@ fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
>           const USE_VTABLE_ATTR: ();
>      });
>  
> +    // Detect existing type ThisModule so we don't add a duplicate.
> +    for i in &item.items {
> +        if let TraitItem::Type(type_item) = i {
> +            if type_item.ident == "ThisModule" {
> +                has_this_module = true;
> +            }
> +        }
> +    }
> +
> +    // Add `type ThisModule: ModuleMetadata` as a required associated type if
> +    // the trait does not already define it. No default is used because
> +    // `associated_type_defaults` is unstable (issue #29661).

I don't think this is relevant. What's the sensible default anyway?

> +    if !has_this_module {

Perhaps just make this an one liner :

    if !item.items.iter().any(|i| matches!(item, TraitItem::Type(t) if t.ident == "ThisModule")) {

> +        gen_items.push(parse_quote! {
> +            /// The module implementing this vtable trait.
> +            ///
> +            /// Automatically set to `crate::LocalModule` by the `#[vtable]`
> +            /// impl macro.
> +            type ThisModule: ::kernel::ModuleMetadata;
> +        });
> +    }
> +
>      for item in &item.items {
>          if let TraitItem::Fn(fn_item) = item {
>              let name = &fn_item.sig.ident;
> @@ -58,18 +81,31 @@ fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
>  fn handle_impl(mut item: ItemImpl) -> Result<ItemImpl> {
>      let mut gen_items = Vec::new();
>      let mut defined_consts = HashSet::new();
> +    let mut defined_types = HashSet::new();

I'd just rename `defined_consts` to `defined_items` to reuse the same set as
there cannot be assoc items with same name anyway.

Best,
Gary

>  
> -    // Iterate over all user-defined constants to gather any possible explicit overrides.
> +    // Iterate over all user-defined constants and types to gather any possible explicit overrides.
>      for item in &item.items {
>          if let ImplItem::Const(const_item) = item {
>              defined_consts.insert(const_item.ident.clone());
>          }
> +        if let ImplItem::Type(type_item) = item {
> +            defined_types.insert(type_item.ident.clone());
> +        }
>      }
>  
>      gen_items.push(parse_quote! {
>          const USE_VTABLE_ATTR: () = ();
>      });
>  
> +    // Auto-insert `type ThisModule = crate::LocalModule` if not explicitly defined.
> +    // `crate::LocalModule` resolves to the real module type (via `module!`) or a
> +    // dummy fallback in non-module contexts (e.g., doctests).
> +    if !defined_types.contains(&parse_quote!(ThisModule)) {
> +        gen_items.push(parse_quote! {
> +            type ThisModule = crate::LocalModule;
> +        });
> +    }
> +
>      for item in &item.items {
>          if let ImplItem::Fn(fn_item) = item {
>              let name = &fn_item.sig.ident;



^ permalink raw reply

* Re: [PATCH] block: remove redundant GD_NEED_PART_SCAN in add_disk_final()
From: Christoph Hellwig @ 2026-06-18 14:07 UTC (permalink / raw)
  To: Connor Williamson
  Cc: axboe, linux-block, linux-kernel, stable, yukuai3, hch, jack,
	nh-open-source
In-Reply-To: <20260615130715.53693-1-connordw@amazon.com>

Looks good:

Reviewed-by: Christoph Hellwig <hch@lst.de>


^ permalink raw reply

* Re: [PATCH 1/1] block: validate user space vectors during extraction
From: Keith Busch @ 2026-06-18 13:51 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Keith Busch, linux-block, linux-fsdevel, dm-devel, axboe, brauner,
	djwong, viro, stable
In-Reply-To: <20260618134346.GA2752@lst.de>

On Thu, Jun 18, 2026 at 03:43:46PM +0200, Christoph Hellwig wrote:
> On Thu, Jun 18, 2026 at 07:17:35AM -0600, Keith Busch wrote:
> > > >  	if (iov_iter_is_bvec(iter)) {
> > > >  		bio_iov_bvec_set(bio, iter);
> > > > +
> > > > +		if (mp_bvec_iter_offset(bio->bi_io_vec, bio->bi_iter) &
> > > > +							vec_align_mask)
> > > > +			return -EINVAL;
> > > 
> > > Can you add a comment here?  Especially as the bvec iter doesn't actually
> > > require all individual bvecs to be aligned and I'm not entirely sure this
> > > handles all case - writing down the rules might help a bit with that.
> > 
> > The rationale is that the only iter_bvec users come from io_uring
> > registered buffers, which are virtually contiguous.
> 
> There's plenty of iov_iter_bdev users, and even without poking deep I
> know that two directly passed on bvecs from block-layer generated bios to
> the underlying file system's direct I/O code: loop and zloop.

Oh, I meant only users that go through this direct-io path, but you're
right, I was wrong about that too. The nvme target file backend can also
get here in addition to what you pointed out.
 
> So we need rules on what can be passed, and preferably some way to
> enforce that at least for debug builds.

Yeah.

^ permalink raw reply

* Re: [PATCH 1/1] block: validate user space vectors during extraction
From: Christoph Hellwig @ 2026-06-18 13:43 UTC (permalink / raw)
  To: Keith Busch
  Cc: Christoph Hellwig, Keith Busch, linux-block, linux-fsdevel,
	dm-devel, axboe, brauner, djwong, viro, stable
In-Reply-To: <ajPv7yOoYsR5O6kf@kbusch-mbp>

On Thu, Jun 18, 2026 at 07:17:35AM -0600, Keith Busch wrote:
> > >  	if (iov_iter_is_bvec(iter)) {
> > >  		bio_iov_bvec_set(bio, iter);
> > > +
> > > +		if (mp_bvec_iter_offset(bio->bi_io_vec, bio->bi_iter) &
> > > +							vec_align_mask)
> > > +			return -EINVAL;
> > 
> > Can you add a comment here?  Especially as the bvec iter doesn't actually
> > require all individual bvecs to be aligned and I'm not entirely sure this
> > handles all case - writing down the rules might help a bit with that.
> 
> The rationale is that the only iter_bvec users come from io_uring
> registered buffers, which are virtually contiguous.

There's plenty of iov_iter_bdev users, and even without poking deep I
know that two directly passed on bvecs from block-layer generated bios to
the underlying file system's direct I/O code: loop and zloop.

So we need rules on what can be passed, and preferably some way to
enforce that at least for debug builds.


^ permalink raw reply

* Re: [PATCH 1/1] block: validate user space vectors during extraction
From: Keith Busch @ 2026-06-18 13:17 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Keith Busch, linux-block, linux-fsdevel, dm-devel, axboe, brauner,
	djwong, viro, stable
In-Reply-To: <20260618102627.GA23200@lst.de>

On Thu, Jun 18, 2026 at 12:26:27PM +0200, Christoph Hellwig wrote:
> On Wed, Jun 17, 2026 at 04:32:35PM -0700, Keith Busch wrote:
> > @@ -1251,6 +1251,11 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
> >  
> >  	if (iov_iter_is_bvec(iter)) {
> >  		bio_iov_bvec_set(bio, iter);
> > +
> > +		if (mp_bvec_iter_offset(bio->bi_io_vec, bio->bi_iter) &
> > +							vec_align_mask)
> > +			return -EINVAL;
> 
> Can you add a comment here?  Especially as the bvec iter doesn't actually
> require all individual bvecs to be aligned and I'm not entirely sure this
> handles all case - writing down the rules might help a bit with that.

The rationale is that the only iter_bvec users come from io_uring
registered buffers, which are virtually contiguous. Subsequent IO
referencing it provides only an offset and a length, so the only
possible unlaignment could bne the first offset (we've already verified
the total length earlier). Every subsequent vector must be page aligned
at a minimum, which is the largest possible dma alignment the block
layer allows, so we don't need to check the rest.
 
> >  		ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec,
> >  				BIO_MAX_SIZE - bio->bi_iter.bi_size,
> > -				&bio->bi_vcnt, bio->bi_max_vecs, flags);
> > +				&bio->bi_vcnt, bio->bi_max_vecs,
> > +				vec_align_mask, flags);
> >  		if (ret <= 0) {
> > +			if (ret == -EINVAL) {
> > +				bio_release_pages(bio, false);
> > +				bio_clear_flag(bio, BIO_PAGE_PINNED);
> > +				bio->bi_iter.bi_size = 0;
> > +				bio->bi_vcnt = 0;
> > +				return ret;
> > +			}
> 
> Do we need all this cleanups beyoned the bio_release_pages()?  Most
> callers just free the bio, so should not care about it, and the error
> handling in __blkdev_direct_IO that calls bio_endio looks buggy for
> other reasons..

Yeah, it's exactly for the __blkdev_direct_IO() error handling, though I
think clearing either the PINNED flag or bi_vcnt is sufficient after
bio_release_pages(). The rest is just resetting the bio to the initial
state since I didn't want to return both an error and something that
looks like a partially constructed bio, even if no one currently cares.

But since you mention it, __blkdev_direct_IO's handling does look wrong,
so maybe I can clean that up first.

^ permalink raw reply

* Re: [PATCH v2 3/5] btrfs: deny freezing a device while it is being removed
From: Johannes Thumshirn @ 2026-06-18 12:56 UTC (permalink / raw)
  To: Christian Brauner, Chris Mason, Jens Axboe, David Sterba,
	Jan Kara
  Cc: Naohiro Aota, Josef Bacik, linux-btrfs, linux-block,
	linux-fsdevel
In-Reply-To: <20260616-work-super-freeze_deny_upstream-v2-3-b3567c7f994b@kernel.org>

Looks good,

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>


^ permalink raw reply

* Re: [PATCH v2 1/5] block: allow making a block device unfreezable
From: Johannes Thumshirn @ 2026-06-18 12:47 UTC (permalink / raw)
  To: Christian Brauner, Chris Mason, Jens Axboe, David Sterba,
	Jan Kara
  Cc: Naohiro Aota, Josef Bacik, linux-btrfs, linux-block,
	linux-fsdevel
In-Reply-To: <20260616-work-super-freeze_deny_upstream-v2-1-b3567c7f994b@kernel.org>

Looks good to me,

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>




^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox