[PATCH v2 29/83] block: rnull: add badblocks support

From: Andreas Hindborg

Date: Tue Jun 09 2026 - 15:34:01 EST


Add badblocks support to the rnull driver with a configfs interface for
managing bad sectors.

- Configfs attribute for adding/removing bad blocks via "+start-end" and
"-start-end" syntax.
- Request handling that checks for bad blocks and returns IO errors.
- Updated request completion to handle error status properly.

The badblocks functionality is disabled by default and is enabled when
first bad block is added.

Signed-off-by: Andreas Hindborg <a.hindborg@xxxxxxxxxx>
---
drivers/block/rnull/configfs.rs | 63 ++++++++++++++++++++++++++++++++++++++---
drivers/block/rnull/rnull.rs | 46 ++++++++++++++++++++++++++----
2 files changed, 100 insertions(+), 9 deletions(-)

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index d9aead646ae0..4db3ba26c2d1 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -6,9 +6,12 @@
};
use kernel::{
bindings,
- block::mq::gen_disk::{
- GenDisk,
- GenDiskBuilder, //
+ block::{
+ badblocks::BadBlocks,
+ mq::gen_disk::{
+ GenDisk,
+ GenDiskBuilder, //
+ }, //
},
configfs::{
self,
@@ -26,7 +29,10 @@
kstrtobool_bytes,
CString, //
},
- sync::Mutex,
+ sync::{
+ Arc,
+ Mutex, //
+ },
time, //
};
use macros::{
@@ -95,6 +101,7 @@ fn make_group(
home_node: 9,
discard: 10,
no_sched:11,
+ badblocks: 12,
],
};

@@ -117,6 +124,7 @@ fn make_group(
home_node: bindings::NUMA_NO_NODE,
discard: false,
no_sched: false,
+ bad_blocks: Arc::pin_init(BadBlocks::new(false), GFP_KERNEL)?,
}),
}),
core::iter::empty(),
@@ -186,6 +194,7 @@ struct DeviceConfigInner {
home_node: i32,
discard: bool,
no_sched: bool,
+ bad_blocks: Arc<BadBlocks>,
}

#[vtable]
@@ -221,6 +230,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
home_node: guard.home_node,
discard: guard.discard,
no_sched: guard.no_sched,
+ bad_blocks: guard.bad_blocks.clone(),
})?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -328,3 +338,48 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
);

configfs_simple_bool_field!(DeviceConfig, 11, no_sched);
+
+#[vtable]
+impl configfs::AttributeOperations<12> for DeviceConfig {
+ type Data = DeviceConfig;
+
+ fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+ let ret = this.data.lock().bad_blocks.show(page, false);
+ if ret < 0 {
+ Err(Error::from_errno(ret as c_int))
+ } else {
+ Ok(ret as usize)
+ }
+ }
+
+ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
+ // This attribute can be set while device is powered.
+
+ for line in core::str::from_utf8(page)?.lines() {
+ let mut chars = line.chars();
+ match chars.next() {
+ Some(sign @ '+' | sign @ '-') => {
+ if let Some((start, end)) = chars.as_str().split_once('-') {
+ let start: u64 = start.parse().map_err(|_| EINVAL)?;
+ let end: u64 = end.parse().map_err(|_| EINVAL)?;
+
+ if start > end {
+ return Err(EINVAL);
+ }
+
+ this.data.lock().bad_blocks.enable();
+
+ if sign == '+' {
+ this.data.lock().bad_blocks.set_bad(start..=end, true)?;
+ } else {
+ this.data.lock().bad_blocks.set_good(start..=end)?;
+ }
+ }
+ }
+ _ => return Err(EINVAL),
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 73f14d6e379f..90dbf318c2f8 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -9,6 +9,7 @@
bindings,
block::{
self,
+ badblocks::{self, BadBlocks},
bio::Segment,
mq::{
self,
@@ -38,6 +39,10 @@
str::CString,
sync::{
aref::ARef,
+ atomic::{
+ ordering,
+ Atomic, //
+ },
Arc,
Mutex, //
},
@@ -153,6 +158,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
home_node: module_parameters::home_node.value(),
discard: module_parameters::discard.value(),
no_sched: module_parameters::no_sched.value(),
+ bad_blocks: Arc::pin_init(BadBlocks::new(false), GFP_KERNEL)?,
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -179,6 +185,7 @@ struct NullBlkOptions<'a> {
home_node: i32,
discard: bool,
no_sched: bool,
+ bad_blocks: Arc<BadBlocks>,
}
struct NullBlkDevice;

@@ -196,6 +203,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
home_node,
discard,
no_sched,
+ bad_blocks,
} = options;

let mut flags = mq::tag_set::Flags::default();
@@ -237,6 +245,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
completion_time,
memory_backed,
block_size: block_size.into(),
+ bad_blocks,
}),
GFP_KERNEL,
)?;
@@ -351,6 +360,16 @@ fn transfer(
}
Ok(())
}
+
+ fn end_request(rq: Owned<mq::Request<Self>>) {
+ let status = rq.data_ref().error.load(ordering::Relaxed);
+ rq.data_ref().error.store(0, ordering::Relaxed);
+
+ match status {
+ 0 => rq.end_ok(),
+ _ => rq.end(bindings::BLK_STS_IOERR),
+ }
+ }
}

static_assert!((PAGE_SIZE >> SECTOR_SHIFT) <= 64);
@@ -396,12 +415,14 @@ struct QueueData {
completion_time: Delta,
memory_backed: bool,
block_size: u64,
+ bad_blocks: Arc<BadBlocks>,
}

#[pin_data]
struct Pdu {
#[pin]
timer: kernel::time::hrtimer::HrTimer<Self>,
+ error: Atomic<u32>,
}

impl HrTimerCallback for Pdu {
@@ -431,6 +452,7 @@ impl Operations for NullBlkDevice {
fn new_request_data() -> impl PinInit<Self::RequestData> {
pin_init!(Pdu {
timer <- kernel::time::hrtimer::HrTimer::new(),
+ error: Atomic::new(0),
})
}

@@ -440,6 +462,19 @@ fn queue_rq(
mut rq: Owned<mq::Request<Self>>,
_is_last: bool,
) -> Result {
+ 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);
+ }
+ }
+
+ // TODO: Skip IO if bad block.
+
if queue_data.memory_backed {
memalloc_scope!(let _noio: NoIo);
let tree = &queue_data.tree;
@@ -461,7 +496,7 @@ fn queue_rq(
}

match queue_data.irq_mode {
- IRQMode::None => rq.end_ok(),
+ IRQMode::None => Self::end_request(rq),
IRQMode::Soft => mq::Request::complete(rq.into()),
IRQMode::Timer => {
OwnableRefCounted::into_shared(rq)
@@ -475,9 +510,10 @@ fn queue_rq(
fn commit_rqs(_queue_data: Pin<&QueueData>) {}

fn complete(rq: ARef<mq::Request<Self>>) {
- OwnableRefCounted::try_from_shared(rq)
- .map_err(|_e| kernel::error::code::EIO)
- .expect("Failed to complete request")
- .end_ok();
+ Self::end_request(
+ OwnableRefCounted::try_from_shared(rq)
+ .map_err(|_e| kernel::error::code::EIO)
+ .expect("Failed to complete request"),
+ )
}
}

--
2.51.2