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 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, } #[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 { + 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 { 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, } struct NullBlkDevice; @@ -196,6 +203,7 @@ fn new(options: NullBlkOptions<'_>) -> Result> { 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> { 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>) { + 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, } #[pin_data] struct Pdu { #[pin] timer: kernel::time::hrtimer::HrTimer, + error: Atomic, } impl HrTimerCallback for Pdu { @@ -431,6 +452,7 @@ impl Operations for NullBlkDevice { fn new_request_data() -> impl PinInit { pin_init!(Pdu { timer <- kernel::time::hrtimer::HrTimer::new(), + error: Atomic::new(0), }) } @@ -440,6 +462,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 { 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>) { - 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