1 // SPDX-License-Identifier: GPL-2.0 2 3 //! Generic disk abstraction. 4 //! 5 //! C header: [`include/linux/blkdev.h`](srctree/include/linux/blkdev.h) 6 //! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h) 7 8 use crate::{ 9 bindings, 10 block::mq::{Operations, TagSet}, 11 error::{self, from_err_ptr, Result}, 12 fmt::{self, Write}, 13 prelude::*, 14 static_lock_class, 15 str::NullTerminatedFormatter, 16 sync::Arc, 17 types::{ForeignOwnable, ScopeGuard}, 18 }; 19 20 /// A builder for [`GenDisk`]. 21 /// 22 /// Use this struct to configure and add new [`GenDisk`] to the VFS. 23 pub struct GenDiskBuilder { 24 rotational: bool, 25 logical_block_size: u32, 26 physical_block_size: u32, 27 capacity_sectors: u64, 28 } 29 30 impl Default for GenDiskBuilder { 31 fn default() -> Self { 32 Self { 33 rotational: false, 34 logical_block_size: bindings::PAGE_SIZE as u32, 35 physical_block_size: bindings::PAGE_SIZE as u32, 36 capacity_sectors: 0, 37 } 38 } 39 } 40 41 impl GenDiskBuilder { 42 /// Create a new instance. 43 pub fn new() -> Self { 44 Self::default() 45 } 46 47 /// Set the rotational media attribute for the device to be built. 48 pub fn rotational(mut self, rotational: bool) -> Self { 49 self.rotational = rotational; 50 self 51 } 52 53 /// Validate block size by verifying that it is between 512 and `PAGE_SIZE`, 54 /// and that it is a power of two. 55 pub fn validate_block_size(size: u32) -> Result { 56 if !(512..=bindings::PAGE_SIZE as u32).contains(&size) || !size.is_power_of_two() { 57 Err(error::code::EINVAL) 58 } else { 59 Ok(()) 60 } 61 } 62 63 /// Set the logical block size of the device to be built. 64 /// 65 /// This method will check that block size is a power of two and between 512 66 /// and 4096. If not, an error is returned and the block size is not set. 67 /// 68 /// This is the smallest unit the storage device can address. It is 69 /// typically 4096 bytes. 70 pub fn logical_block_size(mut self, block_size: u32) -> Result<Self> { 71 Self::validate_block_size(block_size)?; 72 self.logical_block_size = block_size; 73 Ok(self) 74 } 75 76 /// Set the physical block size of the device to be built. 77 /// 78 /// This method will check that block size is a power of two and between 512 79 /// and 4096. If not, an error is returned and the block size is not set. 80 /// 81 /// This is the smallest unit a physical storage device can write 82 /// atomically. It is usually the same as the logical block size but may be 83 /// bigger. One example is SATA drives with 4096 byte physical block size 84 /// that expose a 512 byte logical block size to the operating system. 85 pub fn physical_block_size(mut self, block_size: u32) -> Result<Self> { 86 Self::validate_block_size(block_size)?; 87 self.physical_block_size = block_size; 88 Ok(self) 89 } 90 91 /// Set the capacity of the device to be built, in sectors (512 bytes). 92 pub fn capacity_sectors(mut self, capacity: u64) -> Self { 93 self.capacity_sectors = capacity; 94 self 95 } 96 97 /// Build a new `GenDisk` and add it to the VFS. 98 pub fn build<T: Operations>( 99 self, 100 name: fmt::Arguments<'_>, 101 tagset: Arc<TagSet<T>>, 102 queue_data: T::QueueData, 103 ) -> Result<GenDisk<T>> { 104 let data = queue_data.into_foreign(); 105 let recover_data = ScopeGuard::new(|| { 106 // SAFETY: T::QueueData was created by the call to `into_foreign()` above 107 drop(unsafe { T::QueueData::from_foreign(data) }); 108 }); 109 110 let mut lim: bindings::queue_limits = pin_init::zeroed(); 111 112 lim.logical_block_size = self.logical_block_size; 113 lim.physical_block_size = self.physical_block_size; 114 if self.rotational { 115 lim.features = bindings::BLK_FEAT_ROTATIONAL; 116 } 117 118 // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set 119 let gendisk = from_err_ptr(unsafe { 120 bindings::__blk_mq_alloc_disk( 121 tagset.raw_tag_set(), 122 &mut lim, 123 data, 124 static_lock_class!().as_ptr(), 125 ) 126 })?; 127 128 const TABLE: bindings::block_device_operations = bindings::block_device_operations { 129 submit_bio: None, 130 open: None, 131 release: None, 132 ioctl: None, 133 compat_ioctl: None, 134 check_events: None, 135 unlock_native_capacity: None, 136 getgeo: None, 137 set_read_only: None, 138 swap_slot_free_notify: None, 139 report_zones: None, 140 devnode: None, 141 alternative_gpt_sector: None, 142 get_unique_id: None, 143 // TODO: Set to `THIS_MODULE`. 144 owner: core::ptr::null_mut(), 145 pr_ops: core::ptr::null_mut(), 146 free_disk: None, 147 poll_bio: None, 148 }; 149 150 // SAFETY: `gendisk` is a valid pointer as we initialized it above 151 unsafe { (*gendisk).fops = &TABLE }; 152 153 let cleanup_failure = ScopeGuard::new_with_data((gendisk, data), |(gendisk, data)| { 154 // SAFETY: `gendisk` came from `__blk_mq_alloc_disk()` above and 155 // has not been added to the VFS on this cleanup path. 156 unsafe { bindings::put_disk(gendisk) }; 157 // SAFETY: `data` came from `into_foreign()` above and has not been 158 // converted back on this cleanup path. 159 drop(unsafe { T::QueueData::from_foreign(data) }); 160 }); 161 162 // The failure guard now owns both pieces of cleanup; the early guard 163 // must not run on this path anymore. 164 recover_data.dismiss(); 165 166 let mut writer = NullTerminatedFormatter::new( 167 // SAFETY: `gendisk` points to a valid and initialized instance. We 168 // have exclusive access, since the disk is not added to the VFS 169 // yet. 170 unsafe { &mut (*gendisk).disk_name }, 171 ) 172 .ok_or(EINVAL)?; 173 writer.write_fmt(name)?; 174 175 // SAFETY: `gendisk` points to a valid and initialized instance of 176 // `struct gendisk`. `set_capacity` takes a lock to synchronize this 177 // operation, so we will not race. 178 unsafe { bindings::set_capacity(gendisk, self.capacity_sectors) }; 179 180 crate::error::to_result( 181 // SAFETY: `gendisk` points to a valid and initialized instance of 182 // `struct gendisk`. 183 unsafe { 184 bindings::device_add_disk(core::ptr::null_mut(), gendisk, core::ptr::null_mut()) 185 }, 186 )?; 187 188 cleanup_failure.dismiss(); 189 190 // INVARIANT: `gendisk` was initialized above. 191 // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above. 192 // INVARIANT: `gendisk.queue.queue_data` is set to `data` in the call to 193 // `__blk_mq_alloc_disk` above. 194 Ok(GenDisk { 195 _tagset: tagset, 196 gendisk, 197 }) 198 } 199 } 200 201 /// A generic block device. 202 /// 203 /// # Invariants 204 /// 205 /// - `gendisk` must always point to an initialized and valid `struct gendisk`. 206 /// - `gendisk` was added to the VFS through a call to 207 /// `bindings::device_add_disk`. 208 /// - `self.gendisk.queue.queuedata` is initialized by a call to `ForeignOwnable::into_foreign`. 209 pub struct GenDisk<T: Operations> { 210 _tagset: Arc<TagSet<T>>, 211 gendisk: *mut bindings::gendisk, 212 } 213 214 // SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a 215 // `TagSet` It is safe to send this to other threads as long as T is Send. 216 unsafe impl<T: Operations + Send> Send for GenDisk<T> {} 217 218 impl<T: Operations> Drop for GenDisk<T> { 219 fn drop(&mut self) { 220 // SAFETY: By type invariant of `Self`, `self.gendisk` points to a valid 221 // and initialized instance of `struct gendisk`, and, `queuedata` was 222 // initialized with the result of a call to 223 // `ForeignOwnable::into_foreign`. 224 let queue_data = unsafe { (*(*self.gendisk).queue).queuedata }; 225 226 // SAFETY: By type invariant, `self.gendisk` points to a valid and 227 // initialized instance of `struct gendisk`, and it was previously added 228 // to the VFS. 229 unsafe { bindings::del_gendisk(self.gendisk) }; 230 231 // SAFETY: By type invariant, `self.gendisk` was added to the VFS, so 232 // `put_disk()` must follow `del_gendisk()` to drop the final gendisk 233 // reference and trigger the remaining release path. 234 unsafe { bindings::put_disk(self.gendisk) }; 235 236 // SAFETY: `queue.queuedata` was created by `GenDiskBuilder::build` with 237 // a call to `ForeignOwnable::into_foreign` to create `queuedata`. 238 // `ForeignOwnable::from_foreign` is only called here. 239 drop(unsafe { T::QueueData::from_foreign(queue_data) }); 240 } 241 } 242