Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/rust/kernel/block/mq/gen_disk.rs
29268 views
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
// SAFETY: `bindings::queue_limits` contain only fields that are valid when zeroed.
111
let mut lim: bindings::queue_limits = unsafe { core::mem::zeroed() };
112
113
lim.logical_block_size = self.logical_block_size;
114
lim.physical_block_size = self.physical_block_size;
115
if self.rotational {
116
lim.features = bindings::BLK_FEAT_ROTATIONAL;
117
}
118
119
// SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
120
let gendisk = from_err_ptr(unsafe {
121
bindings::__blk_mq_alloc_disk(
122
tagset.raw_tag_set(),
123
&mut lim,
124
data,
125
static_lock_class!().as_ptr(),
126
)
127
})?;
128
129
const TABLE: bindings::block_device_operations = bindings::block_device_operations {
130
submit_bio: None,
131
open: None,
132
release: None,
133
ioctl: None,
134
compat_ioctl: None,
135
check_events: None,
136
unlock_native_capacity: None,
137
getgeo: None,
138
set_read_only: None,
139
swap_slot_free_notify: None,
140
report_zones: None,
141
devnode: None,
142
alternative_gpt_sector: None,
143
get_unique_id: None,
144
// TODO: Set to THIS_MODULE. Waiting for const_refs_to_static feature to
145
// be merged (unstable in rustc 1.78 which is staged for linux 6.10)
146
// <https://github.com/rust-lang/rust/issues/119618>
147
owner: core::ptr::null_mut(),
148
pr_ops: core::ptr::null_mut(),
149
free_disk: None,
150
poll_bio: None,
151
};
152
153
// SAFETY: `gendisk` is a valid pointer as we initialized it above
154
unsafe { (*gendisk).fops = &TABLE };
155
156
let mut writer = NullTerminatedFormatter::new(
157
// SAFETY: `gendisk` points to a valid and initialized instance. We
158
// have exclusive access, since the disk is not added to the VFS
159
// yet.
160
unsafe { &mut (*gendisk).disk_name },
161
)
162
.ok_or(EINVAL)?;
163
writer.write_fmt(name)?;
164
165
// SAFETY: `gendisk` points to a valid and initialized instance of
166
// `struct gendisk`. `set_capacity` takes a lock to synchronize this
167
// operation, so we will not race.
168
unsafe { bindings::set_capacity(gendisk, self.capacity_sectors) };
169
170
crate::error::to_result(
171
// SAFETY: `gendisk` points to a valid and initialized instance of
172
// `struct gendisk`.
173
unsafe {
174
bindings::device_add_disk(core::ptr::null_mut(), gendisk, core::ptr::null_mut())
175
},
176
)?;
177
178
recover_data.dismiss();
179
180
// INVARIANT: `gendisk` was initialized above.
181
// INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above.
182
// INVARIANT: `gendisk.queue.queue_data` is set to `data` in the call to
183
// `__blk_mq_alloc_disk` above.
184
Ok(GenDisk {
185
_tagset: tagset,
186
gendisk,
187
})
188
}
189
}
190
191
/// A generic block device.
192
///
193
/// # Invariants
194
///
195
/// - `gendisk` must always point to an initialized and valid `struct gendisk`.
196
/// - `gendisk` was added to the VFS through a call to
197
/// `bindings::device_add_disk`.
198
/// - `self.gendisk.queue.queuedata` is initialized by a call to `ForeignOwnable::into_foreign`.
199
pub struct GenDisk<T: Operations> {
200
_tagset: Arc<TagSet<T>>,
201
gendisk: *mut bindings::gendisk,
202
}
203
204
// SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
205
// `TagSet` It is safe to send this to other threads as long as T is Send.
206
unsafe impl<T: Operations + Send> Send for GenDisk<T> {}
207
208
impl<T: Operations> Drop for GenDisk<T> {
209
fn drop(&mut self) {
210
// SAFETY: By type invariant of `Self`, `self.gendisk` points to a valid
211
// and initialized instance of `struct gendisk`, and, `queuedata` was
212
// initialized with the result of a call to
213
// `ForeignOwnable::into_foreign`.
214
let queue_data = unsafe { (*(*self.gendisk).queue).queuedata };
215
216
// SAFETY: By type invariant, `self.gendisk` points to a valid and
217
// initialized instance of `struct gendisk`, and it was previously added
218
// to the VFS.
219
unsafe { bindings::del_gendisk(self.gendisk) };
220
221
// SAFETY: `queue.queuedata` was created by `GenDiskBuilder::build` with
222
// a call to `ForeignOwnable::into_foreign` to create `queuedata`.
223
// `ForeignOwnable::from_foreign` is only called here.
224
drop(unsafe { T::QueueData::from_foreign(queue_data) });
225
}
226
}
227
228