Add Forced Unit Access (FUA) support to rnull. When enabled via the `fua` configfs attribute, the driver advertises FUA capability and handles FUA requests by bypassing the volatile cache in the write path. FUA support requires memory backing and write cache to be enabled. Signed-off-by: Andreas Hindborg --- drivers/block/rnull/configfs.rs | 5 ++++ drivers/block/rnull/disk_storage.rs | 22 +++++++++++++---- drivers/block/rnull/disk_storage/page.rs | 1 + drivers/block/rnull/rnull.rs | 41 ++++++++++++++++++++++++++------ 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs index 0637c1e0ab22..8195d645ecc6 100644 --- a/drivers/block/rnull/configfs.rs +++ b/drivers/block/rnull/configfs.rs @@ -128,6 +128,7 @@ fn make_group( zone_max_active: 25, zone_append_max_sectors: 26, poll_queues: 27, + fua: 28, ], }; @@ -169,6 +170,7 @@ fn make_group( zone_max_active: 0, zone_append_max_sectors: u32::MAX, poll_queues: 0, + fua: true, }), }), core::iter::empty(), @@ -256,6 +258,7 @@ struct DeviceConfigInner { zone_max_active: u32, zone_append_max_sectors: u32, poll_queues: u32, + fua: bool, } #[vtable] @@ -322,6 +325,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result { zone_max_open: guard.zone_max_open, zone_max_active: guard.zone_max_active, zone_append_max_sectors: guard.zone_append_max_sectors, + forced_unit_access: guard.fua, })?); guard.powered = true; } else if guard.powered && !power_op { @@ -515,3 +519,4 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result { } }) ); +configfs_simple_bool_field!(DeviceConfig, 28, fua); diff --git a/drivers/block/rnull/disk_storage.rs b/drivers/block/rnull/disk_storage.rs index 7667830bd616..4a9bf480221f 100644 --- a/drivers/block/rnull/disk_storage.rs +++ b/drivers/block/rnull/disk_storage.rs @@ -92,6 +92,10 @@ pub(crate) fn flush(&self, hw_data: &Pin<&SpinLock>) -> Result { let mut access = self.access(&mut tree_guard, &mut hw_data_guard, None); access.flush() } + + pub(crate) fn cache_enabled(&self) -> bool { + self.cache_size > 0 + } } pub(crate) struct DiskStorageAccess<'a, 'b, 'c> { @@ -205,7 +209,7 @@ fn flush(&mut self) -> Result { Ok(()) } - fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { + fn get_or_alloc_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { let index = Self::to_index(sector); match self.cache_guard.entry(index) { @@ -239,6 +243,12 @@ fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { } } + pub(crate) fn get_cache_page(&mut self, sector: u64) -> Option<&mut NullBlockPage> { + let index = Self::to_index(sector); + + self.cache_guard.get_mut(index) + } + fn get_disk_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { let index = Self::to_index(sector); @@ -256,9 +266,13 @@ fn get_disk_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { Ok(page) } - pub(crate) fn get_write_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> { - let page = if self.disk_storage.cache_size > 0 { - self.get_cache_page(sector)? + pub(crate) fn get_write_page( + &mut self, + sector: u64, + bypass_cache: bool, + ) -> Result<&mut NullBlockPage> { + let page = if self.disk_storage.cache_size > 0 && !bypass_cache { + self.get_or_alloc_cache_page(sector)? } else { self.get_disk_page(sector)? }; diff --git a/drivers/block/rnull/disk_storage/page.rs b/drivers/block/rnull/disk_storage/page.rs index 88dc9a2476bd..846269d31c63 100644 --- a/drivers/block/rnull/disk_storage/page.rs +++ b/drivers/block/rnull/disk_storage/page.rs @@ -15,6 +15,7 @@ uapi::PAGE_SECTORS, // }; +// TODO: Use rust bitmap static_assert!((PAGE_SIZE >> SECTOR_SHIFT) <= 64); pub(crate) struct NullBlockPage { diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs index 0695cbd07f1d..c3126b923367 100644 --- a/drivers/block/rnull/rnull.rs +++ b/drivers/block/rnull/rnull.rs @@ -191,6 +191,10 @@ default: 0, description: "Number of IOPOLL submission queues.", }, + fua: bool { + default: true, + description: "Enable/disable FUA support when cache_size is used.", + }, }, } @@ -267,6 +271,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit { zone_max_open: module_parameters::zone_max_open.value(), zone_max_active: module_parameters::zone_max_active.value(), zone_append_max_sectors: module_parameters::zone_append_max_sectors.value(), + forced_unit_access: module_parameters::fua.value(), })?; disks.push(disk, GFP_KERNEL)?; } @@ -307,6 +312,7 @@ struct NullBlkOptions<'a> { zone_max_active: u32, #[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(dead_code))] zone_append_max_sectors: u32, + forced_unit_access: bool, } #[pin_data] @@ -422,6 +428,7 @@ fn new(options: NullBlkOptions<'_>) -> Result>> { zone_max_active, #[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(unused_variables))] zone_append_max_sectors, + forced_unit_access, } = options; let memory_backed = tag_set.memory_backed; @@ -439,9 +446,10 @@ fn new(options: NullBlkOptions<'_>) -> Result>> { return Err(code::EINVAL); } + let s = storage.clone(); let queue_data = Arc::try_pin_init( try_pin_init!(Self { - storage, + storage: s, irq_mode, completion_time, memory_backed, @@ -474,7 +482,9 @@ fn new(options: NullBlkOptions<'_>) -> Result>> { .capacity_sectors(device_capacity_sectors) .logical_block_size(block_size_bytes)? .physical_block_size(block_size_bytes)? - .rotational(rotational); + .rotational(rotational) + .write_cache(storage.cache_enabled()) + .forced_unit_access(forced_unit_access && storage.cache_enabled()); #[cfg(CONFIG_BLK_DEV_ZONED)] { @@ -553,6 +563,7 @@ fn write<'a, 'b, 'c>( hw_data_guard: &'b mut SpinLockGuard<'c, HwQueueContext>, mut sector: u64, mut segment: Segment<'_>, + bypass_cache: bool, ) -> Result { let mut sheaf: Option> = None; @@ -561,7 +572,13 @@ fn write<'a, 'b, 'c>( let mut access = self.storage.access(tree_guard, hw_data_guard, sheaf); - let page = access.get_write_page(sector)?; + if bypass_cache { + if let Some(page) = access.get_cache_page(sector) { + page.set_free(sector); + } + } + + let page = access.get_write_page(sector, bypass_cache)?; page.set_occupied(sector); // CAST: Page offset always fits in 32 bits. @@ -569,7 +586,11 @@ fn write<'a, 'b, 'c>( ((sector & u64::from(block::PAGE_SECTOR_MASK)) << block::SECTOR_SHIFT) as usize; // CAST: Casting from `usize` to `u64` never overflows. - sector += segment.copy_to_page(page.page_mut().as_pin_mut(), page_offset) as u64 + sector += segment.copy_to_page_limit( + page.page_mut().as_pin_mut(), + page_offset, + self.block_size_bytes.try_into()?, + ) as u64 >> block::SECTOR_SHIFT; sheaf = access.sheaf; @@ -632,6 +653,8 @@ fn transfer( let mut hw_data_guard = hw_data.lock(); let mut tree_guard = self.storage.lock(); + let skip_cache = rq.flags().contains(mq::RequestFlag::ForcedUnitAccess); + for bio in rq.bio_iter_mut() { let segment_iter = bio.segment_iter(); for mut segment in segment_iter { @@ -641,9 +664,13 @@ fn transfer( let length_sectors_allowed = segment_length_sectors.min(max_remaining_sectors); segment.truncate(length_sectors_allowed << SECTOR_SHIFT); match command { - mq::Command::Write => { - self.write(&mut tree_guard, &mut hw_data_guard, sector, segment)? - } + mq::Command::Write => self.write( + &mut tree_guard, + &mut hw_data_guard, + sector, + segment, + skip_cache, + )?, mq::Command::Read => { self.read(&mut tree_guard, &mut hw_data_guard, sector, segment)? } -- 2.51.2