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