1use crate::device::DeviceMode;
6use crate::task::CurrentTask;
7use crate::vfs::buffers::{InputBuffer, OutputBuffer};
8use crate::vfs::pseudo::simple_directory::SimpleDirectory;
9use crate::vfs::{
10 FileObject, FileOps, FsNode, FsNodeOps, FsString, PathBuilder, fileops_impl_noop_sync,
11 fileops_impl_seekable, fs_node_impl_not_dir,
12};
13use starnix_logging::track_stub;
14use starnix_rcu::{RcuHashMap, RcuReadScope};
15use starnix_sync::{FileOpsCore, Locked};
16use starnix_uapi::device_id::DeviceId;
17use starnix_uapi::errors::Errno;
18use starnix_uapi::open_flags::OpenFlags;
19use starnix_uapi::{errno, error};
20use std::sync::Arc;
21
22#[derive(Clone)]
26pub struct Class {
27 pub name: FsString,
28 pub dir: Arc<SimpleDirectory>,
29 pub bus: Bus,
31 pub collection: Arc<SimpleDirectory>,
32}
33
34impl Class {
35 pub fn new(
36 name: FsString,
37 dir: Arc<SimpleDirectory>,
38 bus: Bus,
39 collection: Arc<SimpleDirectory>,
40 ) -> Self {
41 Self { name, dir, bus, collection }
42 }
43}
44
45impl std::fmt::Debug for Class {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("Class").field("name", &self.name).field("bus", &self.bus).finish()
48 }
49}
50
51#[derive(Clone)]
53pub struct Bus {
54 pub name: FsString,
55 pub dir: Arc<SimpleDirectory>,
56 pub collection: Option<Arc<SimpleDirectory>>,
57}
58
59impl Bus {
60 pub fn new(
61 name: FsString,
62 dir: Arc<SimpleDirectory>,
63 collection: Option<Arc<SimpleDirectory>>,
64 ) -> Self {
65 Self { name, dir, collection }
66 }
67}
68
69impl std::fmt::Debug for Bus {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("Bus").field("name", &self.name).finish()
72 }
73}
74
75pub type UEventProperties = Vec<(FsString, FsString)>;
76
77#[derive(Clone, Debug)]
78pub struct Device {
79 pub name: FsString,
80 pub class: Class,
81 pub metadata: Option<DeviceMetadata>,
82}
83
84impl Device {
85 pub fn new(name: FsString, class: Class, metadata: Option<DeviceMetadata>) -> Self {
86 Self { name, class, metadata }
87 }
88
89 pub fn path_from_depth(&self, depth: usize) -> FsString {
91 let mut builder = PathBuilder::new();
92 builder.prepend_element(self.name.as_ref());
93 builder.prepend_element(self.class.name.as_ref());
94 builder.prepend_element(self.class.bus.name.as_ref());
95 builder.prepend_element(b"devices".into());
96 for _ in 0..depth {
97 builder.prepend_element(b"..".into());
98 }
99 builder.build_relative()
100 }
101
102 pub fn uevent_properties(&self, separator: char) -> FsString {
103 let props = self.get_uevent_properties_list();
104 flatten_uevent_properties(props, separator)
105 }
106
107 pub fn get_uevent_properties_list(&self) -> UEventProperties {
108 let mut props = vec![];
109
110 let path = self.path_from_depth(0);
113
114 let mut devpath = vec![b'/'];
115 devpath.extend_from_slice(path.as_ref());
116
117 props.push((b"DEVPATH".into(), devpath.into()));
118 props.push((b"SUBSYSTEM".into(), self.class.name.clone()));
119
120 if let Some(metadata) = &self.metadata {
121 props.push((b"DEVNAME".into(), metadata.devname.clone()));
122 props.push((b"SYNTH_UUID".into(), b"0".into()));
123 props.push((b"MAJOR".into(), metadata.devt.major().to_string().into()));
124 props.push((b"MINOR".into(), metadata.devt.minor().to_string().into()));
125 let scope = RcuReadScope::new();
126 for (key, value) in metadata.properties.iter(&scope) {
127 props.push((key.clone(), value.clone()));
128 }
129 }
130
131 props
132 }
133}
134
135pub fn flatten_uevent_properties(props: UEventProperties, separator: char) -> FsString {
136 let mut result = vec![];
137 let sep = separator as u8;
138 for (key, value) in props {
139 result.extend_from_slice(key.as_ref());
140 result.push(b'=');
141 result.extend_from_slice(value.as_ref());
142 result.push(sep);
143 }
144 result.into()
145}
146
147#[derive(Clone, Debug)]
148pub struct DeviceMetadata {
149 pub devname: FsString,
153 pub devt: DeviceId,
154 pub mode: DeviceMode,
155 pub properties: Arc<RcuHashMap<FsString, FsString>>,
156}
157
158impl DeviceMetadata {
159 pub fn new(devname: FsString, devt: DeviceId, mode: DeviceMode) -> Self {
160 Self { devname, devt, mode, properties: Arc::new(RcuHashMap::default()) }
161 }
162
163 pub fn with_devtype(self, devtype: impl Into<FsString>) -> Self {
164 self.properties.insert(b"DEVTYPE".into(), devtype.into());
165 self
166 }
167}
168
169pub struct UEventFsNode {
170 device: Device,
171}
172
173impl UEventFsNode {
174 pub fn new(device: Device) -> Self {
175 Self { device }
176 }
177}
178
179impl FsNodeOps for UEventFsNode {
180 fs_node_impl_not_dir!();
181
182 fn create_file_ops(
183 &self,
184 _locked: &mut Locked<FileOpsCore>,
185 _node: &FsNode,
186 _current_task: &CurrentTask,
187 _flags: OpenFlags,
188 ) -> Result<Box<dyn FileOps>, Errno> {
189 Ok(Box::new(UEventFile::new(self.device.clone())))
190 }
191}
192
193struct UEventFile {
194 device: Device,
195}
196
197impl UEventFile {
198 pub fn new(device: Device) -> Self {
199 Self { device }
200 }
201
202 fn parse_commands(data: &[u8]) -> Vec<&[u8]> {
203 data.split(|&c| c == b'\0' || c == b'\n').collect()
204 }
205}
206
207impl FileOps for UEventFile {
208 fileops_impl_seekable!();
209 fileops_impl_noop_sync!();
210
211 fn read(
212 &self,
213 _locked: &mut Locked<FileOpsCore>,
214 _file: &FileObject,
215 _current_task: &CurrentTask,
216 offset: usize,
217 data: &mut dyn OutputBuffer,
218 ) -> Result<usize, Errno> {
219 let content = self.device.uevent_properties('\n');
220 let content_bytes: &[u8] = content.as_ref();
221 data.write(content_bytes.get(offset..).ok_or_else(|| errno!(EINVAL))?)
222 }
223
224 fn write(
225 &self,
226 locked: &mut Locked<FileOpsCore>,
227 _file: &FileObject,
228 current_task: &CurrentTask,
229 offset: usize,
230 data: &mut dyn InputBuffer,
231 ) -> Result<usize, Errno> {
232 if offset != 0 {
233 return error!(EINVAL);
234 }
235 let content = data.read_all()?;
236 for command in Self::parse_commands(&content) {
237 if command == b"" {
239 continue;
240 }
241
242 match UEventAction::try_from(command) {
243 Ok(c) => current_task.kernel().device_registry.dispatch_uevent(
244 locked,
245 c,
246 self.device.clone(),
247 ),
248 Err(e) => {
249 track_stub!(TODO("https://fxbug.dev/297435061"), "synthetic uevent variables");
250 return Err(e);
251 }
252 }
253 }
254 Ok(content.len())
255 }
256}
257
258#[derive(Copy, Clone, Eq, PartialEq, Debug)]
259pub enum UEventAction {
260 Add,
261 Remove,
262 Change,
263 Move,
264 Online,
265 Offline,
266 Bind,
267 Unbind,
268}
269
270impl std::fmt::Display for UEventAction {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 match self {
273 UEventAction::Add => write!(f, "add"),
274 UEventAction::Remove => write!(f, "remove"),
275 UEventAction::Change => write!(f, "change"),
276 UEventAction::Move => write!(f, "move"),
277 UEventAction::Online => write!(f, "online"),
278 UEventAction::Offline => write!(f, "offline"),
279 UEventAction::Bind => write!(f, "bind"),
280 UEventAction::Unbind => write!(f, "unbind"),
281 }
282 }
283}
284
285impl TryFrom<&[u8]> for UEventAction {
286 type Error = Errno;
287
288 fn try_from(action: &[u8]) -> Result<Self, Self::Error> {
289 match action {
290 b"add" => Ok(UEventAction::Add),
291 b"remove" => Ok(UEventAction::Remove),
292 b"change" => Ok(UEventAction::Change),
293 b"move" => Ok(UEventAction::Move),
294 b"online" => Ok(UEventAction::Online),
295 b"offline" => Ok(UEventAction::Offline),
296 b"bind" => Ok(UEventAction::Bind),
297 b"unbind" => Ok(UEventAction::Unbind),
298 _ => error!(EINVAL),
299 }
300 }
301}
302
303#[derive(Copy, Clone)]
304pub struct UEventContext {
305 pub seqnum: u64,
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::vfs::pseudo::simple_directory::SimpleDirectory;
312 use starnix_uapi::device_id::DeviceId;
313
314 #[test]
315 fn test_uevent_properties() {
316 let dir = SimpleDirectory::new();
317 let collection = SimpleDirectory::new();
318 let bus = Bus::new("bus".into(), dir.clone(), Some(collection.clone()));
319 let class = Class::new("class".into(), dir.clone(), bus, collection);
320 let device = Device::new(
321 "device".into(),
322 class,
323 Some(
324 DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)
325 .with_devtype("disk"),
326 ),
327 );
328
329 assert_eq!(
330 device.uevent_properties('\n'),
331 b"DEVPATH=/devices/bus/class/device\n\
332 SUBSYSTEM=class\n\
333 DEVNAME=devname\n\
334 SYNTH_UUID=0\n\
335 MAJOR=1\n\
336 MINOR=2\n\
337 DEVTYPE=disk\n"
338 );
339 }
340
341 #[test]
342 fn test_uevent_properties_no_devtype() {
343 let dir = SimpleDirectory::new();
344 let collection = SimpleDirectory::new();
345 let bus = Bus::new("bus".into(), dir.clone(), Some(collection.clone()));
346 let class = Class::new("class".into(), dir.clone(), bus, collection);
347 let device = Device::new(
348 "device".into(),
349 class,
350 Some(DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)),
351 );
352
353 assert_eq!(
354 device.uevent_properties('\n'),
355 b"DEVPATH=/devices/bus/class/device\n\
356 SUBSYSTEM=class\n\
357 DEVNAME=devname\n\
358 SYNTH_UUID=0\n\
359 MAJOR=1\n\
360 MINOR=2\n"
361 );
362 }
363
364 #[::fuchsia::test]
365 fn test_get_uevent_properties_list() {
366 let bus = Bus::new("virtual".into(), SimpleDirectory::new(), None);
367 let class =
368 Class::new("android_usb".into(), SimpleDirectory::new(), bus, SimpleDirectory::new());
369 let metadata =
370 DeviceMetadata::new("android0".into(), DeviceId::new(1, 2), DeviceMode::Char);
371 let device = Device::new("android0".into(), class, Some(metadata));
372
373 let props = device.get_uevent_properties_list();
374
375 assert_eq!(props.len(), 6);
379 assert_eq!(props[0], ("DEVPATH".into(), "/devices/virtual/android_usb/android0".into()));
380 assert_eq!(props[1], ("SUBSYSTEM".into(), "android_usb".into()));
381
382 let properties = &device.metadata.as_ref().unwrap().properties;
383 properties.insert("USB_STATE".into(), "CONNECTED".into());
384 properties.insert("ABC".into(), "XYZ".into());
385 properties.insert("FOO".into(), "BAR".into());
386
387 let props = device.get_uevent_properties_list();
388
389 assert_eq!(props.len(), 9);
390 assert_eq!(props[6], ("USB_STATE".into(), "CONNECTED".into()));
391 assert_eq!(props[7], ("ABC".into(), "XYZ".into()));
392 assert_eq!(props[8], ("FOO".into(), "BAR".into()));
393 }
394}