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, FsStr, FsString, PathBuilder, fileops_impl_noop_sync,
11 fileops_impl_seekable, fs_node_impl_not_dir,
12};
13use derivative::Derivative;
14use starnix_logging::track_stub;
15use starnix_rcu::{RcuHashMap, RcuReadScope};
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, Weak};
21
22#[derive(Clone, Derivative)]
26#[derivative(Debug)]
27pub struct Class {
28 name: FsString,
29 #[derivative(Debug = "ignore")]
30 devices: Arc<SimpleDirectory>,
31}
32
33impl Class {
34 pub(super) fn new(name: FsString, devices: Arc<SimpleDirectory>) -> Self {
35 Self { name, devices }
36 }
37
38 pub fn name(&self) -> &FsStr {
39 self.name.as_ref()
40 }
41
42 pub(super) fn devices(&self) -> &Arc<SimpleDirectory> {
43 &self.devices
44 }
45}
46
47#[derive(Clone, Derivative)]
49#[derivative(Debug)]
50pub struct Bus {
51 name: FsString,
52 #[derivative(Debug = "ignore")]
53 devices: Arc<SimpleDirectory>,
54}
55
56impl Bus {
57 pub(super) fn new(name: FsString, devices: Arc<SimpleDirectory>) -> Self {
58 Self { name, devices }
59 }
60
61 pub fn name(&self) -> &FsStr {
62 self.name.as_ref()
63 }
64
65 pub(super) fn devices(&self) -> &Arc<SimpleDirectory> {
66 &self.devices
67 }
68}
69
70#[derive(Clone, Debug)]
72pub(super) enum Subsystem {
73 Bus(Bus),
74 Class(Class),
75}
76
77impl Subsystem {
78 pub(super) fn name(&self) -> &FsStr {
79 match self {
80 Self::Bus(bus) => bus.name(),
81 Self::Class(class) => class.name(),
82 }
83 }
84}
85
86impl From<Bus> for Subsystem {
87 fn from(bus: Bus) -> Self {
88 Self::Bus(bus)
89 }
90}
91
92impl From<Class> for Subsystem {
93 fn from(class: Class) -> Self {
94 Self::Class(class)
95 }
96}
97
98pub type UEventProperties = Vec<(FsString, FsString)>;
99
100#[derive(Clone, Derivative)]
102#[derivative(Debug)]
103pub struct Device {
104 name: FsString,
105 parent: Option<Arc<Device>>,
106 subsystem: Option<Subsystem>,
107 metadata: Option<DeviceMetadata>,
108 #[derivative(Debug = "ignore")]
114 dir: Weak<SimpleDirectory>,
115}
116
117impl Device {
118 pub(super) fn new(
119 name: FsString,
120 parent: Option<Device>,
121 subsystem: Option<Subsystem>,
122 metadata: Option<DeviceMetadata>,
123 dir: Arc<SimpleDirectory>,
124 ) -> Self {
125 debug_assert!(parent.is_some() || !matches!(subsystem, Some(Subsystem::Class(_))));
126 Self { name, parent: parent.map(Arc::new), subsystem, metadata, dir: Arc::downgrade(&dir) }
127 }
128
129 pub fn name(&self) -> &FsStr {
130 self.name.as_ref()
131 }
132
133 pub fn parent(&self) -> Option<&Device> {
134 self.parent.as_deref()
135 }
136
137 pub fn metadata(&self) -> Option<&DeviceMetadata> {
138 self.metadata.as_ref()
139 }
140
141 pub(super) fn subsystem(&self) -> Option<&Subsystem> {
142 self.subsystem.as_ref()
143 }
144
145 pub(super) fn dir(&self) -> Option<Arc<SimpleDirectory>> {
146 self.dir.upgrade()
147 }
148
149 pub(super) fn glue_dir_name_for<'a>(
151 parent: Option<&Device>,
152 subsystem: Option<&'a Subsystem>,
153 ) -> Option<&'a FsStr> {
154 let (Some(parent), Some(Subsystem::Class(class))) = (parent, subsystem) else {
155 return None;
156 };
157 (!matches!(parent.subsystem(), Some(Subsystem::Class(_)))).then(|| class.name())
158 }
159
160 pub(super) fn glue_dir_name(&self) -> Option<&FsStr> {
163 Self::glue_dir_name_for(self.parent(), self.subsystem())
164 }
165
166 pub fn path_from_depth(&self, depth: usize) -> FsString {
168 let mut builder = PathBuilder::new();
169 for current in std::iter::successors(Some(self), |dev| dev.parent()) {
170 builder.prepend_element(current.name());
171 if let Some(glue_dir) = current.glue_dir_name() {
172 builder.prepend_element(glue_dir);
173 }
174 }
175 builder.prepend_element(b"devices".into());
176 for _ in 0..depth {
177 builder.prepend_element(b"..".into());
178 }
179 builder.build_relative()
180 }
181
182 pub fn uevent_properties(&self, separator: char) -> FsString {
183 let props = self.get_uevent_properties_list();
184 flatten_uevent_properties(props, separator)
185 }
186
187 pub fn get_uevent_properties_list(&self) -> UEventProperties {
188 let mut props = vec![];
189
190 let path = self.path_from_depth(0);
193
194 let mut devpath = vec![b'/'];
195 devpath.extend_from_slice(path.as_ref());
196
197 props.push((b"DEVPATH".into(), devpath.into()));
198 if let Some(subsystem) = &self.subsystem {
199 props.push((b"SUBSYSTEM".into(), subsystem.name().to_owned()));
200 }
201
202 if let Some(metadata) = &self.metadata {
203 props.push((b"DEVNAME".into(), metadata.devname.clone()));
204 props.push((b"SYNTH_UUID".into(), b"0".into()));
205 props.push((b"MAJOR".into(), metadata.devt.major().to_string().into()));
206 props.push((b"MINOR".into(), metadata.devt.minor().to_string().into()));
207 let scope = RcuReadScope::new();
208 for (key, value) in metadata.properties.iter(&scope) {
209 props.push((key.clone(), value.clone()));
210 }
211 }
212
213 props
214 }
215}
216
217pub fn flatten_uevent_properties(props: UEventProperties, separator: char) -> FsString {
218 let mut result = vec![];
219 let sep = separator as u8;
220 for (key, value) in props {
221 result.extend_from_slice(key.as_ref());
222 result.push(b'=');
223 result.extend_from_slice(value.as_ref());
224 result.push(sep);
225 }
226 result.into()
227}
228
229#[derive(Clone, Debug)]
230pub struct DeviceMetadata {
231 pub devname: FsString,
235 pub devt: DeviceId,
236 pub mode: DeviceMode,
237 pub properties: Arc<RcuHashMap<FsString, FsString>>,
238}
239
240impl DeviceMetadata {
241 pub fn new(devname: FsString, devt: DeviceId, mode: DeviceMode) -> Self {
242 Self { devname, devt, mode, properties: Arc::new(RcuHashMap::default()) }
243 }
244
245 pub fn with_devtype(self, devtype: impl Into<FsString>) -> Self {
246 self.properties.insert(b"DEVTYPE".into(), devtype.into());
247 self
248 }
249}
250
251pub struct UEventFsNode {
252 device: Device,
253}
254
255impl UEventFsNode {
256 pub fn new(device: Device) -> Self {
257 Self { device }
258 }
259}
260
261impl FsNodeOps for UEventFsNode {
262 fs_node_impl_not_dir!();
263
264 fn create_file_ops(
265 &self,
266 _node: &FsNode,
267 _current_task: &CurrentTask,
268 _flags: OpenFlags,
269 ) -> Result<Box<dyn FileOps>, Errno> {
270 Ok(Box::new(UEventFile::new(self.device.clone())))
271 }
272}
273
274struct UEventFile {
275 device: Device,
276}
277
278impl UEventFile {
279 pub fn new(device: Device) -> Self {
280 Self { device }
281 }
282
283 fn parse_commands(data: &[u8]) -> Vec<&[u8]> {
284 data.split(|&c| c == b'\0' || c == b'\n').collect()
285 }
286}
287
288impl FileOps for UEventFile {
289 fileops_impl_seekable!();
290 fileops_impl_noop_sync!();
291
292 fn read(
293 &self,
294 _file: &FileObject,
295 _current_task: &CurrentTask,
296 offset: usize,
297 data: &mut dyn OutputBuffer,
298 ) -> Result<usize, Errno> {
299 let content = self.device.uevent_properties('\n');
300 let content_bytes: &[u8] = content.as_ref();
301 data.write(content_bytes.get(offset..).ok_or_else(|| errno!(EINVAL))?)
302 }
303
304 fn write(
305 &self,
306 _file: &FileObject,
307 current_task: &CurrentTask,
308 offset: usize,
309 data: &mut dyn InputBuffer,
310 ) -> Result<usize, Errno> {
311 if offset != 0 {
312 return error!(EINVAL);
313 }
314 let content = data.read_all()?;
315 for command in Self::parse_commands(&content) {
316 if command == b"" {
318 continue;
319 }
320
321 match UEventAction::try_from(command) {
322 Ok(c) => {
323 current_task.kernel().device_registry.dispatch_uevent(c, self.device.clone())
324 }
325 Err(e) => {
326 track_stub!(TODO("https://fxbug.dev/297435061"), "synthetic uevent variables");
327 return Err(e);
328 }
329 }
330 }
331 Ok(content.len())
332 }
333}
334
335#[derive(Copy, Clone, Eq, PartialEq, Debug)]
336pub enum UEventAction {
337 Add,
338 Remove,
339 Change,
340 Move,
341 Online,
342 Offline,
343 Bind,
344 Unbind,
345}
346
347impl std::fmt::Display for UEventAction {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 match self {
350 UEventAction::Add => write!(f, "add"),
351 UEventAction::Remove => write!(f, "remove"),
352 UEventAction::Change => write!(f, "change"),
353 UEventAction::Move => write!(f, "move"),
354 UEventAction::Online => write!(f, "online"),
355 UEventAction::Offline => write!(f, "offline"),
356 UEventAction::Bind => write!(f, "bind"),
357 UEventAction::Unbind => write!(f, "unbind"),
358 }
359 }
360}
361
362impl TryFrom<&[u8]> for UEventAction {
363 type Error = Errno;
364
365 fn try_from(action: &[u8]) -> Result<Self, Self::Error> {
366 match action {
367 b"add" => Ok(UEventAction::Add),
368 b"remove" => Ok(UEventAction::Remove),
369 b"change" => Ok(UEventAction::Change),
370 b"move" => Ok(UEventAction::Move),
371 b"online" => Ok(UEventAction::Online),
372 b"offline" => Ok(UEventAction::Offline),
373 b"bind" => Ok(UEventAction::Bind),
374 b"unbind" => Ok(UEventAction::Unbind),
375 _ => error!(EINVAL),
376 }
377 }
378}
379
380#[derive(Copy, Clone)]
381pub struct UEventContext {
382 pub seqnum: u64,
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::vfs::pseudo::simple_directory::SimpleDirectory;
389 use starnix_uapi::device_id::DeviceId;
390
391 #[test]
392 fn test_uevent_properties() {
393 let dir = SimpleDirectory::new();
394 let devices = SimpleDirectory::new();
395 let bus = Bus::new("bus".into(), devices.clone());
396 let parent = Device::new(
397 "bus_dev".into(),
398 None,
399 Some(bus.into()),
400 None,
401 dir.clone(),
402 );
403 let class = Class::new("class".into(), devices);
404 let device = Device::new(
405 "device".into(),
406 Some(parent),
407 Some(class.into()),
408 Some(
409 DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)
410 .with_devtype("disk"),
411 ),
412 dir,
413 );
414
415 assert_eq!(
416 device.uevent_properties('\n'),
417 b"DEVPATH=/devices/bus_dev/class/device\n\
418 SUBSYSTEM=class\n\
419 DEVNAME=devname\n\
420 SYNTH_UUID=0\n\
421 MAJOR=1\n\
422 MINOR=2\n\
423 DEVTYPE=disk\n"
424 );
425 }
426
427 #[test]
428 fn test_uevent_properties_no_devtype() {
429 let dir = SimpleDirectory::new();
430 let devices = SimpleDirectory::new();
431 let bus = Bus::new("bus".into(), devices.clone());
432 let parent = Device::new(
433 "bus_dev".into(),
434 None,
435 Some(bus.into()),
436 None,
437 dir.clone(),
438 );
439 let class = Class::new("class".into(), devices);
440 let device = Device::new(
441 "device".into(),
442 Some(parent),
443 Some(class.into()),
444 Some(DeviceMetadata::new("devname".into(), DeviceId::new(1, 2), DeviceMode::Char)),
445 dir,
446 );
447
448 assert_eq!(
449 device.uevent_properties('\n'),
450 b"DEVPATH=/devices/bus_dev/class/device\n\
451 SUBSYSTEM=class\n\
452 DEVNAME=devname\n\
453 SYNTH_UUID=0\n\
454 MAJOR=1\n\
455 MINOR=2\n"
456 );
457 }
458
459 #[::fuchsia::test]
460 fn test_root_bus_and_nested_class_device_uevent_properties() {
461 let dir = SimpleDirectory::new();
462 let root_device = Device::new(
463 "platform".into(),
464 None,
465 None,
466 None,
467 dir.clone(),
468 );
469 assert_eq!(root_device.path_from_depth(0), b"devices/platform");
470 assert_eq!(root_device.uevent_properties('\n'), b"DEVPATH=/devices/platform\n");
471
472 let platform_bus = Bus::new("platform".into(), SimpleDirectory::new());
473 let soc_device = Device::new(
474 "soc".into(),
475 Some(root_device),
476 Some(platform_bus.clone().into()),
477 None,
478 dir.clone(),
479 );
480 assert_eq!(soc_device.path_from_depth(0), b"devices/platform/soc");
481 assert_eq!(soc_device.path_from_depth(3), b"../../../devices/platform/soc");
482 assert_eq!(
483 soc_device.uevent_properties('\n'),
484 b"DEVPATH=/devices/platform/soc\n\
485 SUBSYSTEM=platform\n"
486 );
487
488 let drm_class = Class::new("drm".into(), SimpleDirectory::new());
489 let card0 = Device::new(
490 "card0".into(),
491 Some(soc_device),
492 Some(drm_class.clone().into()),
493 None,
494 dir.clone(),
495 );
496 assert_eq!(card0.path_from_depth(0), b"devices/platform/soc/drm/card0");
497
498 let connector = Device::new(
499 "sde-conn-0-DSI-1".into(),
500 Some(card0.clone()),
501 Some(drm_class.into()),
502 None,
503 dir.clone(),
504 );
505 assert_eq!(
506 connector.path_from_depth(0),
507 b"devices/platform/soc/drm/card0/sde-conn-0-DSI-1"
508 );
509 assert_eq!(
510 connector.uevent_properties('\n'),
511 b"DEVPATH=/devices/platform/soc/drm/card0/sde-conn-0-DSI-1\n\
512 SUBSYSTEM=drm\n"
513 );
514
515 let child_bus_dev = Device::new(
518 "aux_dev".into(),
519 Some(card0),
520 Some(platform_bus.into()),
521 None,
522 dir.clone(),
523 );
524 assert_eq!(child_bus_dev.path_from_depth(0), b"devices/platform/soc/drm/card0/aux_dev");
525
526 let wakeup_class = Class::new("wakeup".into(), SimpleDirectory::new());
527 let grandchild_class_dev = Device::new(
528 "wakeup0".into(),
529 Some(child_bus_dev),
530 Some(wakeup_class.into()),
531 None,
532 dir,
533 );
534 assert_eq!(
535 grandchild_class_dev.path_from_depth(0),
536 b"devices/platform/soc/drm/card0/aux_dev/wakeup/wakeup0"
537 );
538 }
539
540 #[::fuchsia::test]
541 fn test_get_uevent_properties_list() {
542 let virtual_device = Device::new(
543 "virtual".into(),
544 None,
545 None,
546 None,
547 SimpleDirectory::new(),
548 );
549 let class = Class::new("android_usb".into(), SimpleDirectory::new());
550 let metadata =
551 DeviceMetadata::new("android0".into(), DeviceId::new(1, 2), DeviceMode::Char);
552 let device = Device::new(
553 "android0".into(),
554 Some(virtual_device),
555 Some(class.into()),
556 Some(metadata),
557 SimpleDirectory::new(),
558 );
559
560 let props = device.get_uevent_properties_list();
561
562 assert_eq!(props.len(), 6);
566 assert_eq!(props[0], ("DEVPATH".into(), "/devices/virtual/android_usb/android0".into()));
567 assert_eq!(props[1], ("SUBSYSTEM".into(), "android_usb".into()));
568
569 let properties = &device.metadata().unwrap().properties;
570 properties.insert("USB_STATE".into(), "CONNECTED".into());
571 properties.insert("ABC".into(), "XYZ".into());
572 properties.insert("FOO".into(), "BAR".into());
573
574 let mut props = device.get_uevent_properties_list();
575
576 assert_eq!(props.len(), 9);
577 props[6..].sort();
580 assert_eq!(props[6], ("ABC".into(), "XYZ".into()));
581 assert_eq!(props[7], ("FOO".into(), "BAR".into()));
582 assert_eq!(props[8], ("USB_STATE".into(), "CONNECTED".into()));
583 }
584}