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, 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/// 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 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/// Device node in the `/sys/devices` hierarchy that can act as a parent for other devices.
103#[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    /// Weak reference to the device's directory in `/sys/devices/...`.
111    ///
112    /// [`KObjectStore`](super::kobject_store::KObjectStore) strongly owns live device directories
113    /// via the sysfs root directory hierarchy. Storing a weak reference here avoids an `Arc` cycle
114    /// with the directory's `"uevent"` [`UEventFsNode`], which holds a clone of [`Device`].
115    #[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    /// Modifies this device's sysfs directory if the device is still registered in sysfs.
152    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    /// Intermediate `<class>` directory name inserted under `parent` for `subsystem`, if any.
159    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    /// Intermediate `<class>` directory name inserted between [`Self::parent`] and this device, if
170    /// any.
171    pub(super) fn glue_dir_name(&self) -> Option<&FsStr> {
172        Self::glue_dir_name_for(self.parent(), self.subsystem())
173    }
174
175    /// Relative path to the device from a directory `depth` levels below the sysfs root.
176    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        // TODO(https://fxbug.dev/42078277): Pass the synthetic UUID when available.
200        // Otherwise, default as "0".
201        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    /// Name of the device in /dev.
241    ///
242    /// Also appears in sysfs via uevent.
243    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            // Ignore empty lines.
326            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            /* parent = */ None,
408            Some(bus.into()),
409            /* metadata = */ 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            /* parent = */ None,
444            Some(bus.into()),
445            /* metadata = */ 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            /* parent = */ None,
474            /* subsystem = */ None,
475            /* metadata = */ 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            /* metadata = */ 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            /* metadata = */ 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            /* metadata = */ 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        // Bus device parented under a class device does not insert a glue directory,
525        // while a class device parented under that bus device does insert its class directory.
526        let child_bus_dev = Device::new(
527            "aux_dev".into(),
528            Some(card0),
529            Some(platform_bus.into()),
530            /* metadata = */ 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            /* metadata = */ 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            /* parent = */ None,
554            /* subsystem = */ None,
555            /* metadata = */ 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        // Now we have metadata, so we expect more properties (DEVNAME, SYNTH_UUID, MAJOR, MINOR).
572        // Original count was 2 (DEVPATH, SUBSYSTEM).
573        // Now we add: DEVNAME, SYNTH_UUID, MAJOR, MINOR. Total 6.
574        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        // The properties from the metadata HashMap are in non-deterministic order.
587        // Sort them by key to make assertions deterministic.
588        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}