xref: /linux/drivers/block/rnull/configfs.rs (revision d8d2b1f81530988abe2e2bfaceec1c5d30b9a0b4)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 use super::{NullBlkDevice, THIS_MODULE};
4 use core::fmt::{Display, Write};
5 use kernel::{
6     block::mq::gen_disk::{GenDisk, GenDiskBuilder},
7     c_str,
8     configfs::{self, AttributeOperations},
9     configfs_attrs, new_mutex,
10     page::PAGE_SIZE,
11     prelude::*,
12     str::{kstrtobool_bytes, CString},
13     sync::Mutex,
14 };
15 use pin_init::PinInit;
16 
17 pub(crate) fn subsystem() -> impl PinInit<kernel::configfs::Subsystem<Config>, Error> {
18     let item_type = configfs_attrs! {
19         container: configfs::Subsystem<Config>,
20         data: Config,
21         child: DeviceConfig,
22         attributes: [
23             features: 0,
24         ],
25     };
26 
27     kernel::configfs::Subsystem::new(c_str!("rnull"), item_type, try_pin_init!(Config {}))
28 }
29 
30 #[pin_data]
31 pub(crate) struct Config {}
32 
33 #[vtable]
34 impl AttributeOperations<0> for Config {
35     type Data = Config;
36 
37     fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
38         let mut writer = kernel::str::Formatter::new(page);
39         writer.write_str("blocksize,size,rotational,irqmode\n")?;
40         Ok(writer.bytes_written())
41     }
42 }
43 
44 #[vtable]
45 impl configfs::GroupOperations for Config {
46     type Child = DeviceConfig;
47 
48     fn make_group(
49         &self,
50         name: &CStr,
51     ) -> Result<impl PinInit<configfs::Group<DeviceConfig>, Error>> {
52         let item_type = configfs_attrs! {
53             container: configfs::Group<DeviceConfig>,
54             data: DeviceConfig,
55             attributes: [
56                 // Named for compatibility with C null_blk
57                 power: 0,
58                 blocksize: 1,
59                 rotational: 2,
60                 size: 3,
61                 irqmode: 4,
62             ],
63         };
64 
65         Ok(configfs::Group::new(
66             name.try_into()?,
67             item_type,
68             // TODO: cannot coerce new_mutex!() to impl PinInit<_, Error>, so put mutex inside
69             try_pin_init!( DeviceConfig {
70                 data <- new_mutex!(DeviceConfigInner {
71                     powered: false,
72                     block_size: 4096,
73                     rotational: false,
74                     disk: None,
75                     capacity_mib: 4096,
76                     irq_mode: IRQMode::None,
77                     name: name.try_into()?,
78                 }),
79             }),
80         ))
81     }
82 }
83 
84 #[derive(Debug, Clone, Copy)]
85 pub(crate) enum IRQMode {
86     None,
87     Soft,
88 }
89 
90 impl TryFrom<u8> for IRQMode {
91     type Error = kernel::error::Error;
92 
93     fn try_from(value: u8) -> Result<Self> {
94         match value {
95             0 => Ok(Self::None),
96             1 => Ok(Self::Soft),
97             _ => Err(EINVAL),
98         }
99     }
100 }
101 
102 impl Display for IRQMode {
103     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104         match self {
105             Self::None => f.write_str("0")?,
106             Self::Soft => f.write_str("1")?,
107         }
108         Ok(())
109     }
110 }
111 
112 #[pin_data]
113 pub(crate) struct DeviceConfig {
114     #[pin]
115     data: Mutex<DeviceConfigInner>,
116 }
117 
118 #[pin_data]
119 struct DeviceConfigInner {
120     powered: bool,
121     name: CString,
122     block_size: u32,
123     rotational: bool,
124     capacity_mib: u64,
125     irq_mode: IRQMode,
126     disk: Option<GenDisk<NullBlkDevice>>,
127 }
128 
129 #[vtable]
130 impl configfs::AttributeOperations<0> for DeviceConfig {
131     type Data = DeviceConfig;
132 
133     fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
134         let mut writer = kernel::str::Formatter::new(page);
135 
136         if this.data.lock().powered {
137             writer.write_str("1\n")?;
138         } else {
139             writer.write_str("0\n")?;
140         }
141 
142         Ok(writer.bytes_written())
143     }
144 
145     fn store(this: &DeviceConfig, page: &[u8]) -> Result {
146         let power_op = kstrtobool_bytes(page)?;
147         let mut guard = this.data.lock();
148 
149         if !guard.powered && power_op {
150             guard.disk = Some(NullBlkDevice::new(
151                 &guard.name,
152                 guard.block_size,
153                 guard.rotational,
154                 guard.capacity_mib,
155                 guard.irq_mode,
156             )?);
157             guard.powered = true;
158         } else if guard.powered && !power_op {
159             drop(guard.disk.take());
160             guard.powered = false;
161         }
162 
163         Ok(())
164     }
165 }
166 
167 #[vtable]
168 impl configfs::AttributeOperations<1> for DeviceConfig {
169     type Data = DeviceConfig;
170 
171     fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
172         let mut writer = kernel::str::Formatter::new(page);
173         writer.write_fmt(fmt!("{}\n", this.data.lock().block_size))?;
174         Ok(writer.bytes_written())
175     }
176 
177     fn store(this: &DeviceConfig, page: &[u8]) -> Result {
178         if this.data.lock().powered {
179             return Err(EBUSY);
180         }
181 
182         let text = core::str::from_utf8(page)?.trim();
183         let value = text.parse::<u32>().map_err(|_| EINVAL)?;
184 
185         GenDiskBuilder::validate_block_size(value)?;
186         this.data.lock().block_size = value;
187         Ok(())
188     }
189 }
190 
191 #[vtable]
192 impl configfs::AttributeOperations<2> for DeviceConfig {
193     type Data = DeviceConfig;
194 
195     fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
196         let mut writer = kernel::str::Formatter::new(page);
197 
198         if this.data.lock().rotational {
199             writer.write_str("1\n")?;
200         } else {
201             writer.write_str("0\n")?;
202         }
203 
204         Ok(writer.bytes_written())
205     }
206 
207     fn store(this: &DeviceConfig, page: &[u8]) -> Result {
208         if this.data.lock().powered {
209             return Err(EBUSY);
210         }
211 
212         this.data.lock().rotational = kstrtobool_bytes(page)?;
213 
214         Ok(())
215     }
216 }
217 
218 #[vtable]
219 impl configfs::AttributeOperations<3> for DeviceConfig {
220     type Data = DeviceConfig;
221 
222     fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
223         let mut writer = kernel::str::Formatter::new(page);
224         writer.write_fmt(fmt!("{}\n", this.data.lock().capacity_mib))?;
225         Ok(writer.bytes_written())
226     }
227 
228     fn store(this: &DeviceConfig, page: &[u8]) -> Result {
229         if this.data.lock().powered {
230             return Err(EBUSY);
231         }
232 
233         let text = core::str::from_utf8(page)?.trim();
234         let value = text.parse::<u64>().map_err(|_| EINVAL)?;
235 
236         this.data.lock().capacity_mib = value;
237         Ok(())
238     }
239 }
240 
241 #[vtable]
242 impl configfs::AttributeOperations<4> for DeviceConfig {
243     type Data = DeviceConfig;
244 
245     fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
246         let mut writer = kernel::str::Formatter::new(page);
247         writer.write_fmt(fmt!("{}\n", this.data.lock().irq_mode))?;
248         Ok(writer.bytes_written())
249     }
250 
251     fn store(this: &DeviceConfig, page: &[u8]) -> Result {
252         if this.data.lock().powered {
253             return Err(EBUSY);
254         }
255 
256         let text = core::str::from_utf8(page)?.trim();
257         let value = text.parse::<u8>().map_err(|_| EINVAL)?;
258 
259         this.data.lock().irq_mode = IRQMode::try_from(value)?;
260         Ok(())
261     }
262 }
263