Skip to main content

starnix_core/device/
kobject.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// Higher-level view of a device.
23///
24/// Groups devices based on what they do, rather than how they are connected.
25#[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/// Identifies how devices are connected to the processor.
48#[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/// Subsystem with which a [`Device`] is associated.
71#[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/// Device node in the `/sys/devices` hierarchy that can act as a parent for other devices.
101#[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    /// Weak reference to the device's directory in `/sys/devices/...`.
109    ///
110    /// [`KObjectStore`](super::kobject_store::KObjectStore) strongly owns live device directories
111    /// via the sysfs root directory hierarchy. Storing a weak reference here avoids an `Arc` cycle
112    /// with the directory's `"uevent"` [`UEventFsNode`], which holds a clone of [`Device`].
113    #[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    /// Intermediate `<class>` directory name inserted under `parent` for `subsystem`, if any.
150    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    /// Intermediate `<class>` directory name inserted between [`Self::parent`] and this device, if
161    /// any.
162    pub(super) fn glue_dir_name(&self) -> Option<&FsStr> {
163        Self::glue_dir_name_for(self.parent(), self.subsystem())
164    }
165
166    /// Relative path to the device from a directory `depth` levels below the sysfs root.
167    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        // TODO(https://fxbug.dev/42078277): Pass the synthetic UUID when available.
191        // Otherwise, default as "0".
192        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    /// Name of the device in /dev.
232    ///
233    /// Also appears in sysfs via uevent.
234    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            // Ignore empty lines.
317            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            /* parent = */ None,
399            Some(bus.into()),
400            /* metadata = */ 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            /* parent = */ None,
435            Some(bus.into()),
436            /* metadata = */ 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            /* parent = */ None,
465            /* subsystem = */ None,
466            /* metadata = */ 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            /* metadata = */ 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            /* metadata = */ 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            /* metadata = */ 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        // Bus device parented under a class device does not insert a glue directory,
516        // while a class device parented under that bus device does insert its class directory.
517        let child_bus_dev = Device::new(
518            "aux_dev".into(),
519            Some(card0),
520            Some(platform_bus.into()),
521            /* metadata = */ 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            /* metadata = */ 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            /* parent = */ None,
545            /* subsystem = */ None,
546            /* metadata = */ 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        // Now we have metadata, so we expect more properties (DEVNAME, SYNTH_UUID, MAJOR, MINOR).
563        // Original count was 2 (DEVPATH, SUBSYSTEM).
564        // Now we add: DEVNAME, SYNTH_UUID, MAJOR, MINOR. Total 6.
565        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        // The properties from the metadata HashMap are in non-deterministic order.
578        // Sort them by key to make assertions deterministic.
579        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}