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