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::block::mq::{raw_writer::RawWriter, Operations, TagSet};
9use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc};
10use crate::{error, static_lock_class};
11use core::fmt::{self, Write};
12
13/// A builder for [`GenDisk`].
14///
15/// Use this struct to configure and add new [`GenDisk`] to the VFS.
16pub struct GenDiskBuilder {
17    rotational: bool,
18    logical_block_size: u32,
19    physical_block_size: u32,
20    capacity_sectors: u64,
21}
22
23impl Default for GenDiskBuilder {
24    fn default() -> Self {
25        Self {
26            rotational: false,
27            logical_block_size: bindings::PAGE_SIZE as u32,
28            physical_block_size: bindings::PAGE_SIZE as u32,
29            capacity_sectors: 0,
30        }
31    }
32}
33
34impl GenDiskBuilder {
35    /// Create a new instance.
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Set the rotational media attribute for the device to be built.
41    pub fn rotational(mut self, rotational: bool) -> Self {
42        self.rotational = rotational;
43        self
44    }
45
46    /// Validate block size by verifying that it is between 512 and `PAGE_SIZE`,
47    /// and that it is a power of two.
48    fn validate_block_size(size: u32) -> Result {
49        if !(512..=bindings::PAGE_SIZE as u32).contains(&size) || !size.is_power_of_two() {
50            Err(error::code::EINVAL)
51        } else {
52            Ok(())
53        }
54    }
55
56    /// Set the logical block size of the device to be built.
57    ///
58    /// This method will check that block size is a power of two and between 512
59    /// and 4096. If not, an error is returned and the block size is not set.
60    ///
61    /// This is the smallest unit the storage device can address. It is
62    /// typically 4096 bytes.
63    pub fn logical_block_size(mut self, block_size: u32) -> Result<Self> {
64        Self::validate_block_size(block_size)?;
65        self.logical_block_size = block_size;
66        Ok(self)
67    }
68
69    /// Set the physical block size of the device to be built.
70    ///
71    /// This method will check that block size is a power of two and between 512
72    /// and 4096. If not, an error is returned and the block size is not set.
73    ///
74    /// This is the smallest unit a physical storage device can write
75    /// atomically. It is usually the same as the logical block size but may be
76    /// bigger. One example is SATA drives with 4096 byte physical block size
77    /// that expose a 512 byte logical block size to the operating system.
78    pub fn physical_block_size(mut self, block_size: u32) -> Result<Self> {
79        Self::validate_block_size(block_size)?;
80        self.physical_block_size = block_size;
81        Ok(self)
82    }
83
84    /// Set the capacity of the device to be built, in sectors (512 bytes).
85    pub fn capacity_sectors(mut self, capacity: u64) -> Self {
86        self.capacity_sectors = capacity;
87        self
88    }
89
90    /// Build a new `GenDisk` and add it to the VFS.
91    pub fn build<T: Operations>(
92        self,
93        name: fmt::Arguments<'_>,
94        tagset: Arc<TagSet<T>>,
95    ) -> Result<GenDisk<T>> {
96        // SAFETY: `bindings::queue_limits` contain only fields that are valid when zeroed.
97        let mut lim: bindings::queue_limits = unsafe { core::mem::zeroed() };
98
99        lim.logical_block_size = self.logical_block_size;
100        lim.physical_block_size = self.physical_block_size;
101        if self.rotational {
102            lim.features = bindings::BLK_FEAT_ROTATIONAL;
103        }
104
105        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
106        let gendisk = from_err_ptr(unsafe {
107            bindings::__blk_mq_alloc_disk(
108                tagset.raw_tag_set(),
109                &mut lim,
110                core::ptr::null_mut(),
111                static_lock_class!().as_ptr(),
112            )
113        })?;
114
115        const TABLE: bindings::block_device_operations = bindings::block_device_operations {
116            submit_bio: None,
117            open: None,
118            release: None,
119            ioctl: None,
120            compat_ioctl: None,
121            check_events: None,
122            unlock_native_capacity: None,
123            getgeo: None,
124            set_read_only: None,
125            swap_slot_free_notify: None,
126            report_zones: None,
127            devnode: None,
128            alternative_gpt_sector: None,
129            get_unique_id: None,
130            // TODO: Set to THIS_MODULE. Waiting for const_refs_to_static feature to
131            // be merged (unstable in rustc 1.78 which is staged for linux 6.10)
132            // https://github.com/rust-lang/rust/issues/119618
133            owner: core::ptr::null_mut(),
134            pr_ops: core::ptr::null_mut(),
135            free_disk: None,
136            poll_bio: None,
137        };
138
139        // SAFETY: `gendisk` is a valid pointer as we initialized it above
140        unsafe { (*gendisk).fops = &TABLE };
141
142        let mut raw_writer = RawWriter::from_array(
143            // SAFETY: `gendisk` points to a valid and initialized instance. We
144            // have exclusive access, since the disk is not added to the VFS
145            // yet.
146            unsafe { &mut (*gendisk).disk_name },
147        )?;
148        raw_writer.write_fmt(name)?;
149        raw_writer.write_char('\0')?;
150
151        // SAFETY: `gendisk` points to a valid and initialized instance of
152        // `struct gendisk`. `set_capacity` takes a lock to synchronize this
153        // operation, so we will not race.
154        unsafe { bindings::set_capacity(gendisk, self.capacity_sectors) };
155
156        crate::error::to_result(
157            // SAFETY: `gendisk` points to a valid and initialized instance of
158            // `struct gendisk`.
159            unsafe {
160                bindings::device_add_disk(core::ptr::null_mut(), gendisk, core::ptr::null_mut())
161            },
162        )?;
163
164        // INVARIANT: `gendisk` was initialized above.
165        // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above.
166        Ok(GenDisk {
167            _tagset: tagset,
168            gendisk,
169        })
170    }
171}
172
173/// A generic block device.
174///
175/// # Invariants
176///
177/// - `gendisk` must always point to an initialized and valid `struct gendisk`.
178/// - `gendisk` was added to the VFS through a call to
179///   `bindings::device_add_disk`.
180pub struct GenDisk<T: Operations> {
181    _tagset: Arc<TagSet<T>>,
182    gendisk: *mut bindings::gendisk,
183}
184
185// SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
186// `TagSet` It is safe to send this to other threads as long as T is Send.
187unsafe impl<T: Operations + Send> Send for GenDisk<T> {}
188
189impl<T: Operations> Drop for GenDisk<T> {
190    fn drop(&mut self) {
191        // SAFETY: By type invariant, `self.gendisk` points to a valid and
192        // initialized instance of `struct gendisk`, and it was previously added
193        // to the VFS.
194        unsafe { bindings::del_gendisk(self.gendisk) };
195    }
196}