Add memory backing to the rust null block driver. This implementation will always allocate a page on write, even though a page backing the written sector is already allocated, in which case the page will be released again. A later patch will fix this inefficiency. Signed-off-by: Andreas Hindborg --- drivers/block/rnull/configfs.rs | 37 +++++++++++- drivers/block/rnull/rnull.rs | 125 ++++++++++++++++++++++++++++++++++------ 2 files changed, 144 insertions(+), 18 deletions(-) diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs index 7952f41f42bfd..b5dc30c5d3e20 100644 --- a/drivers/block/rnull/configfs.rs +++ b/drivers/block/rnull/configfs.rs @@ -59,7 +59,7 @@ impl AttributeOperations<0> for Config { fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result { let mut writer = kernel::str::Formatter::new(page); - writer.write_str("blocksize,size,rotational,irqmode,completion_nsec\n")?; + writer.write_str("blocksize,size,rotational,irqmode,completion_nsec,memory_backed\n")?; Ok(writer.bytes_written()) } } @@ -83,6 +83,7 @@ fn make_group( size: 3, irqmode: 4, completion_nsec: 5, + memory_backed: 6, ], }; @@ -100,6 +101,7 @@ fn make_group( irq_mode: IRQMode::None, completion_time: time::Delta::ZERO, name: name.try_into()?, + memory_backed: false, }), }), core::iter::empty(), @@ -154,6 +156,7 @@ struct DeviceConfigInner { irq_mode: IRQMode, completion_time: time::Delta, disk: Option>, + memory_backed: bool, } #[vtable] @@ -184,6 +187,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result { guard.capacity_mib, guard.irq_mode, guard.completion_time, + guard.memory_backed, )?); guard.powered = true; } else if guard.powered && !power_op { @@ -209,3 +213,34 @@ fn from_str(s: &str) -> Result { value.try_into() } } + +#[vtable] +impl configfs::AttributeOperations<6> for DeviceConfig { + type Data = DeviceConfig; + + fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result { + let mut writer = kernel::str::Formatter::new(page); + + if this.data.lock().memory_backed { + writer.write_fmt(fmt!("1\n"))?; + } else { + writer.write_fmt(fmt!("0\n"))?; + } + + Ok(writer.bytes_written()) + } + + fn store(this: &DeviceConfig, page: &[u8]) -> Result { + if this.data.lock().powered { + return Err(EBUSY); + } + + this.data.lock().memory_backed = core::str::from_utf8(page)? + .trim() + .parse::() + .map_err(|_| kernel::error::code::EINVAL)? + != 0; + + Ok(()) + } +} diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs index 33c1144550a0e..a1156a368c467 100644 --- a/drivers/block/rnull/rnull.rs +++ b/drivers/block/rnull/rnull.rs @@ -6,8 +6,10 @@ use configfs::IRQMode; use kernel::{ + bindings, block::{ self, + bio::Segment, mq::{ self, gen_disk::{ @@ -19,15 +21,12 @@ }, }, error::Result, - new_mutex, + new_mutex, new_xarray, + page::SafePage, pr_info, prelude::*, str::CString, - sync::{ - aref::ARef, - Arc, - Mutex, // - }, + sync::{aref::ARef, Arc, Mutex}, time::{ hrtimer::{ HrTimerCallback, @@ -40,7 +39,8 @@ types::{ OwnableRefCounted, Owned, // - }, // + }, + xarray::XArray, }; use pin_init::PinInit; @@ -76,6 +76,10 @@ default: 10_000, description: "Time in ns to complete a request in hardware. Default: 10,000ns", }, + memory_backed: u8 { + default: 0, + description: "Create a memory-backed block device. 0-false, 1-true. Default: 0", + }, }, } @@ -105,6 +109,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit { *module_parameters::gb.value() * 1024, (*module_parameters::irqmode.value()).try_into()?, Delta::from_nanos(completion_time), + *module_parameters::memory_backed.value() != 0, )?; disks.push(disk, GFP_KERNEL)?; } @@ -129,17 +134,23 @@ fn new( capacity_mib: u64, irq_mode: IRQMode, completion_time: Delta, + memory_backed: bool, ) -> Result> { - let tagset = Arc::pin_init( - TagSet::new(1, 256, 1, mq::tag_set::Flags::default()), - GFP_KERNEL, - )?; + let flags = if memory_backed { + mq::tag_set::Flag::Blocking.into() + } else { + mq::tag_set::Flags::default() + }; + + let tagset = Arc::pin_init(TagSet::new(1, 256, 1, flags), GFP_KERNEL)?; - let queue_data = Box::new( - QueueData { + let queue_data = Box::pin_init( + pin_init!(QueueData { + tree <- new_xarray!(kernel::xarray::AllocKind::Alloc), irq_mode, completion_time, - }, + memory_backed, + }), GFP_KERNEL, )?; @@ -150,11 +161,72 @@ fn new( .rotational(rotational) .build(fmt!("{}", name.to_str()?), tagset, queue_data) } + + #[inline(always)] + fn write(tree: &Tree, mut sector: usize, mut segment: Segment<'_>) -> Result { + while !segment.is_empty() { + let page = SafePage::alloc_page(GFP_NOIO)?; + let mut tree = tree.lock(); + + let page_idx = sector >> block::PAGE_SECTORS_SHIFT; + + let page = if let Some(page) = tree.get_mut(page_idx) { + page + } else { + tree.store(page_idx, page, GFP_NOIO)?; + tree.get_mut(page_idx).unwrap() + }; + + let page_offset = (sector & block::SECTOR_MASK as usize) << block::SECTOR_SHIFT; + sector += segment.copy_to_page(page, page_offset) >> block::SECTOR_SHIFT; + } + Ok(()) + } + + #[inline(always)] + fn read(tree: &Tree, mut sector: usize, mut segment: Segment<'_>) -> Result { + let tree = tree.lock(); + + while !segment.is_empty() { + let idx = sector >> block::PAGE_SECTORS_SHIFT; + + if let Some(page) = tree.get(idx) { + let page_offset = (sector & block::SECTOR_MASK as usize) << block::SECTOR_SHIFT; + sector += segment.copy_from_page(page, page_offset) >> block::SECTOR_SHIFT; + } else { + sector += segment.zero_page() >> block::SECTOR_SHIFT; + } + } + + Ok(()) + } + + #[inline(never)] + fn transfer( + command: bindings::req_op, + tree: &Tree, + sector: usize, + segment: Segment<'_>, + ) -> Result { + match command { + bindings::req_op_REQ_OP_WRITE => Self::write(tree, sector, segment)?, + bindings::req_op_REQ_OP_READ => Self::read(tree, sector, segment)?, + _ => (), + } + Ok(()) + } } +type TreeNode = Owned; +type Tree = XArray; + +#[pin_data] struct QueueData { + #[pin] + tree: Tree, irq_mode: IRQMode, completion_time: Delta, + memory_backed: bool, } #[pin_data] @@ -184,7 +256,7 @@ impl HasHrTimer for Pdu { #[vtable] impl Operations for NullBlkDevice { - type QueueData = KBox; + type QueueData = Pin>; type RequestData = Pdu; fn new_request_data() -> impl PinInit { @@ -194,7 +266,26 @@ fn new_request_data() -> impl PinInit { } #[inline(always)] - fn queue_rq(queue_data: &QueueData, rq: Owned>, _is_last: bool) -> Result { + fn queue_rq( + queue_data: Pin<&QueueData>, + mut rq: Owned>, + _is_last: bool, + ) -> Result { + if queue_data.memory_backed { + let tree = &queue_data.tree; + let command = rq.command(); + let mut sector = rq.sector(); + + for bio in rq.bio_iter_mut() { + let segment_iter = bio.segment_iter(); + for segment in segment_iter { + let length = segment.len(); + Self::transfer(command, tree, sector, segment)?; + sector += length as usize >> block::SECTOR_SHIFT; + } + } + } + match queue_data.irq_mode { IRQMode::None => rq.end_ok(), IRQMode::Soft => mq::Request::complete(rq.into()), @@ -207,7 +298,7 @@ fn queue_rq(queue_data: &QueueData, rq: Owned>, _is_last: bool Ok(()) } - fn commit_rqs(_queue_data: &QueueData) {} + fn commit_rqs(_queue_data: Pin<&QueueData>) {} fn complete(rq: ARef>) { OwnableRefCounted::try_from_shared(rq) -- 2.51.2