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