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