Skip to main content

kernel/block/mq/
gen_disk.rs

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