[PATCH 26/79] block: rnull: add badblocks support

From: Andreas Hindborg

Date: Sun Feb 15 2026 - 18:44:07 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 18e32a87673aa..61a76addf468b 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, //
+ }, //
},
c_str,
configfs::{
@@ -27,7 +30,10 @@
kstrtobool_bytes,
CString, //
},
- sync::Mutex,
+ sync::{
+ Arc,
+ Mutex, //
+ },
time, //
};
use macros::{
@@ -96,6 +102,7 @@ fn make_group(
home_node: 9,
discard: 10,
no_sched:11,
+ badblocks: 12,
],
};

@@ -118,6 +125,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(),
@@ -177,6 +185,7 @@ struct DeviceConfigInner {
home_node: i32,
discard: bool,
no_sched: bool,
+ bad_blocks: Arc<BadBlocks>,
}

#[vtable]
@@ -212,6 +221,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 {
@@ -367,3 +377,48 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
Ok(())
}
}
+
+#[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 b0008e2f9c398..861392c5b5841 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,
@@ -37,6 +38,10 @@
str::CString,
sync::{
aref::ARef,
+ atomic::{
+ ordering,
+ Atomic, //
+ },
Arc,
Mutex, //
},
@@ -155,6 +160,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
home_node: *module_parameters::home_node.value(),
discard: *module_parameters::discard.value() != 0,
no_sched: *module_parameters::no_sched.value() != 0,
+ bad_blocks: Arc::pin_init(BadBlocks::new(false), GFP_KERNEL)?,
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -181,6 +187,7 @@ struct NullBlkOptions<'a> {
home_node: i32,
discard: bool,
no_sched: bool,
+ bad_blocks: Arc<BadBlocks>,
}
struct NullBlkDevice;

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

let mut flags = mq::tag_set::Flags::default();
@@ -226,6 +234,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
completion_time,
memory_backed,
block_size: block_size.into(),
+ bad_blocks,
}),
GFP_KERNEL,
)?;
@@ -327,6 +336,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),
+ }
+ }
}

const _CHEKC_STATUS_WIDTH: () = build_assert!((PAGE_SIZE >> SECTOR_SHIFT) <= 64);
@@ -373,12 +392,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 {
@@ -408,6 +429,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),
})
}

@@ -417,6 +439,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 {
let tree = &queue_data.tree;
let command = rq.command();
@@ -437,7 +472,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)
@@ -451,9 +486,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