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