[PATCH v2 30/83] block: rnull: add badblocks_once support
From: Andreas Hindborg
Date: Tue Jun 09 2026 - 15:16:06 EST
Add support for the badblocks_once feature, which automatically clears
bad blocks after they are encountered during I/O operations. This
matches the functionality in the C null_blk driver.
When badblocks_once is enabled:
- Bad blocks are checked during I/O requests as usual
- If a bad block is encountered, the I/O is marked as failed
- The bad block range is immediately cleared from the bad blocks table
- Subsequent I/O to the same sectors will succeed
This feature is useful for testing scenarios where bad blocks are
transient or where devices can recover from bad sectors after a single
access attempt.
The feature is configurable via the configfs badblocks_once attribute
and disabled by default, maintaining compatibility with existing
behavior.
Signed-off-by: Andreas Hindborg <a.hindborg@xxxxxxxxxx>
---
drivers/block/rnull/configfs.rs | 6 ++++++
drivers/block/rnull/rnull.rs | 21 +++++++++++++++------
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 4db3ba26c2d1..05229ba9173a 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -102,6 +102,7 @@ fn make_group(
discard: 10,
no_sched:11,
badblocks: 12,
+ badblocks_once: 13,
],
};
@@ -125,6 +126,7 @@ fn make_group(
discard: false,
no_sched: false,
bad_blocks: Arc::pin_init(BadBlocks::new(false), GFP_KERNEL)?,
+ bad_blocks_once: false,
}),
}),
core::iter::empty(),
@@ -195,6 +197,7 @@ struct DeviceConfigInner {
discard: bool,
no_sched: bool,
bad_blocks: Arc<BadBlocks>,
+ bad_blocks_once: bool,
}
#[vtable]
@@ -231,6 +234,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
discard: guard.discard,
no_sched: guard.no_sched,
bad_blocks: guard.bad_blocks.clone(),
+ bad_blocks_once: guard.bad_blocks_once,
})?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -383,3 +387,5 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
Ok(())
}
}
+
+configfs_simple_bool_field!(DeviceConfig, 13, bad_blocks_once);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 90dbf318c2f8..5486eb6dd921 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -159,6 +159,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
discard: module_parameters::discard.value(),
no_sched: module_parameters::no_sched.value(),
bad_blocks: Arc::pin_init(BadBlocks::new(false), GFP_KERNEL)?,
+ bad_blocks_once: false,
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -186,6 +187,7 @@ struct NullBlkOptions<'a> {
discard: bool,
no_sched: bool,
bad_blocks: Arc<BadBlocks>,
+ bad_blocks_once: bool,
}
struct NullBlkDevice;
@@ -204,6 +206,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
discard,
no_sched,
bad_blocks,
+ bad_blocks_once,
} = options;
let mut flags = mq::tag_set::Flags::default();
@@ -246,6 +249,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
memory_backed,
block_size: block_size.into(),
bad_blocks,
+ bad_blocks_once,
}),
GFP_KERNEL,
)?;
@@ -416,6 +420,7 @@ struct QueueData {
memory_backed: bool,
block_size: u64,
bad_blocks: Arc<BadBlocks>,
+ bad_blocks_once: bool,
}
#[pin_data]
@@ -465,12 +470,16 @@ fn queue_rq(
if queue_data.bad_blocks.enabled() {
let start = rq.sector();
let end = start + u64::from(rq.sectors());
- if !matches!(
- queue_data.bad_blocks.check(start..end),
- badblocks::BlockStatus::None
- ) {
- rq.data_ref().error.store(1, ordering::Relaxed);
- }
+ match queue_data.bad_blocks.check(start..end) {
+ badblocks::BlockStatus::None => {}
+ badblocks::BlockStatus::Acknowledged(range)
+ | badblocks::BlockStatus::Unacknowledged(range) => {
+ rq.data_ref().error.store(1, ordering::Relaxed);
+ if queue_data.bad_blocks_once {
+ queue_data.bad_blocks.set_good(range)?;
+ }
+ }
+ };
}
// TODO: Skip IO if bad block.
--
2.51.2