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 --- 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, } #[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 { + 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 { 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, } struct NullBlkDevice; @@ -198,6 +205,7 @@ fn new(options: NullBlkOptions<'_>) -> Result> { 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> { 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>) { + 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, } #[pin_data] struct Pdu { #[pin] timer: kernel::time::hrtimer::HrTimer, + error: Atomic, } impl HrTimerCallback for Pdu { @@ -408,6 +429,7 @@ impl Operations for NullBlkDevice { fn new_request_data() -> impl PinInit { pin_init!(Pdu { timer <- kernel::time::hrtimer::HrTimer::new(), + error: Atomic::new(0), }) } @@ -417,6 +439,19 @@ fn queue_rq( mut rq: Owned>, _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>) { - 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