Add support for zoned block devices to the Rust block layer bindings. This includes the `report_zones` callback in `Operations` and methods in `GenDiskBuilder` to configure zoned device parameters. Drivers can mark a disk as zoned and configure the zone size and maximum zone append size. The `report_zones` callback is invoked by the block layer to query zone information. Signed-off-by: Andreas Hindborg --- rust/bindings/bindings_helper.h | 1 + rust/kernel/block/mq/gen_disk.rs | 96 +++++++++++++++++++++++++++++++++----- rust/kernel/block/mq/operations.rs | 60 ++++++++++++++++++++++-- 3 files changed, 142 insertions(+), 15 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 76b58c3fd1ff1..05133a78ecf95 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -133,6 +133,7 @@ const blk_status_t RUST_CONST_HELPER_BLK_STS_ZONE_ACTIVE_RESOURCE = BLK_STS_ZONE const blk_status_t RUST_CONST_HELPER_BLK_STS_OFFLINE = BLK_STS_OFFLINE; const blk_status_t RUST_CONST_HELPER_BLK_STS_DURATION_LIMIT = BLK_STS_DURATION_LIMIT; const blk_status_t RUST_CONST_HELPER_BLK_STS_INVAL = BLK_STS_INVAL; +const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ZONED = BLK_FEAT_ZONED; const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET; const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT; diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs index eb980079530bd..a6f113ea4bea4 100644 --- a/rust/kernel/block/mq/gen_disk.rs +++ b/rust/kernel/block/mq/gen_disk.rs @@ -7,7 +7,7 @@ use crate::{ bindings, - block::mq::{Operations, RequestQueue, TagSet}, + block::mq::{operations::OperationsVTable, Operations, RequestQueue, TagSet}, error::{self, from_err_ptr, Result}, fmt::{self, Write}, prelude::*, @@ -28,6 +28,12 @@ pub struct GenDiskBuilder { physical_block_size: u32, capacity_sectors: u64, max_hw_discard_sectors: u32, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zoned: bool, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zone_size_sectors: u32, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zone_append_max_sectors: u32, _p: PhantomData, } @@ -39,6 +45,12 @@ fn default() -> Self { physical_block_size: bindings::PAGE_SIZE as u32, capacity_sectors: 0, max_hw_discard_sectors: 0, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zoned: false, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zone_size_sectors: 0, + #[cfg(CONFIG_BLK_DEV_ZONED)] + zone_append_max_sectors: 0, _p: PhantomData, } } @@ -110,6 +122,27 @@ pub fn max_hw_discard_sectors(mut self, max_hw_discard_sectors: u32) -> Self { self } + /// Mark this device as a zoned block device. + #[cfg(CONFIG_BLK_DEV_ZONED)] + pub fn zoned(mut self, enable: bool) -> Self { + self.zoned = enable; + self + } + + /// Set the zone size of this block device. + #[cfg(CONFIG_BLK_DEV_ZONED)] + pub fn zone_size(mut self, sectors: u32) -> Self { + self.zone_size_sectors = sectors; + self + } + + /// Set the max zone append size for this block device. + #[cfg(CONFIG_BLK_DEV_ZONED)] + pub fn zone_append_max(mut self, sectors: u32) -> Self { + self.zone_append_max_sectors = sectors; + self + } + /// Build a new `GenDisk` and add it to the VFS. pub fn build( self, @@ -130,7 +163,18 @@ pub fn build( lim.physical_block_size = self.physical_block_size; lim.max_hw_discard_sectors = self.max_hw_discard_sectors; if self.rotational { - lim.features = bindings::BLK_FEAT_ROTATIONAL; + lim.features |= bindings::BLK_FEAT_ROTATIONAL; + } + + #[cfg(CONFIG_BLK_DEV_ZONED)] + if self.zoned { + if !T::HAS_REPORT_ZONES { + return Err(error::code::EINVAL); + } + + lim.features |= bindings::BLK_FEAT_ZONED; + lim.chunk_sectors = self.zone_size_sectors; + lim.max_hw_zone_append_sectors = self.zone_append_max_sectors; } // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set @@ -160,14 +204,6 @@ pub fn build( // operation, so we will not race. unsafe { bindings::set_capacity(gendisk, self.capacity_sectors) }; - crate::error::to_result( - // SAFETY: `gendisk` points to a valid and initialized instance of - // `struct gendisk`. - unsafe { - bindings::device_add_disk(core::ptr::null_mut(), gendisk, core::ptr::null_mut()) - }, - )?; - recover_data.dismiss(); // INVARIANT: `gendisk` was initialized above. @@ -195,7 +231,27 @@ pub fn build( GFP_KERNEL, )?; - Ok(disk.into()) + let disk: Arc<_> = disk.into(); + + // SAFETY: `disk.gendisk` is valid for write as we initialized it above. We have exclusive + // access. + unsafe { (*disk.gendisk).private_data = Arc::as_ptr(&disk).cast_mut().cast() }; + + #[cfg(CONFIG_BLK_DEV_ZONED)] + if self.zoned { + // SAFETY: `disk.gendisk` is valid as we initialized it above. We have exclusive access. + unsafe { bindings::blk_revalidate_disk_zones(gendisk) }; + } + + crate::error::to_result( + // SAFETY: `gendisk` points to a valid and initialized instance of + // `struct gendisk`. + unsafe { + bindings::device_add_disk(core::ptr::null_mut(), gendisk, core::ptr::null_mut()) + }, + )?; + + Ok(disk) } const VTABLE: bindings::block_device_operations = bindings::block_device_operations { @@ -209,7 +265,11 @@ pub fn build( getgeo: None, set_read_only: None, swap_slot_free_notify: None, - report_zones: None, + report_zones: if T::HAS_REPORT_ZONES { + Some(OperationsVTable::::report_zones_callback) + } else { + None + }, devnode: None, alternative_gpt_sector: None, get_unique_id: None, @@ -295,6 +355,18 @@ fn drop(&mut self) { /// `self.0` is valid for use as a reference. pub struct GenDiskRef(NonNull>); +impl GenDiskRef { + /// Create a `GenDiskRef` from a pointer to a `GenDisk`. + /// + /// # Safety + /// + /// `ptr` must be valid for use as a `GenDisk` reference for the lifetime of the returned + /// `GenDiskRef`. + pub(crate) unsafe fn from_ptr(ptr: NonNull>) -> GenDiskRef { + Self(ptr) + } +} + // SAFETY: It is safe to transfer ownership of `GenDiskRef` across thread boundaries. unsafe impl Send for GenDiskRef {} diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs index 17468a39af60f..3f84ebadec86b 100644 --- a/rust/kernel/block/mq/operations.rs +++ b/rust/kernel/block/mq/operations.rs @@ -8,14 +8,14 @@ bindings, block::{ error::BlkResult, - mq::{request::RequestDataWrapper, IdleRequest, Request}, + mq::{gen_disk::GenDiskRef, request::RequestDataWrapper, IdleRequest, Request}, }, - error::{from_result, Result}, + error::{from_result, to_result, Result}, prelude::*, sync::{aref::ARef, atomic::ordering, Refcount}, types::{ForeignOwnable, Owned}, }; -use core::marker::PhantomData; +use core::{marker::PhantomData, ptr::NonNull}; use pin_init::PinInit; type ForeignBorrowed<'a, T> = ::Borrowed<'a>; @@ -87,6 +87,20 @@ fn init_hctx( fn poll(_hw_data: ForeignBorrowed<'_, Self::HwData>) -> bool { build_error!(crate::error::VTABLE_DEFAULT_ERROR) } + + /// Called by the kernel to get a zone report from the driver. + /// + /// The driver must call `callback` once for each zone on `disk` and populate the first argument + /// with a zone descriptor and the second argument when the zone index. + // TODO: We cannot gate this on CONFIG_BLK_DEV_ZONED due to limitations of the `vtable` macro. + fn report_zones( + _disk: &GenDiskRef, + _sector: u64, + _nr_zones: u32, + _callback: impl Fn(&bindings::blk_zone, u32) -> Result, + ) -> Result { + Err(ENOTSUPP) + } } /// A vtable for blk-mq to interact with a block device driver. @@ -340,6 +354,46 @@ impl OperationsVTable { unsafe { core::ptr::drop_in_place(pdu) }; } + /// This function is a callback hook for the C kernel. A pointer to this function is + /// installed in the `blk_mq_ops` vtable for the driver. + /// + /// # Safety + /// + /// - This function may only be called by blk-mq C infrastructure. + /// - `disk_ptr` must be a pointer to a gendisk initialized by `GenDisk::build`. + pub(crate) unsafe extern "C" fn report_zones_callback( + disk_ptr: *mut bindings::gendisk, + sector: u64, + nr_zones: u32, + args: *mut bindings::blk_report_zones_args, + ) -> i32 { + // SAFETY: As `disk_ptr` is a gendisk initialized by `GenDisk::build`, `private_data` is not + // null. + let disk_ref_ptr = unsafe { NonNull::new_unchecked((*disk_ptr).private_data.cast()) }; + + // SAFETY: `disk_ptr.private_data` is a pointer to the `GenDisk` owner of `disk_ptr` that we + // installed when we initialized `disk_ptr`. It is valid for use as a reference for the + // duration of this call. + let disk = unsafe { GenDiskRef::from_ptr(disk_ref_ptr) }; + + from_result(|| { + T::report_zones(&disk, sector, nr_zones, |zone, idx| -> Result { + to_result( + // SAFETY: `disk_ptr` is valid by function safety requirements. + unsafe { + bindings::disk_report_zone( + disk_ptr, + core::ptr::from_ref(zone).cast_mut(), + idx, + args, + ) + }, + ) + }) + .and_then(|v: u32| -> Result<_> { Ok(v.try_into()?) }) + }) + } + const VTABLE: bindings::blk_mq_ops = bindings::blk_mq_ops { queue_rq: Some(Self::queue_rq_callback), queue_rqs: None, -- 2.51.2